-
Notifications
You must be signed in to change notification settings - Fork 546
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
cosign verify-manifest
command (#490)
* Add `cosign verify-manifest` command Signed-off-by: Joshua Hansen <[email protected]> * add allowed extension check Signed-off-by: Batuhan Apaydın <[email protected]> * Remove Dockerfile refs from help Signed-off-by: Joshua Hansen <[email protected]> * Fix lint, doc checks Signed-off-by: Joshua Hansen <[email protected]> Co-authored-by: Batuhan Apaydın <[email protected]>
- Loading branch information
1 parent
7e9cdfb
commit 0fdfaa9
Showing
6 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright 2021 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cli | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/peterbourgon/ff/v3/ffcli" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// VerifyCommand verifies all image signatures on a supplied k8s resource | ||
type VerifyManifestCommand struct { | ||
VerifyCommand | ||
} | ||
|
||
// Verify builds and returns an ffcli command | ||
func VerifyManifest() *ffcli.Command { | ||
cmd := VerifyManifestCommand{VerifyCommand: VerifyCommand{}} | ||
flagset := flag.NewFlagSet("cosign verify-manifest", flag.ExitOnError) | ||
applyVerifyFlags(&cmd.VerifyCommand, flagset) | ||
|
||
return &ffcli.Command{ | ||
Name: "verify-manifest", | ||
ShortUsage: "cosign verify-manifest -key <key path>|<key url>|<kms uri> <path/to/manifest>", | ||
ShortHelp: "Verify all signatures of images specified in the manifest", | ||
LongHelp: `Verify all signature of images in a Kubernetes resource manifest by checking claims | ||
against the transparency log. | ||
EXAMPLES | ||
# verify cosign claims and signing certificates on images in the manifest | ||
cosign verify-manifest <path/to/my-deployment.yaml> | ||
# additionally verify specified annotations | ||
cosign verify-manifest -a key1=val1 -a key2=val2 <path/to/my-deployment.yaml> | ||
# (experimental) additionally, verify with the transparency log | ||
COSIGN_EXPERIMENTAL=1 cosign verify-manifest <path/to/my-deployment.yaml> | ||
# verify images with public key | ||
cosign verify-manifest -key cosign.pub <path/to/my-deployment.yaml> | ||
# verify images with public key provided by URL | ||
cosign verify-manifest -key https://host.for/<FILE> <path/to/my-deployment.yaml> | ||
# verify images with public key stored in Azure Key Vault | ||
cosign verify-manifest -key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] <path/to/my-deployment.yaml> | ||
# verify images with public key stored in AWS KMS | ||
cosign verify-manifest -key awskms://[ENDPOINT]/[ID/ALIAS/ARN] <path/to/my-deployment.yaml> | ||
# verify images with public key stored in Google Cloud KMS | ||
cosign verify-manifest -key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] <path/to/my-deployment.yaml> | ||
# verify images with public key stored in Hashicorp Vault | ||
cosign verify-manifest -key hashivault://[KEY] <path/to/my-deployment.yaml>`, | ||
|
||
FlagSet: flagset, | ||
Exec: cmd.Exec, | ||
} | ||
} | ||
|
||
// Exec runs the verification command | ||
func (c *VerifyManifestCommand) Exec(ctx context.Context, args []string) error { | ||
if len(args) != 1 { | ||
return flag.ErrHelp | ||
} | ||
|
||
manifestPath := args[0] | ||
|
||
err := isExtensionAllowed(manifestPath) | ||
if err != nil { | ||
return errors.Wrap(err, "check if extension is valid") | ||
} | ||
|
||
manifest, err := ioutil.ReadFile(manifestPath) | ||
if err != nil { | ||
return fmt.Errorf("could not read manifest: %v", err) | ||
} | ||
|
||
images := getImagesFromYamlManifest(string(manifest)) | ||
if len(images) == 0 { | ||
return errors.New("no images found in manifest") | ||
} | ||
fmt.Fprintf(os.Stderr, "Extracted image(s): %s\n", strings.Join(images, ", ")) | ||
|
||
return c.VerifyCommand.Exec(ctx, images) | ||
} | ||
|
||
func getImagesFromYamlManifest(manifest string) []string { | ||
var images []string | ||
re := regexp.MustCompile(`image:\s?(?P<Image>.*)\s?`) | ||
for _, s := range re.FindAllStringSubmatch(manifest, -1) { | ||
images = append(images, s[1]) | ||
} | ||
return images | ||
} | ||
|
||
func isExtensionAllowed(ext string) error { | ||
allowedExtensions := allowedExtensionsForManifest() | ||
for _, v := range allowedExtensions { | ||
if strings.EqualFold(filepath.Ext(strings.TrimSpace(ext)), v) { | ||
return nil | ||
} | ||
} | ||
return fmt.Errorf("only %v manifests are supported at this time", allowedExtensions) | ||
} | ||
|
||
func allowedExtensionsForManifest() []string { | ||
return []string{".yaml", ".yml"} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2021 The Sigstore Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cli | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
const SingleContainerManifest = ` | ||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: single-pod | ||
spec: | ||
restartPolicy: Never | ||
containers: | ||
- name: nginx-container | ||
image: nginx:1.21.1 | ||
` | ||
|
||
const MultiContainerManifest = ` | ||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: multi-pod | ||
spec: | ||
restartPolicy: Never | ||
volumes: | ||
- name: shared-data | ||
emptyDir: {} | ||
containers: | ||
- name: nginx-container | ||
image: nginx:1.21.1 | ||
volumeMounts: | ||
- name: shared-data | ||
mountPath: /usr/share/nginx/html | ||
- name: ubuntu-container | ||
image: ubuntu:21.10 | ||
volumeMounts: | ||
- name: shared-data | ||
mountPath: /pod-data | ||
command: ["/bin/sh"] | ||
args: ["-c", "echo Hello, World > /pod-data/index.html"] | ||
` | ||
|
||
func TestGetImagesFromYamlManifest(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
fileContents string | ||
expected []string | ||
}{ | ||
{ | ||
name: "single image", | ||
fileContents: SingleContainerManifest, | ||
expected: []string{"nginx:1.21.1"}, | ||
}, | ||
{ | ||
name: "multi image", | ||
fileContents: MultiContainerManifest, | ||
expected: []string{"nginx:1.21.1", "ubuntu:21.10"}, | ||
}, | ||
{ | ||
name: "no images found", | ||
fileContents: ``, | ||
expected: nil, | ||
}, | ||
} | ||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
got := getImagesFromYamlManifest(tc.fileContents) | ||
if !reflect.DeepEqual(tc.expected, got) { | ||
t.Errorf("getImagesFromYamlManifest returned %v, wanted %v", got, tc.expected) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Copyright 2021 The Sigstore Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http:#www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: single-pod | ||
spec: | ||
restartPolicy: Never | ||
containers: | ||
- name: distroless | ||
image: gcr.io/distroless/base |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Copyright 2021 The Sigstore Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http:#www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: single-pod | ||
spec: | ||
restartPolicy: Never | ||
containers: | ||
- name: nginx-container | ||
image: nginx |