48
48
defaultCPAPI = "api.cp.chainloop.dev:443"
49
49
defaultCASAPI = "api.cas.chainloop.dev:443"
50
50
apiToken string
51
+ flagYes bool
51
52
)
52
53
53
54
const (
55
+ // preference to use an API token if available
54
56
useAPIToken = "withAPITokenAuth"
55
57
appName = "chainloop"
56
58
//nolint:gosec
@@ -60,6 +62,8 @@ const (
60
62
apiTokenAudience = "api-token-auth.chainloop"
61
63
// Follow the convention stated on https://consoledonottrack.com/
62
64
doNotTrackEnv = "DO_NOT_TRACK"
65
+
66
+ trueString = "true"
63
67
)
64
68
65
69
var telemetryWg sync.WaitGroup
@@ -108,7 +112,7 @@ func NewRootCmd(l zerolog.Logger) *cobra.Command {
108
112
logger .Warn ().Msg ("API contacted in insecure mode" )
109
113
}
110
114
111
- apiToken , err := loadControlplaneAuthToken (cmd )
115
+ token , isUserToken , err := loadControlplaneAuthToken (cmd )
112
116
if err != nil {
113
117
return err
114
118
}
@@ -124,8 +128,9 @@ func NewRootCmd(l zerolog.Logger) *cobra.Command {
124
128
controlplaneURL := viper .GetString (confOptions .controlplaneAPI .viperKey )
125
129
126
130
// If no organization is set in local configuration, we load it from server and save it
127
- if viper .GetString (confOptions .organization .viperKey ) == "" {
128
- conn , err := grpcconn .New (controlplaneURL , apiToken , opts ... )
131
+ orgName := viper .GetString (confOptions .organization .viperKey )
132
+ if orgName == "" {
133
+ conn , err := grpcconn .New (controlplaneURL , token , opts ... )
129
134
if err != nil {
130
135
return err
131
136
}
@@ -139,11 +144,19 @@ func NewRootCmd(l zerolog.Logger) *cobra.Command {
139
144
}
140
145
141
146
// reload the connection now that we have the org name
142
- if orgName := viper .GetString (confOptions .organization .viperKey ); orgName != "" {
147
+ orgName = viper .GetString (confOptions .organization .viperKey )
148
+ if orgName != "" {
143
149
opts = append (opts , grpcconn .WithOrgName (orgName ))
144
150
}
145
151
146
- conn , err := grpcconn .New (controlplaneURL , apiToken , opts ... )
152
+ // Warn users when the session is interactive, and the operation is supposed to use an API token instead
153
+ if isAPITokenPreferred (cmd ) && isUserToken && ! flagYes {
154
+ if ! confirmationPrompt (fmt .Sprintf ("This command is will run against the organization %q" , orgName )) {
155
+ return errors .New ("command canceled by user" )
156
+ }
157
+ }
158
+
159
+ conn , err := grpcconn .New (controlplaneURL , token , opts ... )
147
160
if err != nil {
148
161
return err
149
162
}
@@ -165,7 +178,7 @@ func NewRootCmd(l zerolog.Logger) *cobra.Command {
165
178
go func () {
166
179
// For telemetry reasons we parse the token to know the type of token is being used when executing the CLI
167
180
// Once we have the token type we can send it to the telemetry service by injecting it on the context
168
- token , err := parseToken (apiToken )
181
+ token , err := parseToken (token )
169
182
if err != nil {
170
183
logger .Debug ().Err (err ).Msg ("parsing token for telemetry" )
171
184
return
@@ -229,6 +242,9 @@ func NewRootCmd(l zerolog.Logger) *cobra.Command {
229
242
rootCmd .PersistentFlags ().StringP (confOptions .organization .flagName , "n" , "" , "organization name" )
230
243
cobra .CheckErr (viper .BindPFlag (confOptions .organization .viperKey , rootCmd .PersistentFlags ().Lookup (confOptions .organization .flagName )))
231
244
245
+ // Do not ask for confirmation
246
+ rootCmd .PersistentFlags ().BoolVarP (& flagYes , "yes" , "y" , false , "Skip confirmation" )
247
+
232
248
rootCmd .AddCommand (newWorkflowCmd (), newAuthCmd (), NewVersionCmd (),
233
249
newAttestationCmd (), newArtifactCmd (), newConfigCmd (),
234
250
newIntegrationCmd (), newOrganizationCmd (), newCASBackendCmd (),
@@ -261,7 +277,7 @@ func init() {
261
277
262
278
// isTelemetryDisabled checks if the telemetry is disabled by the user or if we are running a development version
263
279
func isTelemetryDisabled () bool {
264
- return os .Getenv (doNotTrackEnv ) == "1" || os .Getenv (doNotTrackEnv ) == "true" || Version == devVersion
280
+ return os .Getenv (doNotTrackEnv ) == "1" || os .Getenv (doNotTrackEnv ) == trueString || Version == devVersion
265
281
}
266
282
267
283
func initLogger (logger zerolog.Logger ) (zerolog.Logger , error ) {
@@ -334,36 +350,38 @@ func cleanup(conn *grpc.ClientConn) error {
334
350
// 2.2 Load the token from the environment variable and from the auth login config file
335
351
// 2.3 if they both exist, we default to the user token
336
352
// 2.4 otherwise to the one that's set
337
- func loadControlplaneAuthToken (cmd * cobra.Command ) (string , error ) {
338
- // If the CMD uses a robot account instead of the regular auth token we override it
339
- if _ , ok := cmd .Annotations [useAPIToken ]; ok {
340
- if attAPIToken == "" {
341
- return "" , newGracefulError (ErrAttestationTokenRequired )
342
- }
353
+ func loadControlplaneAuthToken (cmd * cobra.Command ) (string , bool , error ) {
354
+ // Load the APIToken from the env variable
355
+ apiTokenFromVar := os .Getenv (tokenEnvVarName )
343
356
344
- return attAPIToken , nil
357
+ // Load the user token from the config file
358
+ userToken := viper .GetString (confOptions .authToken .viperKey )
359
+
360
+ apiTokenFromFlagOrVar := apiToken
361
+ if apiTokenFromFlagOrVar == "" {
362
+ apiTokenFromFlagOrVar = apiTokenFromVar
363
+ }
364
+
365
+ // Prefer to use the API token if the command can use it, and it's provided (i.e. attestations)
366
+ if isAPITokenPreferred (cmd ) && apiTokenFromFlagOrVar != "" {
367
+ return apiTokenFromFlagOrVar , false , nil
345
368
}
346
369
347
370
// Now we check explicitly provided API token via the flag
348
371
if apiToken != "" {
349
372
logger .Info ().Msg ("API token provided to the command line" )
350
- return apiToken , nil
373
+ return apiToken , false , nil
351
374
}
352
375
353
- // Load the user token from the config file
354
- userToken := viper .GetString (confOptions .authToken .viperKey )
355
- // Load the APIToken from the env variable
356
- // Instead we load the env variable manually
357
- apiTokenFromVar := os .Getenv (tokenEnvVarName )
358
- // If both the user authentication and the API token are set, we default to user authentication
376
+ // If both the user authentication and the API token en var are set, we default to user authentication
359
377
if userToken != "" && apiTokenFromVar != "" {
360
378
logger .Warn ().Msgf ("Both user credentials and $%s set. Ignoring $%s." , tokenEnvVarName , tokenEnvVarName )
361
- return userToken , nil
379
+ return userToken , true , nil
362
380
} else if apiTokenFromVar != "" {
363
- return apiTokenFromVar , nil
381
+ return apiTokenFromVar , false , nil
364
382
}
365
383
366
- return userToken , nil
384
+ return userToken , true , nil
367
385
}
368
386
369
387
// parseToken the token and return the type of token. At the moment in Chainloop we have 3 types of tokens:
@@ -499,3 +517,7 @@ func setLocalOrganization(orgName string) error {
499
517
viper .Set (confOptions .organization .viperKey , orgName )
500
518
return viper .WriteConfig ()
501
519
}
520
+
521
+ func isAPITokenPreferred (cmd * cobra.Command ) bool {
522
+ return cmd .Annotations [useAPIToken ] == trueString
523
+ }
0 commit comments