-
Notifications
You must be signed in to change notification settings - Fork 558
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add logic to detect and use ambient OIDC from exec envs. (#644)
This is based on some work I have been doing here: https://github.com/mattmoor/oidc-magic At present, it is fairly tedious to use the "keyless" flow inside of environments that have a form of ambient OIDC (e.g. GKE workload identity). For example, in the context of Tekton, one needs to overlay `cosign` on an image like `docker.io/google/cloud-sdk:slim`, and then during execution have the step do something like: ```yaml command: ["/bin/sh"] args: - "-c" - | # Generate an identity token. IDENTITY_TOKEN=$(gcloud auth print-identity-token --audiences=sigstore) # Use the identity token to sign the image. cosign sign \ -identity-token $IDENTITY_TOKEN \ my.registry/the-image@sha256:deadbeef ``` This change adds support for detecting when `cosign` is executing within an environment with this kind of ambient authentication, and automatically producing one when `-identity-token` is not specified (and `COSIGN_EXPERIMENTAL=true`). This means the same signing can now be done with: ```yaml args: ["sign", "my.registry/the-image@sha256:deadbeef"] ``` This is much simpler, but also the image will be both smaller (distroless) and more portable (not just GCP, but any provider we link). Signed-off-by: Matt Moore <[email protected]>
- Loading branch information
Showing
8 changed files
with
297 additions
and
1 deletion.
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
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,18 @@ | ||
// | ||
// 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 providers defines the APIs for providers to detect their relevance | ||
// and register themselves to furnish OIDC tokens within a given environment. | ||
package providers |
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,17 @@ | ||
// | ||
// 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 github defines a github implementation of the providers.Interface. | ||
package github |
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,76 @@ | ||
// | ||
// 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 github | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/sigstore/cosign/pkg/providers" | ||
) | ||
|
||
func init() { | ||
providers.Register("github-actions", &githubActions{}) | ||
} | ||
|
||
type githubActions struct{} | ||
|
||
var _ providers.Interface = (*githubActions)(nil) | ||
|
||
const ( | ||
RequestTokenEnvKey = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" | ||
RequestURLEnvKey = "ACTIONS_ID_TOKEN_REQUEST_URL" | ||
) | ||
|
||
// Enabled implements providers.Interface | ||
func (ga *githubActions) Enabled(ctx context.Context) bool { | ||
if os.Getenv(RequestTokenEnvKey) == "" { | ||
return false | ||
} | ||
if os.Getenv(RequestURLEnvKey) == "" { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// Provide implements providers.Interface | ||
func (ga *githubActions) Provide(ctx context.Context, audience string) (string, error) { | ||
url := os.Getenv(RequestURLEnvKey) + "&audience=" + audience | ||
|
||
req, err := http.NewRequest("GET", url, nil) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
req.Header.Add("Authorization", "bearer "+os.Getenv(RequestTokenEnvKey)) | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer resp.Body.Close() | ||
|
||
var payload struct { | ||
Value string `json:"value"` | ||
} | ||
|
||
decoder := json.NewDecoder(resp.Body) | ||
if err := decoder.Decode(&payload); err != nil { | ||
return "", err | ||
} | ||
return payload.Value, nil | ||
} |
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,17 @@ | ||
// | ||
// 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 google defines a google implementation of the providers.Interface. | ||
package google |
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,68 @@ | ||
// | ||
// 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 google | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"strings" | ||
|
||
"google.golang.org/api/idtoken" | ||
|
||
"github.com/sigstore/cosign/pkg/providers" | ||
) | ||
|
||
func init() { | ||
providers.Register("google-workload-identity", &googleWorkloadIdentity{}) | ||
} | ||
|
||
type googleWorkloadIdentity struct{} | ||
|
||
var _ providers.Interface = (*googleWorkloadIdentity)(nil) | ||
|
||
// gceProductNameFile is the product file path that contains the cloud service name. | ||
// This is a variable instead of a const to enable testing. | ||
var gceProductNameFile = "/sys/class/dmi/id/product_name" | ||
|
||
// Enabled implements providers.Interface | ||
// This is based on k8s.io/kubernetes/pkg/credentialprovider/gcp | ||
func (gwi *googleWorkloadIdentity) Enabled(ctx context.Context) bool { | ||
data, err := ioutil.ReadFile(gceProductNameFile) | ||
if err != nil { | ||
return false | ||
} | ||
name := strings.TrimSpace(string(data)) | ||
if name == "Google" || name == "Google Compute Engine" { | ||
// Just because we're on Google, does not mean workload identity is available. | ||
// TODO(mattmoor): do something better than this. | ||
_, err := gwi.Provide(ctx, "garbage") | ||
return err == nil | ||
} | ||
return false | ||
} | ||
|
||
// Provide implements providers.Interface | ||
func (gwi *googleWorkloadIdentity) Provide(ctx context.Context, audience string) (string, error) { | ||
ts, err := idtoken.NewTokenSource(ctx, audience) | ||
if err != nil { | ||
return "", err | ||
} | ||
tok, err := ts.Token() | ||
if err != nil { | ||
return "", err | ||
} | ||
return tok.AccessToken, nil | ||
} |
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,85 @@ | ||
// | ||
// 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 providers | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
var ( | ||
m sync.Mutex | ||
providers = make(map[string]Interface) | ||
) | ||
|
||
// Interface is what providers need to implement to participate in furnishing OIDC tokens. | ||
type Interface interface { | ||
// Enabled returns true if the provider is enabled. | ||
Enabled(ctx context.Context) bool | ||
|
||
// Provide returns an OIDC token scoped to the provided audience. | ||
Provide(ctx context.Context, audience string) (string, error) | ||
} | ||
|
||
// Register is used by providers to participate in furnishing OIDC tokens. | ||
func Register(name string, p Interface) { | ||
m.Lock() | ||
defer m.Unlock() | ||
|
||
if prev, ok := providers[name]; ok { | ||
panic(fmt.Sprintf("duplicate provider for name %q, %T and %T", name, prev, p)) | ||
} | ||
providers[name] = p | ||
} | ||
|
||
// Enabled checks whether any of the registered providers are enabled in this execution context. | ||
func Enabled(ctx context.Context) bool { | ||
m.Lock() | ||
defer m.Unlock() | ||
|
||
for _, provider := range providers { | ||
if provider.Enabled(ctx) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Provide fetches an OIDC token from one of the active providers. | ||
func Provide(ctx context.Context, audience string) (string, error) { | ||
m.Lock() | ||
defer m.Unlock() | ||
|
||
var id string | ||
var err error | ||
for _, provider := range providers { | ||
if !provider.Enabled(ctx) { | ||
continue | ||
} | ||
id, err = provider.Provide(ctx, audience) | ||
if err == nil { | ||
return id, err | ||
} | ||
} | ||
// return the last id/err combo, unless there wasn't an error in | ||
// which case provider.Enabled() wasn't checked. | ||
if err == nil { | ||
err = errors.New("no providers are enabled, check providers.Enabled() before providers.Provide()") | ||
} | ||
return id, err | ||
} |