Skip to content

Commit d2f1c9c

Browse files
authored
chore: Revoke SA tokens after Terraform command finishes (#3794)
1 parent 7064d1b commit d2f1c9c

File tree

3 files changed

+39
-6
lines changed

3 files changed

+39
-6
lines changed

internal/config/service_account.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ var saInfo = struct {
2222
clientSecret string
2323
baseURL string
2424
mu sync.Mutex
25+
closed bool
2526
}{}
2627

2728
func 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
5758
func 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+
}

internal/testutil/acc/shared_resource.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
1011
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/clean"
1112
"github.com/stretchr/testify/require"
1213
)
@@ -58,6 +59,7 @@ func cleanupSharedResources() {
5859
fmt.Printf("Deleting execution project (%d): %s, id: %s\n", i+1, project.name, project.id)
5960
deleteProject(project.id)
6061
}
62+
config.CloseTokenSource() // Revoke SA token when acceptance tests finish.
6163
}
6264

6365
// ProjectIDExecution returns a project id created for the execution of the tests in the resource package.

main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import (
55
"log"
66

77
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
8+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
89
"github.com/mongodb/terraform-provider-mongodbatlas/internal/provider"
910
)
1011

1112
func main() {
13+
defer config.CloseTokenSource() // Revoke SA token when the plugin is exiting because Terraform command finished.
14+
1215
var debugMode bool
1316
flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve")
1417
flag.Parse()
@@ -23,6 +26,6 @@ func main() {
2326
serveOpts...,
2427
)
2528
if err != nil {
26-
log.Fatal(err)
29+
log.Println(err)
2730
}
2831
}

0 commit comments

Comments
 (0)