diff --git a/lib/amazonka/CHANGELOG.md b/lib/amazonka/CHANGELOG.md index fc8c8ac0586..aa4efbeacdc 100644 --- a/lib/amazonka/CHANGELOG.md +++ b/lib/amazonka/CHANGELOG.md @@ -2,9 +2,20 @@ ## Unreleased +### Changed + +- `amazonka`: Add support for `AWS_SHARED_CREDENTIALS_FILE` and `AWS_CONFIG_FILE` environment variables to override the + default paths `$HOME/.aws/credentials` and `$HOME/.aws/config` [\#951](https://github.com/brendanhay/amazonka/pull/951) + +### Fixed + +- `amazonka`: Allow reading the AWS config file when the credentials file is missing [\#951](https://github.com/brendanhay/amazonka/pull/951). + This is useful when you are using a role-based authentication method or AWS IAM Identity Center (formerly AWS SSO) which does not require a credentials file. + Before this fix you had to create an empty credentials file for these methods to work correctly. - `amazonka`: During logging, do not print bytestrings that have unprintable characters [\#952](https://github.com/brendanhay/amazonka/pull/952) + ## [2.0.0](https://github.com/brendanhay/amazonka/tree/2.0.0) Released: **28 July 2023**, Compare: [2.0.0-rc2](https://github.com/brendanhay/amazonka/compare/2.0.0-rc2...2.0.0) diff --git a/lib/amazonka/src/Amazonka/Auth/ConfigFile.hs b/lib/amazonka/src/Amazonka/Auth/ConfigFile.hs index 2d8ac77e38e..f438b599b23 100644 --- a/lib/amazonka/src/Amazonka/Auth/ConfigFile.hs +++ b/lib/amazonka/src/Amazonka/Auth/ConfigFile.hs @@ -36,7 +36,36 @@ import System.Info (os) -- Amazonka currently understands them: -- -- * AWS recommends credentials do not live in the config file, but --- allows it. +-- allows it. You should instead define them in the credentials file. +-- +-- * You can set @role_arn@ together with either @source_profile@, +-- @credential_source@ , or @web_identity_token_file@. Unlike the +-- standard SDK we only support @role_session_name@ for +-- @web_identity_token_file@ and not the other AssumeRole methods. +-- This might be fixed in the future. +-- +-- * If you set @role_arn@ and @source_profile@, the source profile's +-- credentials will be used to assume the role. +-- +-- * If you set @role_arn@ and @credential_source@, the credentials are +-- retrieved from the specified source. The source can be one of +-- @Environment@, @Ec2InstanceMetadata@, or @EcsContainer@. +-- +-- * If you set @role_arn@ and @web_identity_token_file@, the OIDC token in +-- the file will be used to assume the role. You can also +-- set @role_session_name@ to specify the name of the session. +-- +-- * You can finally also configure assuming a role using AWS Identity Center +-- (Formerly AWS SSO) by setting @sso_start_url@, @sso_region@, +-- @sso_account_id@, and @sso_role_name@ in your profile section. +-- Amazonka currently does not initiate the SSO login flow, so you will have +-- to do that yourself using the AWS CLI. Amazonka will then look in +-- @~\/.aws\/sso\/cache@ for a cached token. +-- +-- * We currently only support 'Legacy' SSO profiles and do not support +-- setting common SSO settings in a @[sso-session ]@ section or +-- support token refresh. So use the following guide to set up your AWS CLI: +-- https://docs.aws.amazon.com/cli/latest/userguide/sso-configure-profile-legacy.html -- -- * Sections in the config file start should either be named -- @[default]@ or @[profile foo]@. Unprefixed @[foo]@ currently @@ -60,7 +89,14 @@ fromFilePath :: Env' withAuth -> m Env fromFilePath profile credentialsFile configFile env = liftIO $ do - credentialsIni <- loadIniFile credentialsFile + -- If we fail to read the credentials file, assume it's empty and + -- move on. It is valid to configure only a config file if you plan + -- to assume a role using any of the assume role methods. + credentialsIni <- + Exception.catchJust + (\(_ :: AuthError) -> Just mempty) + (loadIniFile credentialsFile) + pure -- If we fail to read the config file, assume it's empty and move -- on. It is valid to configure only a credentials file if you only -- want to set keys, for example. @@ -107,12 +143,18 @@ fromFilePath profile credentialsFile configFile env = liftIO $ do config <- ask case HashMap.lookup pName config of Nothing -> - liftIO . Exception.throwIO . InvalidFileError $ - "Missing profile: " <> Text.pack (show pName) + liftIO + . Exception.throwIO + . InvalidFileError + $ "Missing profile: " + <> Text.pack (show pName) Just p -> case parseConfigProfile p of Nothing -> - liftIO . Exception.throwIO . InvalidFileError $ - "Parse error in profile: " <> Text.pack (show pName) + liftIO + . Exception.throwIO + . InvalidFileError + $ "Parse error in profile: " + <> Text.pack (show pName) Just (cp, mRegion) -> do env' <- case cp of ExplicitKeys keys -> @@ -126,7 +168,8 @@ fromFilePath profile credentialsFile configFile env = liftIO $ do in liftIO . Exception.throwIO . InvalidFileError - $ "Infinite source_profile loop: " <> textTrace + $ "Infinite source_profile loop: " + <> textTrace else do lift . modify $ (sourceProfileName :) sourceEnv <- evalConfig sourceProfileName @@ -141,8 +184,9 @@ fromFilePath profile credentialsFile configFile env = liftIO $ do fromWebIdentity tokenFile roleArn mRoleSessionName env AssumeRoleViaSSO startUrl ssoRegion accountId roleName -> do cachedTokenFile <- - liftIO $ - configPathRelative =<< relativeCachedTokenFile startUrl + liftIO + $ configPathRelative + =<< relativeCachedTokenFile startUrl fromSSO cachedTokenFile ssoRegion accountId roleName env -- Once we have the env from the profile, apply the region @@ -188,19 +232,24 @@ parseConfigProfile profile = parseProfile <&> (,parseRegion) parseRegion = Region' <$> HashMap.lookup "region" profile explicitKey = - fmap ExplicitKeys $ - AuthEnv - <$> ( AccessKey . Text.encodeUtf8 - <$> HashMap.lookup "aws_access_key_id" profile - ) - <*> ( Sensitive . SecretKey . Text.encodeUtf8 - <$> HashMap.lookup "aws_secret_access_key" profile - ) - <*> Just - ( Sensitive . SessionToken . Text.encodeUtf8 - <$> HashMap.lookup "aws_session_token" profile + fmap ExplicitKeys + $ AuthEnv + <$> ( AccessKey + . Text.encodeUtf8 + <$> HashMap.lookup "aws_access_key_id" profile ) - <*> Just Nothing -- No token expiry in config file + <*> ( Sensitive + . SecretKey + . Text.encodeUtf8 + <$> HashMap.lookup "aws_secret_access_key" profile + ) + <*> Just + ( Sensitive + . SessionToken + . Text.encodeUtf8 + <$> HashMap.lookup "aws_session_token" profile + ) + <*> Just Nothing -- No token expiry in config file assumeRoleFromProfile = AssumeRoleFromProfile <$> HashMap.lookup "role_arn" profile @@ -254,6 +303,12 @@ data CredentialSource = Environment | Ec2InstanceMetadata | EcsContainer -- Throws 'MissingFileError' if 'credFile' is missing, or 'InvalidFileError' -- if an error occurs during parsing. -- +-- If @AWS_SHARED_CREDENTIALS_FILE@ is set, it will be used instead of looking +-- for @.aws\/credentials@ in the @HOME@ directory +-- If @AWS_CONFIG_FILE@ is set, it will be used instead of looking for +-- @.aws\/config@ in the @HOME@ directory. +-- If @AWS_PROFILE@ is set, it will be used instead of the default profile +-- -- This looks in in the @HOME@ directory as determined by the -- library. -- @@ -263,11 +318,14 @@ data CredentialSource = Environment | Ec2InstanceMetadata | EcsContainer fromFileEnv :: (MonadIO m, Foldable withAuth) => Env' withAuth -> m Env fromFileEnv env = liftIO $ do - mProfile <- Environment.lookupEnv "AWS_PROFILE" - cred <- configPathRelative "/.aws/credentials" - conf <- configPathRelative "/.aws/config" - - fromFilePath (maybe "default" Text.pack mProfile) cred conf env + profile <- Environment.lookupEnv "AWS_PROFILE" <&> maybe "default" Text.pack + conf <- + Environment.lookupEnv "AWS_CONFIG_File" + >>= maybe (configPathRelative "/.aws/config") pure + cred <- + Environment.lookupEnv "AWS_SHARED_CREDENTIALS_FILE" + >>= maybe (configPathRelative "/.aws/credentials") pure + fromFilePath profile cred conf env configPathRelative :: String -> IO String configPathRelative p = handling_ _IOException err dir