Skip to content

Commit

Permalink
Revert "Make cargohold use amazonka instead of aws (#1157)"
Browse files Browse the repository at this point in the history
This reverts commit e570f2e.
  • Loading branch information
fisx committed Jul 14, 2020
1 parent a061d4b commit a43a215
Show file tree
Hide file tree
Showing 31 changed files with 1,057 additions and 607 deletions.
32 changes: 32 additions & 0 deletions libs/ropes/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ library:
source-dirs: src
dependencies:
- aeson >=0.6
- aws >=0.10.2
- base ==4.*
- exceptions >=0.6
- http-client >=0.5
Expand All @@ -30,3 +31,34 @@ library:
- time >=1.1
- tinylog >=0.10.2
- yaml >=0.8.22
executables:
ropes-aws-auth-test:
main: Main.hs
source-dirs: test/integration-aws-auth
ghc-options:
- -threaded
dependencies:
- base >=4 && <5
- aws
- http-client
- ropes
- time
- tinylog
ropes-aws-test:
main: Main.hs
source-dirs: test/integration-aws
ghc-options:
- -threaded
dependencies:
- base >=4 && <5
- aws
- exceptions
- http-client
- http-client-tls
- resourcet
- ropes
- tasty >=0.2
- tasty-hunit >=0.2
- text >=0.11.3
- transformers
- tinylog
56 changes: 55 additions & 1 deletion libs/ropes/ropes.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: d24f5fff02a52bee31f4c44250bf1b220b95113bb66026289808204f560ad4bd
-- hash: 8e1fc11eb8dfecf6b35bf33c8cd612ed3e50f371781a549f8728286a79acb547

name: ropes
version: 0.4.20
Expand All @@ -19,6 +19,8 @@ build-type: Simple

library
exposed-modules:
Ropes.Aws
Ropes.Aws.Ses
Ropes.Nexmo
Ropes.Twilio
other-modules:
Expand All @@ -29,6 +31,7 @@ library
ghc-options: -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path
build-depends:
aeson >=0.6
, aws >=0.10.2
, base ==4.*
, bytestring >=0.9
, errors >=2.0
Expand All @@ -47,3 +50,54 @@ library
, transformers >=0.3
, yaml >=0.8.22
default-language: Haskell2010

executable ropes-aws-auth-test
main-is: Main.hs
other-modules:
Paths_ropes
hs-source-dirs:
test/integration-aws-auth
default-extensions: AllowAmbiguousTypes BangPatterns ConstraintKinds DataKinds DefaultSignatures DerivingStrategies DeriveFunctor DeriveGeneric DeriveLift DeriveTraversable EmptyCase FlexibleContexts FlexibleInstances FunctionalDependencies GADTs InstanceSigs KindSignatures LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude OverloadedStrings PackageImports PatternSynonyms PolyKinds QuasiQuotes RankNTypes ScopedTypeVariables StandaloneDeriving TemplateHaskell TupleSections TypeApplications TypeFamilies TypeFamilyDependencies TypeOperators UndecidableInstances ViewPatterns
ghc-options: -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path -threaded
build-depends:
aws
, base >=4 && <5
, bytestring >=0.9
, errors >=2.0
, http-client
, imports
, mime-mail >=0.4
, ropes
, semigroups >=0.11
, time
, tinylog
default-language: Haskell2010

