Skip to content

Commit 9ad8966

Browse files
committed
feat: Cache token by decoding it and using 'exp' claim
1 parent d5ef3a1 commit 9ad8966

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
genapi "github.com/grafana/grafana-openapi-client-go/client"
1214
"github.com/grafana/grafana-operator/v5/api/v1beta1"
1315
"github.com/grafana/grafana-operator/v5/controllers/config"
@@ -17,12 +19,70 @@ import (
1719
"sigs.k8s.io/controller-runtime/pkg/client"
1820
)
1921

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

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

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

72-
credentials.apikey = string(b)
132+
credentials.apikey = t
73133

74134
return credentials, nil
75135
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ require (
4343
github.com/felixge/httpsnoop v1.0.4 // indirect
4444
github.com/fsnotify/fsnotify v1.7.0 // indirect
4545
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
46+
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
4647
github.com/go-logr/stdr v1.2.2 // indirect
4748
github.com/go-ole/go-ole v1.2.6 // indirect
4849
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
@@ -58,6 +58,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
5858
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
5959
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
6060
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
61+
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
62+
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
6163
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
6264
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
6365
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=

0 commit comments

Comments
 (0)