aws-configuration-tools-0.1.0.0: src/Configuration/Utils/Aws/Credentials.hs
-- Copyright (c) 2013-2015 PivotCloud, Inc.
--
-- Configuration.Utils.Aws.Credentials
--
-- Please feel free to contact us at licensing@pivotmail.com with any
-- contributions, additions, or other feedback; we would love to hear from
-- you.
--
-- Licensed under the Apache License, Version 2.0 (the "License"); you may
-- not use this file except in compliance with the License. You may obtain a
-- copy of the License at http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UnicodeSyntax #-}
-- |
-- Module: Configuration.Utils.Aws.Credentials
-- Copyright: Copyright © 2013-2015 PivotCloud, Inc.
-- License: Apache-2.0
-- Maintainer: Jon Sterling <jsterling@alephcloud.com>
-- Stability: experimental
--
module Configuration.Utils.Aws.Credentials
( -- * Configuration Types
CredentialConfigKey(..)
, credentialConfigKeyId
, credentialConfigKeySecret
, validateCredentialConfigKey
, CredentialConfigFile(..)
, credentialConfigFileName
, credentialConfigKeyName
, validateCredentialConfigFile
, CredentialConfig(..)
, credentialConfigKey
, credentialConfigFile
, credentialConfigEnvironment
, credentialConfigInstanceMetadata
, defaultCredentialConfig
, pCredentialConfig
, pCredentialConfig_
, validateCredentialConfig
-- * Load Credentials
, credentialsFromConfig
, LoadCredentialsException(..)
) where
import Aws
import Configuration.Utils
import Configuration.Utils.Internal
import Configuration.Utils.Validation
import Control.Exception
import Control.Monad
import Control.Monad.Error.Class
import Control.Monad.IO.Class
import Control.Monad.Unicode
import Data.Maybe
import Data.Monoid.Unicode
import Data.Traversable
import Data.Typeable
import qualified Data.ByteString.Char8 as B8
import qualified Data.Text as T
import Prelude.Unicode
-- -------------------------------------------------------------------------- --
-- Credential Configuration
data CredentialConfigKey
= CredentialConfigKey
{ _credentialConfigKeyId ∷ !B8.ByteString
, _credentialConfigKeySecret ∷ !B8.ByteString
} deriving (Show, Read, Eq, Ord, Typeable)
credentialConfigKeyId ∷ Lens' CredentialConfigKey B8.ByteString
credentialConfigKeyId = lens _credentialConfigKeyId $ \a b → a { _credentialConfigKeyId = b }
credentialConfigKeySecret ∷ Lens' CredentialConfigKey B8.ByteString
credentialConfigKeySecret = lens _credentialConfigKeySecret $ \a b → a { _credentialConfigKeySecret = b }
validateCredentialConfigKey ∷ ConfigValidation CredentialConfigKey λ
validateCredentialConfigKey CredentialConfigKey{..} = do
validateNonEmpty "access_key_id" _credentialConfigKeyId
validateNonEmpty "access_key_secret" _credentialConfigKeySecret
instance ToJSON CredentialConfigKey where
toJSON CredentialConfigKey{..} = object
[ "access_key_id" .= B8.unpack _credentialConfigKeyId
, "access_key_secret" .= B8.unpack _credentialConfigKeySecret
]
instance FromJSON CredentialConfigKey where
parseJSON =
withObject "CredentialConfigKey" $ \o →
CredentialConfigKey
<$> (B8.pack <$> o .: "access_key_id")
<*> (B8.pack <$> o .: "access_key_secret")
data CredentialConfigFile
= CredentialConfigFile
{ _credentialConfigFileName ∷ !FilePath
, _credentialConfigKeyName ∷ !T.Text
-- ^ default value is @"default"@
} deriving (Show, Read, Eq, Ord, Typeable)
credentialConfigFileName ∷ Lens' CredentialConfigFile FilePath
credentialConfigFileName = lens _credentialConfigFileName $ \a b → a { _credentialConfigFileName = b }
credentialConfigKeyName ∷ Lens' CredentialConfigFile T.Text
credentialConfigKeyName = lens _credentialConfigKeyName $ \a b → a { _credentialConfigKeyName = b }
validateCredentialConfigFile ∷ ConfigValidation CredentialConfigFile λ
validateCredentialConfigFile CredentialConfigFile{..} =
validateFileReadable "file_name" _credentialConfigFileName
instance ToJSON CredentialConfigFile where
toJSON CredentialConfigFile{..} = object
[ "file_name" .= _credentialConfigFileName
, "key_name" .= _credentialConfigKeyName
]
instance FromJSON CredentialConfigFile where
parseJSON =
withObject "CredentialConfigFile" $ \o →
CredentialConfigFile
<$> o .: "file_name"
<*> o .:? "key_name" .!= "default"
-- | When parsed from configuration files or the command line the settings take
-- the following precedence:
--
-- Explicit credential configuration gets precedence over credential file
-- configuration which has precedence over loading the credentials from the
-- environment over loading the credentials from the instance meta data.
--
-- Any setting on the command line always has precedence over any setting in a
-- configuration file.
--
-- If the configured file does not exist an parse error is triggered. Set this
-- value of '_credentialConfigFile' to 'Nothing' if you don't want to suppress
-- this error.
--
data CredentialConfig
= CredentialConfig
{ _credentialConfigKey ∷ !(Maybe CredentialConfigKey)
, _credentialConfigFile ∷ !(Maybe CredentialConfigFile)
, _credentialConfigEnvironment ∷ !Bool
, _credentialConfigInstanceMetadata ∷ !Bool
} deriving (Show, Eq, Ord)
credentialConfigKey ∷ Lens' CredentialConfig (Maybe CredentialConfigKey)
credentialConfigKey = lens _credentialConfigKey $ \a b → a { _credentialConfigKey = b}
credentialConfigFile ∷ Lens' CredentialConfig (Maybe CredentialConfigFile)
credentialConfigFile = lens _credentialConfigFile $ \a b → a { _credentialConfigFile = b}
credentialConfigEnvironment ∷ Lens' CredentialConfig Bool
credentialConfigEnvironment = lens _credentialConfigEnvironment $ \a b → a { _credentialConfigEnvironment = b}
credentialConfigInstanceMetadata ∷ Lens' CredentialConfig Bool
credentialConfigInstanceMetadata = lens _credentialConfigInstanceMetadata $ \a b → a { _credentialConfigInstanceMetadata = b}
defaultCredentialConfig ∷ CredentialConfig
defaultCredentialConfig = CredentialConfig
{ _credentialConfigKey = Nothing
, _credentialConfigFile = Nothing
, _credentialConfigEnvironment = False
, _credentialConfigInstanceMetadata = False
}
validateCredentialConfig ∷ ConfigValidation CredentialConfig λ
validateCredentialConfig CredentialConfig{..} = do
void $ for _credentialConfigKey validateCredentialConfigKey
void $ for _credentialConfigFile validateCredentialConfigFile
unless (or enabledList) $
throwError "at least one credentials configuration option must be configured"
where
enabledList =
[ isJust _credentialConfigKey
, isJust _credentialConfigFile
, _credentialConfigEnvironment
, _credentialConfigInstanceMetadata
]
instance ToJSON CredentialConfig where
toJSON CredentialConfig{..} = object
[ "key" .= _credentialConfigKey
, "file" .= _credentialConfigFile
, "environment" .= _credentialConfigEnvironment
, "metadata" .= _credentialConfigInstanceMetadata
]
instance FromJSON (CredentialConfig → CredentialConfig) where
parseJSON =
withObject "CredentialConfig" $ \o → id
<$< credentialConfigKey ..: "key" % o
<*< credentialConfigFile ..: "file" % o
<*< credentialConfigEnvironment ..: "environment" % o
<*< credentialConfigInstanceMetadata ..: "metadata" % o
-- | A command-line argument parser for 'CredentialConfig'. The @prefix@
-- argument will be prepended directly onto the argument names.
--
pCredentialConfig
∷ String -- ^ prefix
→ MParser CredentialConfig
pCredentialConfig prefix = id
<$< credentialConfigKey .:: fmap Just % pCredentialConfigKey
<*< credentialConfigFile .:: fmap Just % pCredentialConfigFile
<*< credentialConfigEnvironment .:: boolOption
% long (prefix ⊕ "credentials-from-environment")
⊕ help "load AWS access credentials from environment variables (AWS_ACCESS_KEY_ID and AWS_ACCESS_KEY_SECRET)"
<*< credentialConfigInstanceMetadata .:: boolOption
% long (prefix ⊕ "credentials-from-metadata")
⊕ help "load AWS access credentials from the instance metadata"
where
pKeyId =
B8.pack <$> strOption
% long (prefix ⊕ "credentials-key-id")
⊕ metavar "AWS_ACCESS_KEY_ID"
⊕ help "Id of the AWS access key"
pKeySecret =
B8.pack <$> strOption
% long (prefix ⊕ "credentials-key-secret")
⊕ metavar "AWS_ACCESS_KEY_SECRET"
⊕ help "Secret of the AWS access key"
pCredentialConfigKey =
CredentialConfigKey <$> pKeyId <*> pKeySecret
pKeyName =
T.pack <$> strOption
% long (prefix ⊕ "credentials-key-name")
⊕ metavar "STRING"
⊕ help "the name of the access key in the access key file"
⊕ value (T.unpack credentialsDefaultKey)
pFileName =
fileOption
% long (prefix ⊕ "credentials-key-file")
⊕ help "the name of the file with access keys for the AWS API"
pCredentialConfigFile =
CredentialConfigFile <$> pFileName <*> pKeyName
-- | This is 'pCredentialConfig' with an empty prefix.
--
pCredentialConfig_ ∷ MParser CredentialConfig
pCredentialConfig_ = pCredentialConfig ""
data LoadCredentialsException
= LoadCredentialsFromFileFailed CredentialConfigFile
| LoadCredentialsFromEnvironementFailed
| LoadCredentialsFromInstanceMetadataFailed
| NoCredentialLoadingMethodSpecified
deriving (Eq, Show, Typeable)
instance Exception LoadCredentialsException
-- | Load Credentials according to the credential configuration
--
-- Credentials are loaded with a precedence according to the order of
-- the record fields of 'CredentialConfig':
--
-- 1. '_credentialConfigKey', loads the explicitely specified credentials,
--
-- 2. '_credentialConfigFile', loads credentials with the given key name
-- from the given file,
--
-- 3. '_credentialConfigEnvironment', loads credentials from the
-- environment varialbes @AWS_ACCESS_KEY_ID@ and @AWS_ACCESS_KEY_SECRET@), and
--
-- 3. '_credentialConfigInstanceMetadata', load the credentials from the
-- EC2 instance metadata.
--
-- It is an error if a particular method is specified in the configuration but
-- loading the credentials with that methods fails.
--
credentialsFromConfig
∷ (MonadError LoadCredentialsException m, MonadIO m)
⇒ CredentialConfig
→ m Credentials
credentialsFromConfig CredentialConfig{..}
| Just CredentialConfigKey{..} ← _credentialConfigKey =
liftIO $ makeCredentials _credentialConfigKeyId _credentialConfigKeySecret
| Just ccf@(CredentialConfigFile file awsKeyName) ← _credentialConfigFile =
liftIO (loadCredentialsFromFile file awsKeyName) ≫= \case
Nothing → throwError $ LoadCredentialsFromFileFailed ccf
Just c → return c
| _credentialConfigEnvironment =
liftIO loadCredentialsFromEnv ≫= \case
Nothing → throwError LoadCredentialsFromEnvironementFailed
Just c → return c
| _credentialConfigInstanceMetadata =
liftIO loadCredentialsFromInstanceMetadata ≫= \case
Nothing → throwError LoadCredentialsFromInstanceMetadataFailed
Just c → return c
| otherwise = throwError NoCredentialLoadingMethodSpecified