@@ -22,12 +22,17 @@ var saInfo = struct {
2222 clientSecret string
2323 baseURL string
2424 mu sync.Mutex
25+ closed bool
2526}{}
2627
2728func getTokenSource (clientID , clientSecret , baseURL string , tokenRenewalBase http.RoundTripper ) (auth.TokenSource , error ) {
2829 saInfo .mu .Lock ()
2930 defer saInfo .mu .Unlock ()
3031
32+ if saInfo .closed {
33+ return nil , fmt .Errorf ("service account token source already closed" )
34+ }
35+
3136 baseURL = NormalizeBaseURL (baseURL )
3237 if saInfo .tokenSource != nil { // Token source in cache.
3338 if saInfo .clientID != clientID || saInfo .clientSecret != clientSecret || saInfo .baseURL != baseURL {
@@ -36,13 +41,9 @@ func getTokenSource(clientID, clientSecret, baseURL string, tokenRenewalBase htt
3641 return saInfo .tokenSource , nil
3742 }
3843
39- conf := clientcredentials .NewConfig (clientID , clientSecret )
40- if baseURL != "" {
41- conf .TokenURL = baseURL + clientcredentials .TokenAPIPath
42- conf .RevokeURL = baseURL + clientcredentials .RevokeAPIPath
43- }
4444 // Use a new context to avoid "context canceled" errors as the token source is reused and can outlast the callee context.
4545 ctx := context .WithValue (context .Background (), auth .HTTPClient , & http.Client {Transport : tokenRenewalBase })
46+ conf := getConfig (clientID , clientSecret , baseURL )
4647 tokenSource := oauth2 .ReuseTokenSourceWithExpiry (nil , conf .TokenSource (ctx ), saTokenExpiryBuffer )
4748 if _ , err := tokenSource .Token (); err != nil { // Retrieve token to fail-fast if credentials are invalid.
4849 return nil , err
@@ -57,3 +58,30 @@ func getTokenSource(clientID, clientSecret, baseURL string, tokenRenewalBase htt
5758func NormalizeBaseURL (baseURL string ) string {
5859 return strings .TrimRight (baseURL , "/" )
5960}
61+
62+ func getConfig (clientID , clientSecret , baseURL string ) * clientcredentials.Config {
63+ config := clientcredentials .NewConfig (clientID , clientSecret )
64+ if baseURL != "" {
65+ config .TokenURL = baseURL + clientcredentials .TokenAPIPath
66+ config .RevokeURL = baseURL + clientcredentials .RevokeAPIPath
67+ }
68+ return config
69+ }
70+
71+ // CloseTokenSource is called just before the provider finishes, it does a best-effort try to revoke the Service Access token.
72+ // It sets saInfo.closed = true to avoid future calls to getTokenSource, that should't happen as the provider is exiting.
73+ func CloseTokenSource () {
74+ saInfo .mu .Lock ()
75+ defer saInfo .mu .Unlock ()
76+ if saInfo .closed {
77+ return
78+ }
79+ saInfo .closed = true
80+ if saInfo .tokenSource == nil { // No need to do anything if SA was not initialized.
81+ return
82+ }
83+ if token , err := saInfo .tokenSource .Token (); err == nil {
84+ conf := getConfig (saInfo .clientID , saInfo .clientSecret , saInfo .baseURL )
85+ _ = conf .RevokeToken (context .Background (), token ) // Best-effort, no need to do anything if it fails.
86+ }
87+ }
0 commit comments