executable ropes-aws-test
main-is: Main.hs
other-modules:
Tests.Ropes.Aws.Ses
Paths_ropes
hs-source-dirs:
test/integration-aws
default-extensions: AllowAmbiguousTypes BangPatterns ConstraintKinds DataKinds DefaultSignatures DerivingStrategies DeriveFunctor DeriveGeneric DeriveLift DeriveTraversable EmptyCase FlexibleContexts FlexibleInstances FunctionalDependencies GADTs InstanceSigs KindSignatures LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude OverloadedStrings PackageImports PatternSynonyms PolyKinds QuasiQuotes RankNTypes ScopedTypeVariables StandaloneDeriving TemplateHaskell TupleSections TypeApplications TypeFamilies TypeFamilyDependencies TypeOperators UndecidableInstances ViewPatterns
ghc-options: -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path -threaded
build-depends:
aws
, base >=4 && <5
, bytestring >=0.9
, errors >=2.0
, exceptions
, http-client
, http-client-tls
, imports
, mime-mail >=0.4
, resourcet
, ropes
, semigroups >=0.11
, tasty >=0.2
, tasty-hunit >=0.2
, text >=0.11.3
, tinylog
, transformers
default-language: Haskell2010
216 changes: 216 additions & 0 deletions libs/ropes/src/Ropes/Aws.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2020 Wire Swiss GmbH <[email protected]>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Ropes.Aws
( -- * Env
AccessKeyId (..),
SecretAccessKey (..),
Env,
newEnv,
getConfig,
getCredentials,
getManager,

-- * Sending Requests
sendRequest,

-- * Re-exports
Configuration (..),
Credentials (..),
ServiceConfiguration,
NormalQuery,
Transaction,
ResponseMetadata,
)
where

import Aws (Configuration (..), aws)
import qualified Aws
import Aws.Core
import Control.Error
import Control.Monad.Catch
import Control.Monad.Trans.Resource
import Control.Retry
import Data.Aeson
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as LB
import Data.Text (pack, unpack)
import Data.Text.Encoding (encodeUtf8)
import Data.Time.Clock
import Imports
import Network.HTTP.Client
import System.Logger (Logger)
import qualified System.Logger as Logger

-------------------------------------------------------------------------------
-- Config

newtype AccessKeyId = AccessKeyId
{unKey :: ByteString}
deriving (Eq, Show)

instance FromJSON AccessKeyId where
parseJSON =
withText "Aws.AccessKeyId" $
pure . AccessKeyId . encodeUtf8

newtype SecretAccessKey = SecretAccessKey
{unSecret :: ByteString}
deriving (Eq)

instance Show SecretAccessKey where
show _ = "AWS Secret hidden"

instance FromJSON SecretAccessKey where
parseJSON =
withText "Aws.SecretAccessKey" $
pure . SecretAccessKey . encodeUtf8

data Auth
= PermAuth Configuration
| TempAuth (IORef Configuration)

-- | An environment for executing AWS requests. See 'sendRequest'.
data Env = Env
{ _auth :: !Auth,
-- | Get the HTTP 'Manager' used by an 'Env'ironment.
getManager :: !Manager
}

-- | If credentials are supplied to this function, they are used to create the 'Env'
-- | Otherwise, it tries to discover AWS credentials by calling the underlying
-- | loadCredentialsDefault. If that does not succeed, if fallsback to instance metadata
newEnv :: Logger -> Manager -> Maybe (AccessKeyId, SecretAccessKey) -> IO Env
newEnv lgr mgr ks = do
auth <- case ks of
Just (k, s) -> permAuth =<< makeCredentials (unKey k) (unSecret s)
Nothing -> discover
return $ Env auth mgr
where
permAuth creds = PermAuth <$> newPermConfig lgr creds
tempAuth = TempAuth <$> newTempConfig lgr mgr
discover = loadCredentialsDefault >>= maybe tempAuth permAuth

-- | Get the currently used 'Credentials' from the current
-- 'Configuration' used by the given 'Env'.
getCredentials :: Env -> IO Credentials
getCredentials = fmap credentials . getConfig

-- | Get the currently used 'Configuration' of the given 'Env'.
getConfig :: Env -> IO Configuration
getConfig (Env (PermAuth c) _) = return c
getConfig (Env (TempAuth r) _) = readIORef r

-------------------------------------------------------------------------------
-- Sending Requests

sendRequest ::
(MonadIO m, Transaction r a) =>
Env ->
ServiceConfiguration r NormalQuery ->
r ->
ResourceT m (ResponseMetadata a, a)
sendRequest env scfg req = transResourceT liftIO $ do
cfg <- liftIO $ getConfig env
rsp <- aws cfg scfg (getManager env) req
a <- readResponseIO rsp
return (responseMetadata rsp, a)

-------------------------------------------------------------------------------
-- Internals

newPermConfig :: Logger -> Credentials -> IO Configuration
newPermConfig lgr creds = return $ Configuration Timestamp creds (awsLog lgr) Nothing

