shared_config.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package session
  2. import (
  3. "fmt"
  4. "github.com/aws/aws-sdk-go/aws/awserr"
  5. "github.com/aws/aws-sdk-go/aws/credentials"
  6. "github.com/aws/aws-sdk-go/internal/ini"
  7. )
  8. const (
  9. // Static Credentials group
  10. accessKeyIDKey = `aws_access_key_id` // group required
  11. secretAccessKey = `aws_secret_access_key` // group required
  12. sessionTokenKey = `aws_session_token` // optional
  13. // Assume Role Credentials group
  14. roleArnKey = `role_arn` // group required
  15. sourceProfileKey = `source_profile` // group required (or credential_source)
  16. credentialSourceKey = `credential_source` // group required (or source_profile)
  17. externalIDKey = `external_id` // optional
  18. mfaSerialKey = `mfa_serial` // optional
  19. roleSessionNameKey = `role_session_name` // optional
  20. // Additional Config fields
  21. regionKey = `region`
  22. // endpoint discovery group
  23. enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional
  24. // External Credential Process
  25. credentialProcessKey = `credential_process`
  26. // DefaultSharedConfigProfile is the default profile to be used when
  27. // loading configuration from the config files if another profile name
  28. // is not provided.
  29. DefaultSharedConfigProfile = `default`
  30. )
  31. type assumeRoleConfig struct {
  32. RoleARN string
  33. SourceProfile string
  34. CredentialSource string
  35. ExternalID string
  36. MFASerial string
  37. RoleSessionName string
  38. }
  39. // sharedConfig represents the configuration fields of the SDK config files.
  40. type sharedConfig struct {
  41. // Credentials values from the config file. Both aws_access_key_id
  42. // and aws_secret_access_key must be provided together in the same file
  43. // to be considered valid. The values will be ignored if not a complete group.
  44. // aws_session_token is an optional field that can be provided if both of the
  45. // other two fields are also provided.
  46. //
  47. // aws_access_key_id
  48. // aws_secret_access_key
  49. // aws_session_token
  50. Creds credentials.Value
  51. AssumeRole assumeRoleConfig
  52. AssumeRoleSource *sharedConfig
  53. // An external process to request credentials
  54. CredentialProcess string
  55. // Region is the region the SDK should use for looking up AWS service endpoints
  56. // and signing requests.
  57. //
  58. // region
  59. Region string
  60. // EnableEndpointDiscovery can be enabled in the shared config by setting
  61. // endpoint_discovery_enabled to true
  62. //
  63. // endpoint_discovery_enabled = true
  64. EnableEndpointDiscovery *bool
  65. }
  66. type sharedConfigFile struct {
  67. Filename string
  68. IniData ini.Sections
  69. }
  70. // loadSharedConfig retrieves the configuration from the list of files
  71. // using the profile provided. The order the files are listed will determine
  72. // precedence. Values in subsequent files will overwrite values defined in
  73. // earlier files.
  74. //
  75. // For example, given two files A and B. Both define credentials. If the order
  76. // of the files are A then B, B's credential values will be used instead of A's.
  77. //
  78. // See sharedConfig.setFromFile for information how the config files
  79. // will be loaded.
  80. func loadSharedConfig(profile string, filenames []string) (sharedConfig, error) {
  81. if len(profile) == 0 {
  82. profile = DefaultSharedConfigProfile
  83. }
  84. files, err := loadSharedConfigIniFiles(filenames)
  85. if err != nil {
  86. return sharedConfig{}, err
  87. }
  88. cfg := sharedConfig{}
  89. if err = cfg.setFromIniFiles(profile, files); err != nil {
  90. return sharedConfig{}, err
  91. }
  92. if len(cfg.AssumeRole.SourceProfile) > 0 {
  93. if err := cfg.setAssumeRoleSource(profile, files); err != nil {
  94. return sharedConfig{}, err
  95. }
  96. }
  97. return cfg, nil
  98. }
  99. func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) {
  100. files := make([]sharedConfigFile, 0, len(filenames))
  101. for _, filename := range filenames {
  102. sections, err := ini.OpenFile(filename)
  103. if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ini.ErrCodeUnableToReadFile {
  104. // Skip files which can't be opened and read for whatever reason
  105. continue
  106. } else if err != nil {
  107. return nil, SharedConfigLoadError{Filename: filename, Err: err}
  108. }
  109. files = append(files, sharedConfigFile{
  110. Filename: filename, IniData: sections,
  111. })
  112. }
  113. return files, nil
  114. }
  115. func (cfg *sharedConfig) setAssumeRoleSource(origProfile string, files []sharedConfigFile) error {
  116. var assumeRoleSrc sharedConfig
  117. if len(cfg.AssumeRole.CredentialSource) > 0 {
  118. // setAssumeRoleSource is only called when source_profile is found.
  119. // If both source_profile and credential_source are set, then
  120. // ErrSharedConfigSourceCollision will be returned
  121. return ErrSharedConfigSourceCollision
  122. }
  123. // Multiple level assume role chains are not support
  124. if cfg.AssumeRole.SourceProfile == origProfile {
  125. assumeRoleSrc = *cfg
  126. assumeRoleSrc.AssumeRole = assumeRoleConfig{}
  127. } else {
  128. err := assumeRoleSrc.setFromIniFiles(cfg.AssumeRole.SourceProfile, files)
  129. if err != nil {
  130. return err
  131. }
  132. }
  133. if len(assumeRoleSrc.Creds.AccessKeyID) == 0 {
  134. return SharedConfigAssumeRoleError{RoleARN: cfg.AssumeRole.RoleARN}
  135. }
  136. cfg.AssumeRoleSource = &assumeRoleSrc
  137. return nil
  138. }
  139. func (cfg *sharedConfig) setFromIniFiles(profile string, files []sharedConfigFile) error {
  140. // Trim files from the list that don't exist.
  141. for _, f := range files {
  142. if err := cfg.setFromIniFile(profile, f); err != nil {
  143. if _, ok := err.(SharedConfigProfileNotExistsError); ok {
  144. // Ignore proviles missings
  145. continue
  146. }
  147. return err
  148. }
  149. }
  150. return nil
  151. }
  152. // setFromFile loads the configuration from the file using
  153. // the profile provided. A sharedConfig pointer type value is used so that
  154. // multiple config file loadings can be chained.
  155. //
  156. // Only loads complete logically grouped values, and will not set fields in cfg
  157. // for incomplete grouped values in the config. Such as credentials. For example
  158. // if a config file only includes aws_access_key_id but no aws_secret_access_key
  159. // the aws_access_key_id will be ignored.
  160. func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile) error {
  161. section, ok := file.IniData.GetSection(profile)
  162. if !ok {
  163. // Fallback to to alternate profile name: profile <name>
  164. section, ok = file.IniData.GetSection(fmt.Sprintf("profile %s", profile))
  165. if !ok {
  166. return SharedConfigProfileNotExistsError{Profile: profile, Err: nil}
  167. }
  168. }
  169. // Shared Credentials
  170. akid := section.String(accessKeyIDKey)
  171. secret := section.String(secretAccessKey)
  172. if len(akid) > 0 && len(secret) > 0 {
  173. cfg.Creds = credentials.Value{
  174. AccessKeyID: akid,
  175. SecretAccessKey: secret,
  176. SessionToken: section.String(sessionTokenKey),
  177. ProviderName: fmt.Sprintf("SharedConfigCredentials: %s", file.Filename),
  178. }
  179. }
  180. // Assume Role
  181. roleArn := section.String(roleArnKey)
  182. srcProfile := section.String(sourceProfileKey)
  183. credentialSource := section.String(credentialSourceKey)
  184. hasSource := len(srcProfile) > 0 || len(credentialSource) > 0
  185. if len(roleArn) > 0 && hasSource {
  186. cfg.AssumeRole = assumeRoleConfig{
  187. RoleARN: roleArn,
  188. SourceProfile: srcProfile,
  189. CredentialSource: credentialSource,
  190. ExternalID: section.String(externalIDKey),
  191. MFASerial: section.String(mfaSerialKey),
  192. RoleSessionName: section.String(roleSessionNameKey),
  193. }
  194. }
  195. // `credential_process`
  196. if credProc := section.String(credentialProcessKey); len(credProc) > 0 {
  197. cfg.CredentialProcess = credProc
  198. }
  199. // Region
  200. if v := section.String(regionKey); len(v) > 0 {
  201. cfg.Region = v
  202. }
  203. // Endpoint discovery
  204. if section.Has(enableEndpointDiscoveryKey) {
  205. v := section.Bool(enableEndpointDiscoveryKey)
  206. cfg.EnableEndpointDiscovery = &v
  207. }
  208. return nil
  209. }
  210. // SharedConfigLoadError is an error for the shared config file failed to load.
  211. type SharedConfigLoadError struct {
  212. Filename string
  213. Err error
  214. }
  215. // Code is the short id of the error.
  216. func (e SharedConfigLoadError) Code() string {
  217. return "SharedConfigLoadError"
  218. }
  219. // Message is the description of the error
  220. func (e SharedConfigLoadError) Message() string {
  221. return fmt.Sprintf("failed to load config file, %s", e.Filename)
  222. }
  223. // OrigErr is the underlying error that caused the failure.
  224. func (e SharedConfigLoadError) OrigErr() error {
  225. return e.Err
  226. }
  227. // Error satisfies the error interface.
  228. func (e SharedConfigLoadError) Error() string {
  229. return awserr.SprintError(e.Code(), e.Message(), "", e.Err)
  230. }
  231. // SharedConfigProfileNotExistsError is an error for the shared config when
  232. // the profile was not find in the config file.
  233. type SharedConfigProfileNotExistsError struct {
  234. Profile string
  235. Err error
  236. }
  237. // Code is the short id of the error.
  238. func (e SharedConfigProfileNotExistsError) Code() string {
  239. return "SharedConfigProfileNotExistsError"
  240. }
  241. // Message is the description of the error
  242. func (e SharedConfigProfileNotExistsError) Message() string {
  243. return fmt.Sprintf("failed to get profile, %s", e.Profile)
  244. }
  245. // OrigErr is the underlying error that caused the failure.
  246. func (e SharedConfigProfileNotExistsError) OrigErr() error {
  247. return e.Err
  248. }
  249. // Error satisfies the error interface.
  250. func (e SharedConfigProfileNotExistsError) Error() string {
  251. return awserr.SprintError(e.Code(), e.Message(), "", e.Err)
  252. }
  253. // SharedConfigAssumeRoleError is an error for the shared config when the
  254. // profile contains assume role information, but that information is invalid
  255. // or not complete.
  256. type SharedConfigAssumeRoleError struct {
  257. RoleARN string
  258. }
  259. // Code is the short id of the error.
  260. func (e SharedConfigAssumeRoleError) Code() string {
  261. return "SharedConfigAssumeRoleError"
  262. }
  263. // Message is the description of the error
  264. func (e SharedConfigAssumeRoleError) Message() string {
  265. return fmt.Sprintf("failed to load assume role for %s, source profile has no shared credentials",
  266. e.RoleARN)
  267. }
  268. // OrigErr is the underlying error that caused the failure.
  269. func (e SharedConfigAssumeRoleError) OrigErr() error {
  270. return nil
  271. }
  272. // Error satisfies the error interface.
  273. func (e SharedConfigAssumeRoleError) Error() string {
  274. return awserr.SprintError(e.Code(), e.Message(), "", nil)
  275. }