Skip to content

Commit 343ee7d

Browse files
committed
Use cluster issuer as default builder id
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.
1 parent dd3620e commit 343ee7d

File tree

7 files changed

+239
-5
lines changed

7 files changed

+239
-5
lines changed

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
module github.com/tektoncd/chains
22

3-
go 1.20
3+
go 1.21
4+
5+
toolchain go1.21.2
46

57
require (
68
cloud.google.com/go/compute/metadata v0.2.3
79
cloud.google.com/go/storage v1.35.1
10+
github.com/golang-jwt/jwt/v5 v5.0.0
811
github.com/golangci/golangci-lint v1.55.2
912
github.com/google/addlicense v1.1.1
1013
github.com/google/go-cmp v0.6.0
@@ -33,6 +36,7 @@ require (
3336
gocloud.dev/docstore/mongodocstore v0.34.0
3437
gocloud.dev/pubsub/kafkapubsub v0.34.0
3538
golang.org/x/crypto v0.15.0
39+
golang.org/x/oauth2 v0.13.0
3640
google.golang.org/grpc v1.59.0
3741
google.golang.org/protobuf v1.31.0
3842
k8s.io/api v0.28.3
@@ -220,7 +224,6 @@ require (
220224
github.com/gofrs/flock v0.8.1 // indirect
221225
github.com/gogo/protobuf v1.3.2 // indirect
222226
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
223-
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
224227
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
225228
github.com/golang/protobuf v1.5.3 // indirect
226229
github.com/golang/snappy v0.0.4 // indirect
@@ -440,7 +443,6 @@ require (
440443
golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect
441444
golang.org/x/mod v0.13.0 // indirect
442445
golang.org/x/net v0.17.0 // indirect
443-
golang.org/x/oauth2 v0.13.0 // indirect
444446
golang.org/x/sync v0.5.0 // indirect
445447
golang.org/x/sys v0.14.0 // indirect
446448
golang.org/x/term v0.14.0 // indirect

go.sum

Lines changed: 75 additions & 0 deletions
Large diffs are not rendered by default.

pkg/config/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package config
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"strconv"
2223
"strings"
2324

2425
"github.com/sigstore/sigstore/pkg/tuf"
26+
"github.com/tektoncd/chains/pkg/internal/cluster"
2527
corev1 "k8s.io/api/core/v1"
2628
"k8s.io/apimachinery/pkg/util/sets"
2729
cm "knative.dev/pkg/configmap"
@@ -251,7 +253,9 @@ func defaultConfig() *Config {
251253
},
252254
},
253255
Builder: BuilderConfig{
254-
ID: "https://tekton.dev/chains/v2",
256+
// TODO: Not sure if we should be setting here, or fetch it
257+
// when it's used.
258+
ID: cluster.BuilderID(context.TODO()),
255259
},
256260
BuildDefinition: BuildDefinitionConfig{
257261
BuildType: "https://tekton.dev/chains/v2/slsa",

pkg/config/store_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ var defaultSigners = SignerConfigs{
9393
}
9494

9595
var defaultBuilder = BuilderConfig{
96-
ID: "https://tekton.dev/chains/v2",
96+
ID: "",
9797
}
9898

9999
var defaultArtifacts = ArtifactConfigs{

pkg/internal/cluster/builder_id.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package cluster
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"github.com/golang-jwt/jwt/v5"
8+
"golang.org/x/oauth2"
9+
"knative.dev/pkg/logging"
10+
)
11+
12+
var (
13+
provider = &tokenSource{
14+
path: "/var/run/secrets/kubernetes.io/serviceaccount/token",
15+
}
16+
)
17+
18+
type tokenSource struct {
19+
path string
20+
21+
token *oauth2.Token
22+
iss string
23+
}
24+
25+
func (ts *tokenSource) Token() (*oauth2.Token, error) {
26+
if ts.token.Valid() {
27+
return ts.token, nil
28+
}
29+
b, err := os.ReadFile(ts.path)
30+
if err != nil {
31+
return nil, err
32+
}
33+
ts.token = &oauth2.Token{
34+
AccessToken: string(b),
35+
}
36+
37+
return ts.token, nil
38+
}
39+
40+
func (ts *tokenSource) Issuer(ctx context.Context) string {
41+
log := logging.FromContext(ctx)
42+
43+
if ts.token.Valid() && ts.iss != "" {
44+
return ts.iss
45+
}
46+
47+
oauth, err := ts.Token()
48+
if err != nil {
49+
log.Errorf("failed to get cluster token: %v", err)
50+
return ""
51+
}
52+
53+
// We're assuming that in order to place a token in the path
54+
// you already have some amount of privilege.
55+
// While we don't know if the token is actually real, even if we
56+
// wanted to verify it against the api server we'd need to trust
57+
// the cacert bundle also included in this same path, so trusting
58+
// the token is correctly set by the Kubernetes cluster is
59+
// likely a reasonable compromise.
60+
parser := jwt.NewParser()
61+
claims := new(jwt.RegisteredClaims)
62+
if _, _, err := parser.ParseUnverified(oauth.AccessToken, claims); err != nil {
63+
log.Errorf("failed to parse cluster token: %v", err)
64+
return ""
65+
}
66+
67+
ts.iss = claims.Issuer
68+
return claims.Issuer
69+
}
70+
71+
// BuilderID returns a builder ID that the current cluster.
72+
// To approximate this, we use the cluster token issuer as defined by
73+
// the controller's default service account token.
74+
// See https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/#directly-accessing-the-rest-api for more details.
75+
// This will be change depending on where/how the cluster is running.
76+
//
77+
// Some examples:
78+
// - GKE: https://containers.googleapis.com/v1/projects/123456789012/locations/us-east1/clusters/cluster-1
79+
// - EKS: https://oidc.eks.us-east-1.amazonaws.com/id/12345678901234567890123456789012
80+
// - AKS: https://eastus.oic.prod-aks.azure.com/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/
81+
// - Kind/Local: https://kubernetes.default.svc (NOTE: this isn't a real URL and won't give you much useful information)
82+
func BuilderID(ctx context.Context) string {
83+
return provider.Issuer(ctx)
84+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cluster
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/golang-jwt/jwt/v5"
12+
logtesting "knative.dev/pkg/logging/testing"
13+
)
14+
15+
func TestBuilderID(t *testing.T) {
16+
// Create the Claims
17+
claims := &jwt.RegisteredClaims{
18+
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
19+
Issuer: "example.com",
20+
}
21+
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
22+
23+
pk, err := rsa.GenerateKey(rand.Reader, 2048)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
ss, err := token.SignedString(pk)
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
// This is a real token taken from a local kind cluster.
33+
realToken, err := os.ReadFile("testdata/token.txt")
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
for _, tc := range []struct {
39+
token []byte
40+
want string
41+
}{
42+
{
43+
token: []byte(ss),
44+
want: claims.Issuer,
45+
},
46+
{
47+
token: realToken,
48+
want: "https://kubernetes.default.svc",
49+
},
50+
} {
51+
t.Run(tc.want, func(t *testing.T) {
52+
path := filepath.Join(t.TempDir(), "token")
53+
if err := os.WriteFile(path, []byte(ss), 0600); err != nil {
54+
t.Fatal(err)
55+
}
56+
57+
provider = &tokenSource{
58+
path: path,
59+
}
60+
61+
ctx := logtesting.TestContextWithLogger(t)
62+
got := BuilderID(ctx)
63+
if got != claims.Issuer {
64+
t.Errorf("BuilderID() = %s, want %s", got, claims.Issuer)
65+
}
66+
})
67+
}
68+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzI1NiIsImtpZCI6IjR2b0s2RFJKZ1EybG04TmVMR2wyWFVUQ2xmNjFFV1E0cU9Fc2UxT0d4X1kifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzMyMzIzMTY3LCJpYXQiOjE3MDA3ODcxNjcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJoZWxsby1weDJqai1wb2QiLCJ1aWQiOiI0MDUyZDY2NS1iMThjLTQ2OTYtOGM3MS0wNGI2MjY2OTNhN2EifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJmODg3NDBiMy1jNzRmLTQyNmYtYTllNi0xMDIyMjk2OWI2NjUifSwid2FybmFmdGVyIjoxNzAwNzkwNzc0fSwibmJmIjoxNzAwNzg3MTY3LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.<omitted>

0 commit comments

Comments
 (0)