8
8
"os"
9
9
"time"
10
10
11
+ "github.com/go-jose/go-jose/v4"
12
+ "github.com/go-jose/go-jose/v4/jwt"
11
13
httptransport "github.com/go-openapi/runtime/client"
12
14
genapi "github.com/grafana/grafana-openapi-client-go/client"
13
15
"github.com/grafana/grafana-operator/v5/api/v1beta1"
@@ -18,12 +20,70 @@ import (
18
20
"sigs.k8s.io/controller-runtime/pkg/client"
19
21
)
20
22
23
+ const (
24
+ serviceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" // nolint:gosec
25
+ )
26
+
21
27
type grafanaAdminCredentials struct {
22
28
adminUser string
23
29
adminPassword string
24
30
apikey string
25
31
}
26
32
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
+
27
87
func getExternalAdminUser (ctx context.Context , c client.Client , cr * v1beta1.Grafana ) (string , error ) {
28
88
if cr .Spec .External != nil && cr .Spec .External .AdminUser != nil {
29
89
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.
65
125
credentials := & grafanaAdminCredentials {}
66
126
67
127
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 )
69
129
if err != nil {
70
130
return nil , err
71
131
}
72
132
73
- credentials .apikey = string ( b )
133
+ credentials .apikey = t
74
134
75
135
return credentials , nil
76
136
}
0 commit comments