Skip to content

Commit

Permalink
Use cluster issuer as default builder id
Browse files Browse the repository at this point in the history
This is probably the most "correct" value for this field - it should
uniquely identify the cluster, and will match other signature data
included in Fulcio certs, etc.

This is technically a breaking change, but likely one worth making.
Users can still override this behavior with the config map as before.
If omitted, this field is not populated as an indication that we don't
know how to accurately identify this cluster.
  • Loading branch information
wlynch committed Nov 24, 2023
1 parent dd3620e commit 343ee7d
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 5 deletions.
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
module github.com/tektoncd/chains

go 1.20
go 1.21

toolchain go1.21.2

require (
cloud.google.com/go/compute/metadata v0.2.3
cloud.google.com/go/storage v1.35.1
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golangci/golangci-lint v1.55.2
github.com/google/addlicense v1.1.1
github.com/google/go-cmp v0.6.0
Expand Down Expand Up @@ -33,6 +36,7 @@ require (
gocloud.dev/docstore/mongodocstore v0.34.0
gocloud.dev/pubsub/kafkapubsub v0.34.0
golang.org/x/crypto v0.15.0
golang.org/x/oauth2 v0.13.0
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
k8s.io/api v0.28.3
Expand Down Expand Up @@ -220,7 +224,6 @@ require (
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand Down Expand Up @@ -440,7 +443,6 @@ require (
golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.14.0 // indirect
Expand Down
75 changes: 75 additions & 0 deletions go.sum

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ limitations under the License.
package config

import (
"context"
"fmt"
"strconv"
"strings"

"github.com/sigstore/sigstore/pkg/tuf"
"github.com/tektoncd/chains/pkg/internal/cluster"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
cm "knative.dev/pkg/configmap"
Expand Down Expand Up @@ -251,7 +253,9 @@ func defaultConfig() *Config {
},
},
Builder: BuilderConfig{
ID: "https://tekton.dev/chains/v2",
// TODO: Not sure if we should be setting here, or fetch it
// when it's used.
ID: cluster.BuilderID(context.TODO()),
},
BuildDefinition: BuildDefinitionConfig{
BuildType: "https://tekton.dev/chains/v2/slsa",
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ var defaultSigners = SignerConfigs{
}

var defaultBuilder = BuilderConfig{
ID: "https://tekton.dev/chains/v2",
ID: "",
}

var defaultArtifacts = ArtifactConfigs{
Expand Down
84 changes: 84 additions & 0 deletions pkg/internal/cluster/builder_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package cluster

import (
"context"
"os"

"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
"knative.dev/pkg/logging"
)

var (
provider = &tokenSource{
path: "/var/run/secrets/kubernetes.io/serviceaccount/token",
}
)

type tokenSource struct {
path string

token *oauth2.Token
iss string
}

func (ts *tokenSource) Token() (*oauth2.Token, error) {
if ts.token.Valid() {
return ts.token, nil
}
b, err := os.ReadFile(ts.path)
if err != nil {
return nil, err
}
ts.token = &oauth2.Token{
AccessToken: string(b),
}

return ts.token, nil
}

func (ts *tokenSource) Issuer(ctx context.Context) string {
log := logging.FromContext(ctx)

if ts.token.Valid() && ts.iss != "" {
return ts.iss
}

oauth, err := ts.Token()
if err != nil {
log.Errorf("failed to get cluster token: %v", err)
return ""
}

// We're assuming that in order to place a token in the path
// you already have some amount of privilege.
// While we don't know if the token is actually real, even if we
// wanted to verify it against the api server we'd need to trust
// the cacert bundle also included in this same path, so trusting
// the token is correctly set by the Kubernetes cluster is
// likely a reasonable compromise.
parser := jwt.NewParser()
claims := new(jwt.RegisteredClaims)
if _, _, err := parser.ParseUnverified(oauth.AccessToken, claims); err != nil {
log.Errorf("failed to parse cluster token: %v", err)
return ""
}

ts.iss = claims.Issuer
return claims.Issuer
}

// BuilderID returns a builder ID that the current cluster.
// To approximate this, we use the cluster token issuer as defined by
// the controller's default service account token.
// See https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/#directly-accessing-the-rest-api for more details.
// This will be change depending on where/how the cluster is running.
//
// Some examples:
// - GKE: https://containers.googleapis.com/v1/projects/123456789012/locations/us-east1/clusters/cluster-1
// - EKS: https://oidc.eks.us-east-1.amazonaws.com/id/12345678901234567890123456789012
// - AKS: https://eastus.oic.prod-aks.azure.com/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/
// - Kind/Local: https://kubernetes.default.svc (NOTE: this isn't a real URL and won't give you much useful information)
func BuilderID(ctx context.Context) string {
return provider.Issuer(ctx)
}
68 changes: 68 additions & 0 deletions pkg/internal/cluster/builder_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cluster

import (
"crypto/rand"
"crypto/rsa"
"os"
"path/filepath"
"testing"
"time"

"github.com/golang-jwt/jwt/v5"
logtesting "knative.dev/pkg/logging/testing"
)

func TestBuilderID(t *testing.T) {
// Create the Claims
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
Issuer: "example.com",
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

pk, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
ss, err := token.SignedString(pk)
if err != nil {
t.Fatal(err)
}

// This is a real token taken from a local kind cluster.
realToken, err := os.ReadFile("testdata/token.txt")
if err != nil {
t.Fatal(err)
}

for _, tc := range []struct {
token []byte
want string
}{
{
token: []byte(ss),
want: claims.Issuer,
},
{
token: realToken,
want: "https://kubernetes.default.svc",
},
} {
t.Run(tc.want, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "token")
if err := os.WriteFile(path, []byte(ss), 0600); err != nil {
t.Fatal(err)
}

provider = &tokenSource{
path: path,
}

ctx := logtesting.TestContextWithLogger(t)
got := BuilderID(ctx)
if got != claims.Issuer {
t.Errorf("BuilderID() = %s, want %s", got, claims.Issuer)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/internal/cluster/testdata/token.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsImtpZCI6IjR2b0s2RFJKZ1EybG04TmVMR2wyWFVUQ2xmNjFFV1E0cU9Fc2UxT0d4X1kifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzMyMzIzMTY3LCJpYXQiOjE3MDA3ODcxNjcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJoZWxsby1weDJqai1wb2QiLCJ1aWQiOiI0MDUyZDY2NS1iMThjLTQ2OTYtOGM3MS0wNGI2MjY2OTNhN2EifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJmODg3NDBiMy1jNzRmLTQyNmYtYTllNi0xMDIyMjk2OWI2NjUifSwid2FybmFmdGVyIjoxNzAwNzkwNzc0fSwibmJmIjoxNzAwNzg3MTY3LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.<omitted>

0 comments on commit 343ee7d

Please sign in to comment.