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,71 @@ 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 algorithms mirrore of: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#:~:text=RS256
55
+ // RS256 should be the default, but no documentation came up regarding configuration of how service accounts are generated
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
+
26
87
func getExternalAdminUser (ctx context.Context , c client.Client , cr * v1beta1.Grafana ) (string , error ) {
27
88
if cr .Spec .External != nil && cr .Spec .External .AdminUser != nil {
28
89
adminUser , err := GetValueFromSecretKey (ctx , cr .Spec .External .AdminUser , c , cr .Namespace )
@@ -64,12 +125,12 @@ func getAdminCredentials(ctx context.Context, c client.Client, grafana *v1beta1.
64
125
credentials := & grafanaAdminCredentials {}
65
126
66
127
if grafana .Spec .Client != nil && grafana .Spec .Client .UseKubeAuth {
67
- b , err := os . ReadFile ( "/var/run/secrets/kubernetes.io/serviceaccount/token" )
128
+ t , err := getBearerToken ( serviceAccountTokenPath )
68
129
if err != nil {
69
130
return nil , err
70
131
}
71
132
72
- credentials .apikey = string ( b )
133
+ credentials .apikey = t
73
134
74
135
return credentials , nil
75
136
}
0 commit comments