@@ -4,11 +4,15 @@ import (
4
4
"context"
5
5
"crypto/tls"
6
6
"crypto/x509"
7
+ "encoding/base64"
8
+ "encoding/json"
9
+ "errors"
7
10
"fmt"
8
11
"net/http"
9
12
"net/url"
10
13
"regexp"
11
14
"strconv"
15
+ "strings"
12
16
"time"
13
17
14
18
"github.com/PuerkitoBio/rehttp"
@@ -17,11 +21,17 @@ import (
17
21
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
18
22
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
19
23
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"
24
+ "github.com/zalando/go-keyring"
20
25
21
26
"github.com/auth0/terraform-provider-auth0/internal/mutex"
22
27
)
23
28
24
- const providerName = "Terraform-Provider-Auth0" // #nosec G101
29
+ const providerName = "Terraform-Provider-Auth0" // #nosec G101
30
+ const secretAccessToken = "Auth0 CLI Access Token" // #nosec G101
31
+
32
+ // Access tokens have no size limit, but should be smaller than (50*2048) bytes.
33
+ // The max number of loops safeguards against infinite loops, however unlikely.
34
+ const secretAccessTokenMaxChunks = 50
25
35
26
36
var version = "dev"
27
37
@@ -55,23 +65,92 @@ func (c *Config) GetMutex() *mutex.KeyValue {
55
65
// and passed into the subsequent resources as the meta parameter.
56
66
func ConfigureProvider (terraformVersion * string ) schema.ConfigureContextFunc {
57
67
return func (_ context.Context , data * schema.ResourceData ) (interface {}, diag.Diagnostics ) {
68
+ var apiToken string
58
69
domain := data .Get ("domain" ).(string )
59
70
clientID := data .Get ("client_id" ).(string )
60
71
clientSecret := data .Get ("client_secret" ).(string )
61
- apiToken : = data .Get ("api_token" ).(string )
72
+ apiToken = data .Get ("api_token" ).(string )
62
73
audience := data .Get ("audience" ).(string )
63
74
debug := data .Get ("debug" ).(bool )
64
75
dynamicCredentials := data .Get ("dynamic_credentials" ).(bool )
76
+ cliLogin := data .Get ("cli_login" ).(bool )
77
+
78
+ switch {
79
+ case dynamicCredentials :
80
+ if domain == "" {
81
+ return nil , diag.Diagnostics {{
82
+ Severity : diag .Error ,
83
+ Summary : "Missing required configuration" ,
84
+ Detail : "The 'AUTH0_DOMAIN' must be specified along with the 'AUTH0_DYNAMIC_CREDENTIALS'." ,
85
+ }}
86
+ }
87
+ case cliLogin :
88
+ // Ensure domain is present.
89
+ if domain == "" {
90
+ return nil , diag.Diagnostics {{
91
+ Severity : diag .Error ,
92
+ Summary : "Missing required configuration" ,
93
+ Detail : "The 'AUTH0_DOMAIN' must be specified along with the 'AUTH0_CLI_LOGIN'." ,
94
+ }}
95
+ }
96
+
97
+ // Check for tempToken when cliLogin is enabled.
98
+ var tempToken string
99
+ for i := 0 ; i < secretAccessTokenMaxChunks ; i ++ {
100
+ a , err := keyring .Get (fmt .Sprintf ("%s %d" , secretAccessToken , i ), domain )
101
+ if errors .Is (err , keyring .ErrNotFound ) || err != nil {
102
+ break
103
+ }
104
+ tempToken += a
105
+ }
106
+
107
+ if tempToken == "" {
108
+ return nil , diag.Diagnostics {{
109
+ Severity : diag .Error ,
110
+ Summary : "Authentication required" ,
111
+ Detail : "No CLI token found. Please log in using 'auth0 login' via auth0-cli or disable 'AUTH0_CLI_LOGIN'." ,
112
+ }}
113
+ }
65
114
66
- if ! dynamicCredentials && apiToken == "" && ( clientID == "" || clientSecret == "" || domain == "" ) {
67
- return nil , diag. Diagnostics {
68
- {
115
+ // Check if the token is expired.
116
+ if err := validateTokenExpiry ( tempToken ); err != nil {
117
+ return nil , diag. Diagnostics { {
69
118
Severity : diag .Error ,
70
- Summary : "Missing environment variables" ,
71
- Detail : fmt .Sprintf ("Either AUTH0_API_TOKEN or AUTH0_DOMAIN:AUTH0_CLIENT_ID:AUTH0_CLIENT_SECRET must be configured. " +
72
- "Ref: https://registry.terraform.io/providers/auth0/auth0/latest/docs" ),
73
- },
119
+ Summary : "Token validation failed" ,
120
+ Detail : err .Error (),
121
+ }}
74
122
}
123
+
124
+ // Set the apiToken to the tempToken.
125
+ apiToken = tempToken
126
+
127
+ case apiToken != "" :
128
+ // Ensure domain is present.
129
+ if domain == "" {
130
+ return nil , diag.Diagnostics {{
131
+ Severity : diag .Error ,
132
+ Summary : "Missing required configuration" ,
133
+ Detail : "The 'AUTH0_DOMAIN' must be specified along with the 'AUTH0_API_TOKEN'." ,
134
+ }}
135
+ }
136
+
137
+ case clientID != "" && clientSecret != "" :
138
+ // Ensure domain is present.
139
+ if domain == "" {
140
+ return nil , diag.Diagnostics {{
141
+ Severity : diag .Error ,
142
+ Summary : "Missing required configuration" ,
143
+ Detail : "The 'AUTH0_DOMAIN' must be specified." ,
144
+ }}
145
+ }
146
+
147
+ default :
148
+ return nil , diag.Diagnostics {{
149
+ Severity : diag .Error ,
150
+ Summary : "Missing environment variables" ,
151
+ Detail : "AUTH0_DOMAIN is required. Then, configure either AUTH0_API_TOKEN, " +
152
+ "or both AUTH0_CLIENT_ID and AUTH0_CLIENT_SECRET. Or enable CLI login with AUTH0_CLI_LOGIN=true" ,
153
+ }}
75
154
}
76
155
77
156
apiClient , err := management .New (domain ,
@@ -82,6 +161,7 @@ func ConfigureProvider(terraformVersion *string) schema.ConfigureContextFunc {
82
161
management .WithNoRetries (),
83
162
management .WithClient (customClientWithRetries ()),
84
163
)
164
+
85
165
if err != nil {
86
166
return nil , diag .FromErr (err )
87
167
}
@@ -90,6 +170,57 @@ func ConfigureProvider(terraformVersion *string) schema.ConfigureContextFunc {
90
170
}
91
171
}
92
172
173
+ func decodeSegment (seg string ) ([]byte , error ) {
174
+ // Add padding if necessary.
175
+ if l := len (seg ) % 4 ; l > 0 {
176
+ seg += strings .Repeat ("=" , 4 - l )
177
+ }
178
+ return base64 .URLEncoding .DecodeString (seg )
179
+ }
180
+
181
+ func decodeJWT (token string ) (map [string ]interface {}, map [string ]interface {}, error ) {
182
+ parts := strings .Split (token , "." )
183
+ if len (parts ) != 3 {
184
+ return nil , nil , fmt .Errorf ("invalid JWT format" )
185
+ }
186
+
187
+ headerBytes , err := decodeSegment (parts [0 ])
188
+ if err != nil {
189
+ return nil , nil , fmt .Errorf ("error decoding header: %w" , err )
190
+ }
191
+ payloadBytes , err := decodeSegment (parts [1 ])
192
+ if err != nil {
193
+ return nil , nil , fmt .Errorf ("error decoding payload: %w" , err )
194
+ }
195
+
196
+ var header , payload map [string ]interface {}
197
+ if err := json .Unmarshal (headerBytes , & header ); err != nil {
198
+ return nil , nil , fmt .Errorf ("error unmarshalling header: %w" , err )
199
+ }
200
+ if err := json .Unmarshal (payloadBytes , & payload ); err != nil {
201
+ return nil , nil , fmt .Errorf ("error unmarshalling payload: %w" , err )
202
+ }
203
+
204
+ return header , payload , nil
205
+ }
206
+
207
+ func validateTokenExpiry (tokenString string ) error {
208
+ _ , payload , err := decodeJWT (tokenString )
209
+ if err != nil {
210
+ return err
211
+ }
212
+
213
+ if exp , ok := payload ["exp" ].(float64 ); ok {
214
+ if time .Now ().Unix () > int64 (exp ) {
215
+ return fmt .Errorf ("expired token: the stored auth0-cli token has expired. Please log in again" )
216
+ }
217
+ } else {
218
+ return fmt .Errorf ("missing expiration: the token does not contain an expiration claim" )
219
+ }
220
+
221
+ return nil
222
+ }
223
+
93
224
// userAgent computes the desired User-Agent header for the *management.Management client.
94
225
func userAgent (terraformVersion * string ) string {
95
226
sdkVersion := auth0 .Version
0 commit comments