packages feed

wreq-0.4.0.0: tests/AWS/IAM.hs

{-# LANGUAGE OverloadedLists, OverloadedStrings #-}
module AWS.IAM (tests) where

import AWS.Aeson
import Control.Concurrent (threadDelay)
import Control.Lens hiding ((.=))
import Data.Aeson.Encode (encode)
import Data.Aeson.Lens (key, _String, values)
import Data.IORef (IORef, readIORef, writeIORef)
import Data.Text as T (Text, pack, unpack, split)
import Data.Text.Lazy as LT (toStrict)
import Data.Text.Lazy.Encoding as E (decodeUtf8)
import Network.Wreq
import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (testCase)
import Test.HUnit (assertBool)

tests :: String -> String -> Options -> IORef String -> Test
tests prefix region baseopts iamTestState = testGroup "iam" [
    testCase "listUsers"     $ listUsers prefix region baseopts
  , testCase "createRole"    $ createRole prefix region baseopts iamTestState
  , testCase "listRoles"     $ listRoles prefix region baseopts
  , testCase "putRolePolicy" $ putRolePolicy prefix region baseopts
  , testCase "stsAssumeRole" $ stsAssumeRole prefix region baseopts iamTestState
  , testCase "deleteRolePolicy" $ deleteRolePolicy prefix region baseopts
  , testCase "deleteRole"    $ deleteRole prefix region baseopts
  ]

listUsers :: String -> String -> Options -> IO ()
listUsers _prefix region baseopts = do
  let opts = baseopts
             & param  "Action"  .~ ["ListUsers"]
             & param  "Version" .~ ["2010-05-08"]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "listUsers 200" $ r ^. responseStatus . statusCode == 200
  assertBool "listUsers OK" $ r ^. responseStatus . statusMessage == "OK"

createRole :: String -> String -> Options -> IORef String -> IO ()
createRole prefix region baseopts iamTestState = do
  let opts = baseopts
             & param  "Action"  .~ ["CreateRole"]
             & param  "Version" .~ ["2010-05-08"]
             & param  "RoleName" .~ [T.pack $ prefix ++ roleName]
             & param  "AssumeRolePolicyDocument" .~ [rolePolicyDoc]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "createRole 200" $ r ^. responseStatus . statusCode == 200
  assertBool "createRole OK" $ r ^. responseStatus . statusMessage == "OK"
  let [arn] = r ^.. responseBody . key "CreateRoleResponse"
                                 . key "CreateRoleResult"
                                 . key "Role"
                                 . key "Arn" . _String
  writeIORef iamTestState $ T.unpack arn

putRolePolicy :: String -> String -> Options -> IO ()
putRolePolicy prefix region baseopts = do
  let opts = baseopts
             & param  "Action"  .~ ["PutRolePolicy"]
             & param  "Version" .~ ["2010-05-08"]
             & param  "RoleName" .~ [T.pack $ prefix ++ roleName]
             & param  "PolicyName" .~ [testPolicyName]
             & param  "PolicyDocument" .~ [policyDoc]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "putRolePolicy 200" $ r ^. responseStatus . statusCode == 200
  assertBool "putRolePolicy OK" $ r ^. responseStatus . statusMessage == "OK"
  threadDelay $ 30*1000*1000 -- 30 sleep, allow change to propagate to region

deleteRolePolicy :: String -> String -> Options -> IO ()
deleteRolePolicy prefix region baseopts = do
  let opts = baseopts
             & param  "Action"  .~ ["DeleteRolePolicy"]
             & param  "Version" .~ ["2010-05-08"]
             & param  "RoleName" .~ [T.pack $ prefix ++ roleName]
             & param  "PolicyName" .~ [testPolicyName]
             & param  "PolicyDocument" .~ [policyDoc]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "deleteRolePolicy 200" $ r ^. responseStatus . statusCode == 200
  assertBool "deleteRolePolicy OK" $ r ^. responseStatus . statusMessage == "OK"

deleteRole :: String -> String -> Options -> IO ()
deleteRole prefix region baseopts = do
  let opts = baseopts
             & param  "Action"  .~ ["DeleteRole"]
             & param  "Version" .~ ["2010-05-08"]
             & param  "RoleName" .~ [T.pack $ prefix ++ roleName]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "deleteRole 200" $ r ^. responseStatus . statusCode == 200
  assertBool "deleteRole OK" $ r ^. responseStatus . statusMessage == "OK"

listRoles :: String -> String -> Options -> IO ()
listRoles prefix region baseopts = do
  let opts = baseopts
             & param  "Action"  .~ ["ListRoles"]
             & param  "Version" .~ ["2010-05-08"]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (iamUrl region)
  assertBool "listRoles 200" $ r ^. responseStatus . statusCode == 200
  assertBool "listRoles OK" $ r ^. responseStatus . statusMessage == "OK"
  let arns = r ^.. responseBody . key "ListRolesResponse" .
                                  key "ListRolesResult" .
                                  key "Roles" .
                                  values .
                                  key "Arn" . _String
  -- arns are of form: "arn:aws:iam::<acct>:role/ec2-role"
  let arns' = map (T.unpack . last . T.split (=='/')) arns
  assertBool "listRoles contains test role" $
    elem (prefix ++ roleName) arns'

-- Security Token Service (STS)
stsAssumeRole :: String -> String -> Options -> IORef String -> IO ()
stsAssumeRole _prefix region baseopts iamTestState = do
  arn <- readIORef iamTestState
  let opts = baseopts
             & param  "Action"  .~ ["AssumeRole"]
             & param  "Version" .~ ["2011-06-15"]
             & param  "RoleArn" .~ [T.pack arn]
             & param  "ExternalId" .~ [externalId]
             & param  "RoleSessionName" .~ ["Bob"]
             & header "Accept"  .~ ["application/json"]
  r <- getWith opts (stsUrl region) -- STS call (part of IAM service family)
  assertBool "stsAssumeRole 200" $ r ^. responseStatus . statusCode == 200
  assertBool "stsAssumeRole OK" $ r ^. responseStatus . statusMessage == "OK"

iamUrl :: String -> String
iamUrl _ =
  "https://iam.amazonaws.com/" -- IAM is not region specific

stsUrl :: String -> String
stsUrl _region =
  "https://sts.amazonaws.com/" -- keep from needing to enable STS in regions
  -- To test region specific behavior, uncomment the line below
  --   "https://sts." ++ _region ++ ".amazonaws.com/" -- region specific
  -- Note: to access AWS STS in any region other than us-east-1, or the default
  --   region (sts.amazonaws.com), STS needs to be enabled in the
  --   AWS Management Console under
  --   Account Settings > Security Token Service Region
  --   If you forget, the AssumeRole call will return a 403 error with:
  --     "STS is not activated in this region for account:<acct>.
  --      Your account administrator can activate STS in this region using
  --      the IAM Console."

roleName :: String
roleName = "test"

testPolicyName :: T.Text
testPolicyName = "testPolicy"

-- Note that ExternalId is a concept used for cross account use cases
-- with 3rd parties. But the check works for same-account as well, which
-- makes it more convenient to test.
-- For more, see:
-- http://docs.aws.amazon.com/STS/latest/UsingSTS/sts-delegating-externalid.html
externalId :: T.Text
externalId = "someExternalId"

rolePolicyDoc :: T.Text
rolePolicyDoc = LT.toStrict . E.decodeUtf8 . encode $
  object [
      "Version" .= "2012-10-17",
      "Statement" .= [
        object [
          "Effect" .= "Allow",
          "Action" .= "sts:AssumeRole",
          "Principal" .= object ["AWS" .= "*"],
          "Condition" .= object ["StringEquals" .=
                                 object ["sts:ExternalId" .= string externalId]]
        ]
      ]
  ]

policyDoc :: T.Text
policyDoc = LT.toStrict . E.decodeUtf8 . encode $
  object [
      "Version" .= "2012-10-17",
      "Statement" .= [
        object [
          "Effect" .= "Allow",
          "Action" .= ["*"],
          "Resource" .= ["*"]
        ]
      ]
  ]