From 3f83940d3f3d97075d606af1e0793051cc6fc19b Mon Sep 17 00:00:00 2001 From: dlorenc Date: Sun, 12 Sep 2021 13:38:20 -0500 Subject: [PATCH] Support GCP environments without workload identity (GCB). (#652) This requires the use of an environment variable called "GOOGLE_SERVICE_ACCOUNT_NAME" to direct the client on which account to impersonate. Signed-off-by: Dan Lorenc Signed-off-by: Dan Lorenc --- KEYLESS.md | 10 ++++++++++ go.mod | 3 +-- go.sum | 2 -- pkg/providers/google/google.go | 31 +++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/KEYLESS.md b/KEYLESS.md index 0d4ad2d0750..e6482d97004 100644 --- a/KEYLESS.md +++ b/KEYLESS.md @@ -93,6 +93,16 @@ $ cosign sign --identity-token=$( In order to impersonate an IAM service account, your account must have the `roles/iam.serviceAccountTokenCreator` role. +**Note**: On Google Cloud Build, standard identity tokens are not supported through the GCE metadata server. +`cosign` has a special flow for this case, where you can instruct the Cloud Build service account to impersonate +another service account. +To configure this flow: + +1. Create a service account to use for signatures (the email address will be present in the certificate subject). +2. Grant the Cloud Build service account the `roles/iam.serviceAccountTokenCreator` role for this target account. +3. Set the `GOOGLE_SERVICE_ACCOUNT_NAME` environment variable to the name of the target account in your cloudbuid.yaml +4. Sign images in GCB, without keys! + ### Timestamps Signature timestamps are checked in the [rekor](https://github.com/sigstore/rekor) transparency log. Rekor's `IntegratedTime` is signed as part of its `signedEntryTimestamp`. Cosign verifies the signature over the timestamp and checks that the signature was created while the certificate was valid. diff --git a/go.mod b/go.mod index 566030f5502..fad82a7f6d4 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/sigstore/fulcio v0.1.2-0.20210831152525-42f7422734bb github.com/sigstore/rekor v0.3.0 github.com/sigstore/sigstore v0.0.0-20210729211320-56a91f560f44 - github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 github.com/theupdateframework/go-tuf v0.0.0-20210722233521-90e262754396 golang.org/x/mod v0.5.0 // indirect @@ -36,7 +35,7 @@ require ( k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 - k8s.io/klog/v2 v2.10.0 + k8s.io/klog/v2 v2.10.0 // indirect ) require ( diff --git a/go.sum b/go.sum index f141a90f22c..d51dca65b21 100644 --- a/go.sum +++ b/go.sum @@ -2505,8 +2505,6 @@ k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= knative.dev/hack v0.0.0-20210806075220-815cd312d65c/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= -knative.dev/pkg v0.0.0-20210907232433-26db1ba732f6 h1:nLHaEQP+SNW4E6zsgSS6wwpHNpSpag8vw8SSsH4ILw8= -knative.dev/pkg v0.0.0-20210907232433-26db1ba732f6/go.mod h1:jMSqkNMsrzuy+XR4Yr/BMy7SDVbUOl3KKB6+5MR+ZU8= knative.dev/pkg v0.0.0-20210908025933-71508fc69a57 h1:du+do3to2zboxuk0Uj8Xvf2onOxthyaBSr98+hVB7z4= knative.dev/pkg v0.0.0-20210908025933-71508fc69a57/go.mod h1:jMSqkNMsrzuy+XR4Yr/BMy7SDVbUOl3KKB6+5MR+ZU8= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/pkg/providers/google/google.go b/pkg/providers/google/google.go index 1e3edb0c056..6daa2fcdd8e 100644 --- a/pkg/providers/google/google.go +++ b/pkg/providers/google/google.go @@ -18,15 +18,18 @@ package google import ( "context" "io/ioutil" + "os" "strings" "google.golang.org/api/idtoken" + "google.golang.org/api/impersonate" "github.com/sigstore/cosign/pkg/providers" ) func init() { providers.Register("google-workload-identity", &googleWorkloadIdentity{}) + providers.Register("google-impersonate", &googleImpersonate{}) } type googleWorkloadIdentity struct{} @@ -66,3 +69,31 @@ func (gwi *googleWorkloadIdentity) Provide(ctx context.Context, audience string) } return tok.AccessToken, nil } + +type googleImpersonate struct{} + +var _ providers.Interface = (*googleImpersonate)(nil) + +// Enabled implements providers.Interface +func (gi *googleImpersonate) Enabled(ctx context.Context) bool { + // The "impersonate" method requires a target service account to impersonate. + return os.Getenv("GOOGLE_SERVICE_ACCOUNT_NAME") != "" +} + +// Provide implements providers.Interface +func (gi *googleImpersonate) Provide(ctx context.Context, audience string) (string, error) { + target := os.Getenv("GOOGLE_SERVICE_ACCOUNT_NAME") + ts, err := impersonate.IDTokenSource(ctx, impersonate.IDTokenConfig{ + Audience: "sigstore", + TargetPrincipal: target, + IncludeEmail: true, + }) + if err != nil { + return "", err + } + tok, err := ts.Token() + if err != nil { + return "", err + } + return tok.AccessToken, nil +}