shared_config.go 18 KB

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