1package session 2 3import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws/awserr" 7 "github.com/aws/aws-sdk-go/aws/credentials" 8 "github.com/aws/aws-sdk-go/aws/endpoints" 9 "github.com/aws/aws-sdk-go/internal/ini" 10) 11 12const ( 13 // Static Credentials group 14 accessKeyIDKey = `aws_access_key_id` // group required 15 secretAccessKey = `aws_secret_access_key` // group required 16 sessionTokenKey = `aws_session_token` // optional 17 18 // Assume Role Credentials group 19 roleArnKey = `role_arn` // group required 20 sourceProfileKey = `source_profile` // group required (or credential_source) 21 credentialSourceKey = `credential_source` // group required (or source_profile) 22 externalIDKey = `external_id` // optional 23 mfaSerialKey = `mfa_serial` // optional 24 roleSessionNameKey = `role_session_name` // optional 25 26 // CSM options 27 csmEnabledKey = `csm_enabled` 28 csmHostKey = `csm_host` 29 csmPortKey = `csm_port` 30 csmClientIDKey = `csm_client_id` 31 32 // Additional Config fields 33 regionKey = `region` 34 35 // endpoint discovery group 36 enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional 37 38 // External Credential Process 39 credentialProcessKey = `credential_process` // optional 40 41 // Web Identity Token File 42 webIdentityTokenFileKey = `web_identity_token_file` // optional 43 44 // Additional config fields for regional or legacy endpoints 45 stsRegionalEndpointSharedKey = `sts_regional_endpoints` 46 47 // Additional config fields for regional or legacy endpoints 48 s3UsEast1RegionalSharedKey = `s3_us_east_1_regional_endpoint` 49 50 // DefaultSharedConfigProfile is the default profile to be used when 51 // loading configuration from the config files if another profile name 52 // is not provided. 53 DefaultSharedConfigProfile = `default` 54 55 // S3 ARN Region Usage 56 s3UseARNRegionKey = "s3_use_arn_region" 57) 58 59// sharedConfig represents the configuration fields of the SDK config files. 60type sharedConfig struct { 61 // Credentials values from the config file. Both aws_access_key_id and 62 // aws_secret_access_key must be provided together in the same file to be 63 // considered valid. The values will be ignored if not a complete group. 64 // aws_session_token is an optional field that can be provided if both of 65 // the other two fields are also provided. 66 // 67 // aws_access_key_id 68 // aws_secret_access_key 69 // aws_session_token 70 Creds credentials.Value 71 72 CredentialSource string 73 CredentialProcess string 74 WebIdentityTokenFile string 75 76 RoleARN string 77 RoleSessionName string 78 ExternalID string 79 MFASerial string 80 81 SourceProfileName string 82 SourceProfile *sharedConfig 83 84 // Region is the region the SDK should use for looking up AWS service 85 // endpoints and signing requests. 86 // 87 // region 88 Region string 89 90 // EnableEndpointDiscovery can be enabled in the shared config by setting 91 // endpoint_discovery_enabled to true 92 // 93 // endpoint_discovery_enabled = true 94 EnableEndpointDiscovery *bool 95 96 // CSM Options 97 CSMEnabled *bool 98 CSMHost string 99 CSMPort string 100 CSMClientID string 101 102 // Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service 103 // 104 // sts_regional_endpoints = regional 105 // This can take value as `LegacySTSEndpoint` or `RegionalSTSEndpoint` 106 STSRegionalEndpoint endpoints.STSRegionalEndpoint 107 108 // Specifies the Regional Endpoint flag for the SDK to resolve the endpoint for a service 109 // 110 // s3_us_east_1_regional_endpoint = regional 111 // This can take value as `LegacyS3UsEast1Endpoint` or `RegionalS3UsEast1Endpoint` 112 S3UsEast1RegionalEndpoint endpoints.S3UsEast1RegionalEndpoint 113 114 // Specifies if the S3 service should allow ARNs to direct the region 115 // the client's requests are sent to. 116 // 117 // s3_use_arn_region=true 118 S3UseARNRegion bool 119} 120 121type sharedConfigFile struct { 122 Filename string 123 IniData ini.Sections 124} 125 126// loadSharedConfig retrieves the configuration from the list of files using 127// the profile provided. The order the files are listed will determine 128// precedence. Values in subsequent files will overwrite values defined in 129// earlier files. 130// 131// For example, given two files A and B. Both define credentials. If the order 132// of the files are A then B, B's credential values will be used instead of 133// A's. 134// 135// See sharedConfig.setFromFile for information how the config files 136// will be loaded. 137func loadSharedConfig(profile string, filenames []string, exOpts bool) (sharedConfig, error) { 138 if len(profile) == 0 { 139 profile = DefaultSharedConfigProfile 140 } 141 142 files, err := loadSharedConfigIniFiles(filenames) 143 if err != nil { 144 return sharedConfig{}, err 145 } 146 147 cfg := sharedConfig{} 148 profiles := map[string]struct{}{} 149 if err = cfg.setFromIniFiles(profiles, profile, files, exOpts); err != nil { 150 return sharedConfig{}, err 151 } 152 153 return cfg, nil 154} 155 156func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) { 157 files := make([]sharedConfigFile, 0, len(filenames)) 158 159 for _, filename := range filenames { 160 sections, err := ini.OpenFile(filename) 161 if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ini.ErrCodeUnableToReadFile { 162 // Skip files which can't be opened and read for whatever reason 163 continue 164 } else if err != nil { 165 return nil, SharedConfigLoadError{Filename: filename, Err: err} 166 } 167 168 files = append(files, sharedConfigFile{ 169 Filename: filename, IniData: sections, 170 }) 171 } 172 173 return files, nil 174} 175 176func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile string, files []sharedConfigFile, exOpts bool) error { 177 // Trim files from the list that don't exist. 178 var skippedFiles int 179 var profileNotFoundErr error 180 for _, f := range files { 181 if err := cfg.setFromIniFile(profile, f, exOpts); err != nil { 182 if _, ok := err.(SharedConfigProfileNotExistsError); ok { 183 // Ignore profiles not defined in individual files. 184 profileNotFoundErr = err 185 skippedFiles++ 186 continue 187 } 188 return err 189 } 190 } 191 if skippedFiles == len(files) { 192 // If all files were skipped because the profile is not found, return 193 // the original profile not found error. 194 return profileNotFoundErr 195 } 196 197 if _, ok := profiles[profile]; ok { 198 // if this is the second instance of the profile the Assume Role 199 // options must be cleared because they are only valid for the 200 // first reference of a profile. The self linked instance of the 201 // profile only have credential provider options. 202 cfg.clearAssumeRoleOptions() 203 } else { 204 // First time a profile has been seen, It must either be a assume role 205 // or credentials. Assert if the credential type requires a role ARN, 206 // the ARN is also set. 207 if err := cfg.validateCredentialsRequireARN(profile); err != nil { 208 return err 209 } 210 } 211 profiles[profile] = struct{}{} 212 213 if err := cfg.validateCredentialType(); err != nil { 214 return err 215 } 216 217 // Link source profiles for assume roles 218 if len(cfg.SourceProfileName) != 0 { 219 // Linked profile via source_profile ignore credential provider 220 // options, the source profile must provide the credentials. 221 cfg.clearCredentialOptions() 222 223 srcCfg := &sharedConfig{} 224 err := srcCfg.setFromIniFiles(profiles, cfg.SourceProfileName, files, exOpts) 225 if err != nil { 226 // SourceProfile that doesn't exist is an error in configuration. 227 if _, ok := err.(SharedConfigProfileNotExistsError); ok { 228 err = SharedConfigAssumeRoleError{ 229 RoleARN: cfg.RoleARN, 230 SourceProfile: cfg.SourceProfileName, 231 } 232 } 233 return err 234 } 235 236 if !srcCfg.hasCredentials() { 237 return SharedConfigAssumeRoleError{ 238 RoleARN: cfg.RoleARN, 239 SourceProfile: cfg.SourceProfileName, 240 } 241 } 242 243 cfg.SourceProfile = srcCfg 244 } 245 246 return nil 247} 248 249// setFromFile loads the configuration from the file using the profile 250// provided. A sharedConfig pointer type value is used so that multiple config 251// file loadings can be chained. 252// 253// Only loads complete logically grouped values, and will not set fields in cfg 254// for incomplete grouped values in the config. Such as credentials. For 255// example if a config file only includes aws_access_key_id but no 256// aws_secret_access_key the aws_access_key_id will be ignored. 257func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, exOpts bool) error { 258 section, ok := file.IniData.GetSection(profile) 259 if !ok { 260 // Fallback to to alternate profile name: profile <name> 261 section, ok = file.IniData.GetSection(fmt.Sprintf("profile %s", profile)) 262 if !ok { 263 return SharedConfigProfileNotExistsError{Profile: profile, Err: nil} 264 } 265 } 266 267 if exOpts { 268 // Assume Role Parameters 269 updateString(&cfg.RoleARN, section, roleArnKey) 270 updateString(&cfg.ExternalID, section, externalIDKey) 271 updateString(&cfg.MFASerial, section, mfaSerialKey) 272 updateString(&cfg.RoleSessionName, section, roleSessionNameKey) 273 updateString(&cfg.SourceProfileName, section, sourceProfileKey) 274 updateString(&cfg.CredentialSource, section, credentialSourceKey) 275 updateString(&cfg.Region, section, regionKey) 276 277 if v := section.String(stsRegionalEndpointSharedKey); len(v) != 0 { 278 sre, err := endpoints.GetSTSRegionalEndpoint(v) 279 if err != nil { 280 return fmt.Errorf("failed to load %s from shared config, %s, %v", 281 stsRegionalEndpointSharedKey, file.Filename, err) 282 } 283 cfg.STSRegionalEndpoint = sre 284 } 285 286 if v := section.String(s3UsEast1RegionalSharedKey); len(v) != 0 { 287 sre, err := endpoints.GetS3UsEast1RegionalEndpoint(v) 288 if err != nil { 289 return fmt.Errorf("failed to load %s from shared config, %s, %v", 290 s3UsEast1RegionalSharedKey, file.Filename, err) 291 } 292 cfg.S3UsEast1RegionalEndpoint = sre 293 } 294 } 295 296 updateString(&cfg.CredentialProcess, section, credentialProcessKey) 297 updateString(&cfg.WebIdentityTokenFile, section, webIdentityTokenFileKey) 298 299 // Shared Credentials 300 creds := credentials.Value{ 301 AccessKeyID: section.String(accessKeyIDKey), 302 SecretAccessKey: section.String(secretAccessKey), 303 SessionToken: section.String(sessionTokenKey), 304 ProviderName: fmt.Sprintf("SharedConfigCredentials: %s", file.Filename), 305 } 306 if creds.HasKeys() { 307 cfg.Creds = creds 308 } 309 310 // Endpoint discovery 311 updateBoolPtr(&cfg.EnableEndpointDiscovery, section, enableEndpointDiscoveryKey) 312 313 // CSM options 314 updateBoolPtr(&cfg.CSMEnabled, section, csmEnabledKey) 315 updateString(&cfg.CSMHost, section, csmHostKey) 316 updateString(&cfg.CSMPort, section, csmPortKey) 317 updateString(&cfg.CSMClientID, section, csmClientIDKey) 318 319 updateBool(&cfg.S3UseARNRegion, section, s3UseARNRegionKey) 320 321 return nil 322} 323 324func (cfg *sharedConfig) validateCredentialsRequireARN(profile string) error { 325 var credSource string 326 327 switch { 328 case len(cfg.SourceProfileName) != 0: 329 credSource = sourceProfileKey 330 case len(cfg.CredentialSource) != 0: 331 credSource = credentialSourceKey 332 case len(cfg.WebIdentityTokenFile) != 0: 333 credSource = webIdentityTokenFileKey 334 } 335 336 if len(credSource) != 0 && len(cfg.RoleARN) == 0 { 337 return CredentialRequiresARNError{ 338 Type: credSource, 339 Profile: profile, 340 } 341 } 342 343 return nil 344} 345 346func (cfg *sharedConfig) validateCredentialType() error { 347 // Only one or no credential type can be defined. 348 if !oneOrNone( 349 len(cfg.SourceProfileName) != 0, 350 len(cfg.CredentialSource) != 0, 351 len(cfg.CredentialProcess) != 0, 352 len(cfg.WebIdentityTokenFile) != 0, 353 ) { 354 return ErrSharedConfigSourceCollision 355 } 356 357 return nil 358} 359 360func (cfg *sharedConfig) hasCredentials() bool { 361 switch { 362 case len(cfg.SourceProfileName) != 0: 363 case len(cfg.CredentialSource) != 0: 364 case len(cfg.CredentialProcess) != 0: 365 case len(cfg.WebIdentityTokenFile) != 0: 366 case cfg.Creds.HasKeys(): 367 default: 368 return false 369 } 370 371 return true 372} 373 374func (cfg *sharedConfig) clearCredentialOptions() { 375 cfg.CredentialSource = "" 376 cfg.CredentialProcess = "" 377 cfg.WebIdentityTokenFile = "" 378 cfg.Creds = credentials.Value{} 379} 380 381func (cfg *sharedConfig) clearAssumeRoleOptions() { 382 cfg.RoleARN = "" 383 cfg.ExternalID = "" 384 cfg.MFASerial = "" 385 cfg.RoleSessionName = "" 386 cfg.SourceProfileName = "" 387} 388 389func oneOrNone(bs ...bool) bool { 390 var count int 391 392 for _, b := range bs { 393 if b { 394 count++ 395 if count > 1 { 396 return false 397 } 398 } 399 } 400 401 return true 402} 403 404// updateString will only update the dst with the value in the section key, key 405// is present in the section. 406func updateString(dst *string, section ini.Section, key string) { 407 if !section.Has(key) { 408 return 409 } 410 *dst = section.String(key) 411} 412 413// updateBool will only update the dst with the value in the section key, key 414// is present in the section. 415func updateBool(dst *bool, section ini.Section, key string) { 416 if !section.Has(key) { 417 return 418 } 419 *dst = section.Bool(key) 420} 421 422// updateBoolPtr will only update the dst with the value in the section key, 423// key is present in the section. 424func updateBoolPtr(dst **bool, section ini.Section, key string) { 425 if !section.Has(key) { 426 return 427 } 428 *dst = new(bool) 429 **dst = section.Bool(key) 430} 431 432// SharedConfigLoadError is an error for the shared config file failed to load. 433type SharedConfigLoadError struct { 434 Filename string 435 Err error 436} 437 438// Code is the short id of the error. 439func (e SharedConfigLoadError) Code() string { 440 return "SharedConfigLoadError" 441} 442 443// Message is the description of the error 444func (e SharedConfigLoadError) Message() string { 445 return fmt.Sprintf("failed to load config file, %s", e.Filename) 446} 447 448// OrigErr is the underlying error that caused the failure. 449func (e SharedConfigLoadError) OrigErr() error { 450 return e.Err 451} 452 453// Error satisfies the error interface. 454func (e SharedConfigLoadError) Error() string { 455 return awserr.SprintError(e.Code(), e.Message(), "", e.Err) 456} 457 458// SharedConfigProfileNotExistsError is an error for the shared config when 459// the profile was not find in the config file. 460type SharedConfigProfileNotExistsError struct { 461 Profile string 462 Err error 463} 464 465// Code is the short id of the error. 466func (e SharedConfigProfileNotExistsError) Code() string { 467 return "SharedConfigProfileNotExistsError" 468} 469 470// Message is the description of the error 471func (e SharedConfigProfileNotExistsError) Message() string { 472 return fmt.Sprintf("failed to get profile, %s", e.Profile) 473} 474 475// OrigErr is the underlying error that caused the failure. 476func (e SharedConfigProfileNotExistsError) OrigErr() error { 477 return e.Err 478} 479 480// Error satisfies the error interface. 481func (e SharedConfigProfileNotExistsError) Error() string { 482 return awserr.SprintError(e.Code(), e.Message(), "", e.Err) 483} 484 485// SharedConfigAssumeRoleError is an error for the shared config when the 486// profile contains assume role information, but that information is invalid 487// or not complete. 488type SharedConfigAssumeRoleError struct { 489 RoleARN string 490 SourceProfile string 491} 492 493// Code is the short id of the error. 494func (e SharedConfigAssumeRoleError) Code() string { 495 return "SharedConfigAssumeRoleError" 496} 497 498// Message is the description of the error 499func (e SharedConfigAssumeRoleError) Message() string { 500 return fmt.Sprintf( 501 "failed to load assume role for %s, source profile %s has no shared credentials", 502 e.RoleARN, e.SourceProfile, 503 ) 504} 505 506// OrigErr is the underlying error that caused the failure. 507func (e SharedConfigAssumeRoleError) OrigErr() error { 508 return nil 509} 510 511// Error satisfies the error interface. 512func (e SharedConfigAssumeRoleError) Error() string { 513 return awserr.SprintError(e.Code(), e.Message(), "", nil) 514} 515 516// CredentialRequiresARNError provides the error for shared config credentials 517// that are incorrectly configured in the shared config or credentials file. 518type CredentialRequiresARNError struct { 519 // type of credentials that were configured. 520 Type string 521 522 // Profile name the credentials were in. 523 Profile string 524} 525 526// Code is the short id of the error. 527func (e CredentialRequiresARNError) Code() string { 528 return "CredentialRequiresARNError" 529} 530 531// Message is the description of the error 532func (e CredentialRequiresARNError) Message() string { 533 return fmt.Sprintf( 534 "credential type %s requires role_arn, profile %s", 535 e.Type, e.Profile, 536 ) 537} 538 539// OrigErr is the underlying error that caused the failure. 540func (e CredentialRequiresARNError) OrigErr() error { 541 return nil 542} 543 544// Error satisfies the error interface. 545func (e CredentialRequiresARNError) Error() string { 546 return awserr.SprintError(e.Code(), e.Message(), "", nil) 547} 548