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