Skip to content

Commit 6976ded

Browse files
committed
feat: Cache token by decoding it and using 'exp' claim
1 parent adc482e commit 6976ded

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

controllers/client/grafana_client.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"os"
99
"time"
1010

11+
"github.com/go-jose/go-jose/v4"
12+
"github.com/go-jose/go-jose/v4/jwt"
1113
httptransport "github.com/go-openapi/runtime/client"
1214
genapi "github.com/grafana/grafana-openapi-client-go/client"
1315
"github.com/grafana/grafana-operator/v5/api/v1beta1"
@@ -18,12 +20,70 @@ import (
1820
"sigs.k8s.io/controller-runtime/pkg/client"
1921
)
2022

23+
const (
24+
serviceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" // nolint:gosec
25+
)
26+
2127
type grafanaAdminCredentials struct {
2228
adminUser string
2329
adminPassword string
2430
apikey string
2531
}
2632

33+
type JWTCache struct {
34+
Token string
35+
Expiration time.Time
36+
}
37+
38+
var jwtCache *JWTCache
39+
40+
// getBearerToken will read JWT token from given file and cache it until it expires.
41+
// accepts filepath arg for testing
42+
func getBearerToken(bearerTokenPath string) (string, error) {
43+
// Return cached token if not expired
44+
if jwtCache != nil && jwtCache.Expiration.After(time.Now()) {
45+
return jwtCache.Token, nil
46+
}
47+
48+
b, err := os.ReadFile(bearerTokenPath)
49+
if err != nil {
50+
return "", fmt.Errorf("reading token file at %s, %w", bearerTokenPath, err)
51+
}
52+
53+
token := string(b)
54+
55+
// List of accepted JWT signing algorithms from: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#:~:text=oidc-signing-algs
56+
t, err := jwt.ParseSigned(token, []jose.SignatureAlgorithm{
57+
jose.RS256, jose.RS384, jose.RS512,
58+
jose.ES256, jose.ES384, jose.ES512,
59+
jose.PS256, jose.PS384, jose.PS512,
60+
})
61+
if err != nil {
62+
return "", err
63+
}
64+
65+
claims := jwt.Claims{}
66+
67+
// TODO fetch JWKS from https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}/openid/v1/jwks
68+
// Then verify token using the keys
69+
err = t.UnsafeClaimsWithoutVerification(&claims)
70+
if err != nil {
71+
return "", fmt.Errorf("decoding ServiceAccount token %w", err)
72+
}
73+
74+
tokenExpiration := claims.Expiry.Time()
75+
if tokenExpiration.Before(time.Now()) {
76+
return "", fmt.Errorf("token expired at %s, expected %s to be rotated", tokenExpiration.String(), bearerTokenPath)
77+
}
78+
79+
jwtCache = &JWTCache{
80+
Token: token,
81+
Expiration: tokenExpiration,
82+
}
83+
84+
return token, nil
85+
}
86+
2787
func getExternalAdminUser(ctx context.Context, c client.Client, cr *v1beta1.Grafana) (string, error) {
2888
if cr.Spec.External != nil && cr.Spec.External.AdminUser != nil {
2989
adminUser, err := GetValueFromSecretKey(ctx, cr.Spec.External.AdminUser, c, cr.Namespace)
@@ -65,12 +125,12 @@ func getAdminCredentials(ctx context.Context, c client.Client, grafana *v1beta1.
65125
credentials := &grafanaAdminCredentials{}
66126

67127
if grafana.Spec.Client != nil && grafana.Spec.Client.UseKubeAuth {
68-
b, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
128+
t, err := getBearerToken(serviceAccountTokenPath)
69129
if err != nil {
70130
return nil, err
71131
}
72132

73-
credentials.apikey = string(b)
133+
credentials.apikey = t
74134

75135
return credentials, nil
76136
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ require (
4444
github.com/felixge/httpsnoop v1.0.4 // indirect
4545
github.com/fsnotify/fsnotify v1.7.0 // indirect
4646
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
47+
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
4748
github.com/go-logr/stdr v1.2.2 // indirect
4849
github.com/go-ole/go-ole v1.2.6 // indirect
4950
github.com/go-openapi/analysis v0.23.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
6060
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
6161
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
6262
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
63+
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
64+
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
6365
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
6466
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
6567
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=

0 commit comments

Comments
 (0)