newTempConfig :: Logger -> Manager -> IO (IORef Configuration)
newTempConfig lgr mgr = do
Just (c, x) <- retrying x5 (const shouldRetry) (const tryMetadata)
r <- newIORef c
schedule r x
return r
where
schedule r x = for_ x $ \expires -> do
now <- getCurrentTime
let secs = round (expires `diffUTCTime` now) - (60 * 30)
msecs = secs * 1000 * 1000
forkIO $ threadDelay msecs >> refresh r
refresh r = do
Just (c, x) <- retrying xInf (const shouldRetry) (const tryMetadata)
writeIORef r c
schedule r x
shouldRetry = return . isNothing
tryMetadata :: IO (Maybe (Configuration, Maybe UTCTime))
tryMetadata = do
Logger.info lgr $ Logger.msg ("Fetching instance metadata" :: String)
r <- runExceptT . syncIO $ fromMetadata mgr
case r of
Left e -> Logger.err lgr (Logger.msg $ show e) >> return Nothing
Right a -> do
keys <- newIORef [] -- V4 signing keys used by the 'aws' package
let (c, x) = mkCreds a keys
cfg = Configuration Timestamp c (awsLog lgr) Nothing
return $ Just (cfg, x)
mkCreds (TempCredentials (AccessKeyId k) (SecretAccessKey s) (SessionToken t) expiry) keys =
(Credentials k s keys (Just t), expiry)
x5 = constantDelay 1000000 <> limitRetries 5 -- every second
xInf = constantDelay (60 * 1000000) -- every minute

awsLog :: Logger -> Aws.LogLevel -> Text -> IO ()
awsLog lgr l m = Logger.log lgr (level l) (Logger.msg m)
where
level Aws.Debug = Logger.Debug
level Aws.Info = Logger.Info
level Aws.Warning = Logger.Warn
level Aws.Error = Logger.Error

data TempCredentials = TempCredentials
{ _tmpKey :: AccessKeyId,
_tmpSecret :: SecretAccessKey,
_tmpToken :: SessionToken,
_tmpExpiry :: Maybe UTCTime
}

newtype SessionToken = SessionToken ByteString

newtype MetadataException = MetadataException Text
deriving (Eq, Typeable)

instance Exception MetadataException

instance Show MetadataException where
show (MetadataException e) = "Ropes.Aws.MetadataException: " ++ unpack e

fromMetadata :: Manager -> IO TempCredentials
fromMetadata mgr = do
req <- parseUrlThrow $ C.unpack url
role <- C.takeWhile (/= '\n') . LB.toStrict . responseBody <$> httpLbs req mgr
cred <- eitherDecode . responseBody <$> httpLbs (req {path = (path req) <> "/" <> role}) mgr
either (throwM . MetadataException . ("Failed to parse: " <>) . pack) return cred
where
url :: ByteString
url = "http://instance-data/latest/meta-data/iam/security-credentials/"

instance FromJSON TempCredentials where
parseJSON = withObject "credentials" $ \o ->
TempCredentials
<$> (AccessKeyId . encodeUtf8 <$> o .: "AccessKeyId")
<*> (SecretAccessKey . encodeUtf8 <$> o .: "SecretAccessKey")
<*> (SessionToken . encodeUtf8 <$> o .: "Token")
<*> o .:? "Expiration"
34 changes: 34 additions & 0 deletions libs/ropes/src/Ropes/Aws/Ses.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2020 Wire Swiss GmbH <[email protected]>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Ropes.Aws.Ses where

import Aws.Ses
import Data.ByteString.Lazy (toStrict)
import Imports
import Network.Mail.Mime

-- | Convenience function for constructing a 'SendRawEmail' command,
-- which involves extracting/duplicating some data from the MIME 'Mail'.
sendRawEmail :: MonadIO m => Mail -> m SendRawEmail
sendRawEmail m = do
msg <- liftIO $ toStrict <$> renderMail' m
return $
SendRawEmail
(map addressEmail (mailTo m))
(RawMessage msg)
(Just . Sender . addressEmail $ mailFrom m)
Loading

0 comments on commit a43a215

Please sign in to comment.