From ffe9b8efe581e9823f5ec40ac0e08d56c5de97ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Apayd=C4=B1n?= Date: Wed, 30 Jun 2021 23:36:44 +0300 Subject: [PATCH 1/2] sign with Kubernetes Secret MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Batuhan Apaydın --- cmd/cosign/cli/keys.go | 22 ++++++++++++++++++++++ cmd/cosign/cli/sign.go | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cmd/cosign/cli/keys.go b/cmd/cosign/cli/keys.go index cff119eb4b4..3efcac32fa5 100644 --- a/cmd/cosign/cli/keys.go +++ b/cmd/cosign/cli/keys.go @@ -20,9 +20,13 @@ import ( "path/filepath" "strings" + "github.com/pkg/errors" "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/cosign/kubernetes" "github.com/sigstore/sigstore/pkg/kms" "github.com/sigstore/sigstore/pkg/signature" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func loadKey(keyPath string, pf cosign.PassFunc) (signature.ECDSASignerVerifier, error) { @@ -47,6 +51,24 @@ func signerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass return kms.Get(ctx, keyRef) } } + + s := strings.Split(keyRef, "/") // / + if len(s) == 2 { + namespace, name := s[0], s[1] + // create the k8s client + client, err := kubernetes.Client() + if err != nil { + return nil, errors.Wrap(err, "new for config") + } + + var s *v1.Secret + if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil { + return nil, errors.Wrap(err, "checking if secret exists") + } else { + return cosign.LoadECDSAPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) + } + } + return loadKey(keyRef, pf) } diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go index eef57cb6b83..40af57ab346 100644 --- a/cmd/cosign/cli/sign.go +++ b/cmd/cosign/cli/sign.go @@ -73,7 +73,7 @@ func (a *annotationsMap) String() string { func Sign() *ffcli.Command { var ( flagset = flag.NewFlagSet("cosign sign", flag.ExitOnError) - key = flagset.String("key", "", "path to the private key file or KMS URI") + key = flagset.String("key", "", "path to the private key file, KMS URI or Kubernetes Secret") upload = flagset.Bool("upload", true, "whether to upload the signature") sk = flagset.Bool("sk", false, "whether to use a hardware security key") slot = flagset.String("slot", "", "security key slot to use for generated key (authentication|signature|card-authentication|key-management)") From c05a18ab7aa4b686553a84602846d09a52c5560a Mon Sep 17 00:00:00 2001 From: Furkan Date: Thu, 1 Jul 2021 00:18:38 +0300 Subject: [PATCH 2/2] add kubernetes verifying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Furkan Signed-off-by: Batuhan Apaydın --- cmd/cosign/cli/generate_key_pair.go | 11 ++++----- cmd/cosign/cli/keys.go | 37 +++++++++++++++++++--------- cmd/cosign/cli/verify.go | 2 +- pkg/cosign/kubernetes/secret.go | 28 ++++++++++++++++++--- pkg/cosign/kubernetes/secret_test.go | 6 ++--- test/e2e_test.go | 2 +- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/cmd/cosign/cli/generate_key_pair.go b/cmd/cosign/cli/generate_key_pair.go index e4f532b419c..ec1f49db5c7 100644 --- a/cmd/cosign/cli/generate_key_pair.go +++ b/cmd/cosign/cli/generate_key_pair.go @@ -40,7 +40,6 @@ func GenerateKeyPair() *ffcli.Command { var ( flagset = flag.NewFlagSet("cosign generate-key-pair", flag.ExitOnError) kmsVal = flagset.String("kms", "", "create key pair in KMS service to use for signing") - k8sRef = flagset.String("k8s", "", "create key pair and store in Kubernetes secret, format as /") ) return &ffcli.Command{ @@ -60,19 +59,19 @@ EXAMPLES: cosign generate-key-pair -kms hashivault://[KEY] # generate a key-pair in Kubernetes Secret - cosign generate-key-pair -k8s [NAMESPACE]/[SECRET_NAME] + cosign generate-key-pair k8s://[NAMESPACE]/[NAME] CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one.`, FlagSet: flagset, Exec: func(ctx context.Context, args []string) error { - return GenerateKeyPairCmd(ctx, *kmsVal, *k8sRef) + return GenerateKeyPairCmd(ctx, *kmsVal, args) }, } } -func GenerateKeyPairCmd(ctx context.Context, kmsVal, k8sRef string) error { +func GenerateKeyPairCmd(ctx context.Context, kmsVal string, args []string) error { if kmsVal != "" { k, err := kms.Get(ctx, kmsVal) if err != nil { @@ -92,8 +91,8 @@ func GenerateKeyPairCmd(ctx context.Context, kmsVal, k8sRef string) error { fmt.Fprintln(os.Stderr, "Public key written to cosign.pub") return nil } - if k8sRef != "" { - return kubernetes.KeyPairSecret(ctx, k8sRef, GetPass) + if len(args) > 0 { + return kubernetes.KeyPairSecret(ctx, args[0], GetPass) } keys, err := cosign.GenerateKeyPair(GetPass) diff --git a/cmd/cosign/cli/keys.go b/cmd/cosign/cli/keys.go index 3efcac32fa5..c92e9101713 100644 --- a/cmd/cosign/cli/keys.go +++ b/cmd/cosign/cli/keys.go @@ -16,6 +16,7 @@ package cli import ( "context" + "crypto" "io/ioutil" "path/filepath" "strings" @@ -25,8 +26,6 @@ import ( "github.com/sigstore/cosign/pkg/cosign/kubernetes" "github.com/sigstore/sigstore/pkg/kms" "github.com/sigstore/sigstore/pkg/signature" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func loadKey(keyPath string, pf cosign.PassFunc) (signature.ECDSASignerVerifier, error) { @@ -41,6 +40,15 @@ func loadKey(keyPath string, pf cosign.PassFunc) (signature.ECDSASignerVerifier, return cosign.LoadECDSAPrivateKey(kb, pass) } +func loadPublicKey(raw []byte) (cosign.PublicKey, error) { + // PEM encoded file. + ed, err := cosign.PemToECDSAKey(raw) + if err != nil { + return nil, errors.Wrap(err, "pem to ecdsa") + } + return signature.ECDSAVerifier{Key: ed, HashAlg: crypto.SHA256}, nil +} + func signerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { return signerVerifierFromKeyRef(ctx, keyRef, pf) } @@ -52,19 +60,13 @@ func signerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } } - s := strings.Split(keyRef, "/") // / - if len(s) == 2 { - namespace, name := s[0], s[1] - // create the k8s client - client, err := kubernetes.Client() + if strings.HasPrefix(keyRef, kubernetes.KeyReference) { + s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { - return nil, errors.Wrap(err, "new for config") + return nil, err } - var s *v1.Secret - if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil { - return nil, errors.Wrap(err, "checking if secret exists") - } else { + if len(s.Data) > 0 { return cosign.LoadECDSAPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) } } @@ -73,5 +75,16 @@ func signerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } func publicKeyFromKeyRef(ctx context.Context, keyRef string) (cosign.PublicKey, error) { + if strings.HasPrefix(keyRef, kubernetes.KeyReference) { + s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) + if err != nil { + return nil, err + } + + if len(s.Data) > 0 { + return loadPublicKey(s.Data["cosign.pub"]) + } + } + return cosign.LoadPublicKey(ctx, keyRef) } diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 417841635b8..53c19a26850 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -46,7 +46,7 @@ type VerifyCommand struct { func applyVerifyFlags(cmd *VerifyCommand, flagset *flag.FlagSet) { annotations := annotationsMap{} - flagset.StringVar(&cmd.KeyRef, "key", "", "path to the public key file, URL, or KMS URI") + flagset.StringVar(&cmd.KeyRef, "key", "", "path to the public key file, URL, KMS URI or Kubernetes Secret") flagset.BoolVar(&cmd.Sk, "sk", false, "whether to use a hardware security key") flagset.StringVar(&cmd.Slot, "slot", "", "security key slot to use for generated key (authentication|signature|card-authentication|key-management)") flagset.BoolVar(&cmd.CheckClaims, "check-claims", true, "whether to check the claims found") diff --git a/pkg/cosign/kubernetes/secret.go b/pkg/cosign/kubernetes/secret.go index 6d9e9fec41a..e1adde041b6 100644 --- a/pkg/cosign/kubernetes/secret.go +++ b/pkg/cosign/kubernetes/secret.go @@ -22,13 +22,33 @@ import ( "strings" "github.com/pkg/errors" + "github.com/sigstore/cosign/pkg/cosign" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/sigstore/cosign/pkg/cosign" ) +const KeyReference = "k8s://" + +func GetKeyPairSecret(ctx context.Context, k8sRef string) (*v1.Secret, error) { + namespace, name, err := parseRef(k8sRef) + if err != nil { + return nil, err + } + + client, err := Client() + if err != nil { + return nil, errors.Wrap(err, "new for config") + } + + var s *v1.Secret + if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil { + return nil, errors.Wrap(err, "checking if secret exists") + } + + return s, nil +} + func KeyPairSecret(ctx context.Context, k8sRef string, pf cosign.PassFunc) error { namespace, name, err := parseRef(k8sRef) if err != nil { @@ -93,9 +113,9 @@ func secret(keys *cosign.Keys, namespace, name string, data map[string][]byte) * // the reference should be formatted as / func parseRef(k8sRef string) (string, string, error) { - s := strings.Split(k8sRef, "/") + s := strings.Split(strings.TrimPrefix(k8sRef, KeyReference), "/") if len(s) != 2 { - return "", "", errors.New("please format the k8s secret reference as /") + return "", "", errors.New("kubernetes specification should be in the format k8s:///") } return s[0], s[1], nil } diff --git a/pkg/cosign/kubernetes/secret_test.go b/pkg/cosign/kubernetes/secret_test.go index 616f0cd164c..5cbbe506c2c 100644 --- a/pkg/cosign/kubernetes/secret_test.go +++ b/pkg/cosign/kubernetes/secret_test.go @@ -88,16 +88,16 @@ func TestParseRef(t *testing.T) { }{ { desc: "valid", - ref: "default/cosign-secret", + ref: "k8s://default/cosign-secret", name: "cosign-secret", namespace: "default", }, { desc: "invalid, 1 field", - ref: "something", + ref: "k8s://something", shouldErr: true, }, { desc: "invalid, more than 2 fields", - ref: "yet/another/arg", + ref: "k8s://yet/another/arg", shouldErr: true, }, } diff --git a/test/e2e_test.go b/test/e2e_test.go index a42a5cb101f..7bea69ee3f9 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -252,7 +252,7 @@ func TestGenerateKeyPairK8s(t *testing.T) { ctx := context.Background() name := "cosign-secret" namespace := "default" - if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("%s/%s", namespace, name), cli.GetPass); err != nil { + if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), cli.GetPass); err != nil { t.Fatal(err) } // make sure the secret actually exists