Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signing&verifying container images based on Kubernetes Secrets #398

Merged
merged 2 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions cmd/cosign/cli/generate_key_pair.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <namespace>/<secret name>")
)

return &ffcli.Command{
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions cmd/cosign/cli/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ package cli

import (
"context"
"crypto"
"io/ioutil"
"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"
)
Expand All @@ -37,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)
}
Expand All @@ -47,9 +59,32 @@ func signerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass
return kms.Get(ctx, keyRef)
}
}

if strings.HasPrefix(keyRef, kubernetes.KeyReference) {
s, err := kubernetes.GetKeyPairSecret(ctx, keyRef)
if err != nil {
return nil, err
}

if len(s.Data) > 0 {
return cosign.LoadECDSAPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"])
}
}

return loadKey(keyRef, pf)
}

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)
}
2 changes: 1 addition & 1 deletion cmd/cosign/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
28 changes: 24 additions & 4 deletions pkg/cosign/kubernetes/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -93,9 +113,9 @@ func secret(keys *cosign.Keys, namespace, name string, data map[string][]byte) *

// the reference should be formatted as <namespace>/<secret name>
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 <namespace>/<secret name>")
return "", "", errors.New("kubernetes specification should be in the format k8s://<namespace>/<secret>")
}
return s[0], s[1], nil
}
6 changes: 3 additions & 3 deletions pkg/cosign/kubernetes/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
Expand Down
2 changes: 1 addition & 1 deletion test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down