Add support for AssumeRole STS API (#188)
This change adds support for requesting temporary object storage credentials using the STS API. Some breaking changes are introduced to enable this support: - `Credentials` type has been removed. Use the `CredentialValue` type instead. Corresponding to this the type signature for `setCreds` has changed, though the functionality is the same. - The type alias `Provider` has been renamed to `CredentialLoader` to avoid naming confusion.
This commit is contained in:
parent
7ae8a8179d
commit
fa62ed599a
14
CHANGELOG.md
14
CHANGELOG.md
@ -3,8 +3,18 @@ Changelog
|
|||||||
|
|
||||||
## Version 1.7.0 -- Unreleased
|
## Version 1.7.0 -- Unreleased
|
||||||
|
|
||||||
* Fix data type `EventMessage` to not export partial fields
|
* Fix data type `EventMessage` to not export partial fields (#179)
|
||||||
* Bump up min bound on time dep and fix deprecation warnings.
|
* Bump up min bound on time dep and fix deprecation warnings (#181)
|
||||||
|
* Add `dev` flag to cabal for building with warnings as errors (#182)
|
||||||
|
* Fix AWS region map (#185)
|
||||||
|
* Fix XML generator tests (#187)
|
||||||
|
* Add support for STS Assume Role API (#188)
|
||||||
|
|
||||||
|
### Breaking changes in 1.7.0
|
||||||
|
|
||||||
|
* `Credentials` type has been removed. Use `CredentialValue` instead.
|
||||||
|
* `Provider` type has been replaced with `CredentialLoader`.
|
||||||
|
* `EventMessage` data type is updated.
|
||||||
|
|
||||||
## Version 1.6.0
|
## Version 1.6.0
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# MinIO Haskell Client SDK for Amazon S3 Compatible Cloud Storage [](https://travis-ci.org/minio/minio-hs)[](https://hackage.haskell.org/package/minio-hs)[](https://slack.min.io)
|
# MinIO Haskell Client SDK for Amazon S3 Compatible Cloud Storage [](https://github.com/minio/minio-hs/actions/workflows/ci.yml)[](https://hackage.haskell.org/package/minio-hs)[](https://slack.min.io)
|
||||||
|
|
||||||
The MinIO Haskell Client SDK provides simple APIs to access [MinIO](https://min.io) and any Amazon S3 compatible object storage.
|
The MinIO Haskell Client SDK provides simple APIs to access [MinIO](https://min.io) and any Amazon S3 compatible object storage.
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2022 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -15,19 +15,33 @@
|
|||||||
--
|
--
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
import Network.Minio.Credentials
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Network.Minio
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
res <-
|
-- Use play credentials for example.
|
||||||
retrieveCredentials
|
let assumeRole =
|
||||||
$ STSAssumeRole
|
STSAssumeRole
|
||||||
"https://play.min.io"
|
( CredentialValue
|
||||||
( CredentialValue
|
"Q3AM3UQ867SPQQA43P2F"
|
||||||
"Q3AM3UQ867SPQQA43P2F"
|
"zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
||||||
"zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
Nothing
|
||||||
Nothing
|
)
|
||||||
)
|
$ defaultSTSAssumeRoleOptions
|
||||||
$ defaultSTSAssumeRoleOptions {saroLocation = Just "us-east-1"}
|
{ saroLocation = Just "us-east-1",
|
||||||
|
saroEndpoint = Just "https://play.min.io:9000"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Retrieve temporary credentials and print them.
|
||||||
|
cv <- requestSTSCredential assumeRole
|
||||||
|
print $ "Temporary credentials" ++ show (credentialValueText $ fst cv)
|
||||||
|
print $ "Expiry" ++ show (snd cv)
|
||||||
|
|
||||||
|
-- Configure 'ConnectInfo' to request temporary credentials on demand.
|
||||||
|
ci <- setSTSCredential assumeRole "https://play.min.io"
|
||||||
|
res <- runMinio ci $ do
|
||||||
|
buckets <- listBuckets
|
||||||
|
liftIO $ print $ "Top 5 buckets: " ++ show (take 5 buckets)
|
||||||
print res
|
print res
|
||||||
|
|||||||
@ -77,8 +77,6 @@ common base-settings
|
|||||||
, RankNTypes
|
, RankNTypes
|
||||||
, ScopedTypeVariables
|
, ScopedTypeVariables
|
||||||
, TupleSections
|
, TupleSections
|
||||||
, TypeFamilies
|
|
||||||
|
|
||||||
|
|
||||||
other-modules: Lib.Prelude
|
other-modules: Lib.Prelude
|
||||||
, Network.Minio.API
|
, Network.Minio.API
|
||||||
@ -97,7 +95,11 @@ common base-settings
|
|||||||
, Network.Minio.Utils
|
, Network.Minio.Utils
|
||||||
, Network.Minio.XmlGenerator
|
, Network.Minio.XmlGenerator
|
||||||
, Network.Minio.XmlParser
|
, Network.Minio.XmlParser
|
||||||
|
, Network.Minio.XmlCommon
|
||||||
, Network.Minio.JsonParser
|
, Network.Minio.JsonParser
|
||||||
|
, Network.Minio.Credentials.Types
|
||||||
|
, Network.Minio.Credentials.AssumeRole
|
||||||
|
, Network.Minio.Credentials
|
||||||
|
|
||||||
mixins: base hiding (Prelude)
|
mixins: base hiding (Prelude)
|
||||||
, relude (Relude as Prelude)
|
, relude (Relude as Prelude)
|
||||||
@ -142,7 +144,6 @@ library
|
|||||||
exposed-modules: Network.Minio
|
exposed-modules: Network.Minio
|
||||||
, Network.Minio.AdminAPI
|
, Network.Minio.AdminAPI
|
||||||
, Network.Minio.S3API
|
, Network.Minio.S3API
|
||||||
, Network.Minio.Credentials
|
|
||||||
|
|
||||||
Flag live-test
|
Flag live-test
|
||||||
Description: Build the test suite that runs against a live MinIO server
|
Description: Build the test suite that runs against a live MinIO server
|
||||||
@ -164,6 +165,7 @@ test-suite minio-hs-live-server-test
|
|||||||
, Network.Minio.Utils.Test
|
, Network.Minio.Utils.Test
|
||||||
, Network.Minio.XmlGenerator.Test
|
, Network.Minio.XmlGenerator.Test
|
||||||
, Network.Minio.XmlParser.Test
|
, Network.Minio.XmlParser.Test
|
||||||
|
, Network.Minio.Credentials
|
||||||
build-depends: minio-hs
|
build-depends: minio-hs
|
||||||
, raw-strings-qq
|
, raw-strings-qq
|
||||||
, tasty
|
, tasty
|
||||||
@ -197,6 +199,7 @@ test-suite minio-hs-test
|
|||||||
, Network.Minio.Utils.Test
|
, Network.Minio.Utils.Test
|
||||||
, Network.Minio.XmlGenerator.Test
|
, Network.Minio.XmlGenerator.Test
|
||||||
, Network.Minio.XmlParser.Test
|
, Network.Minio.XmlParser.Test
|
||||||
|
, Network.Minio.Credentials
|
||||||
|
|
||||||
Flag examples
|
Flag examples
|
||||||
Description: Build the examples
|
Description: Build the examples
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- Module: Network.Minio
|
-- Module: Network.Minio
|
||||||
-- Copyright: (c) 2017-2019 MinIO Dev Team
|
-- Copyright: (c) 2017-2023 MinIO Dev Team
|
||||||
-- License: Apache 2.0
|
-- License: Apache 2.0
|
||||||
-- Maintainer: MinIO Dev Team <dev@min.io>
|
-- Maintainer: MinIO Dev Team <dev@min.io>
|
||||||
--
|
--
|
||||||
@ -24,13 +24,17 @@
|
|||||||
-- storage servers like MinIO.
|
-- storage servers like MinIO.
|
||||||
module Network.Minio
|
module Network.Minio
|
||||||
( -- * Credentials
|
( -- * Credentials
|
||||||
Credentials (..),
|
CredentialValue (..),
|
||||||
|
credentialValueText,
|
||||||
|
AccessKey (..),
|
||||||
|
SecretKey (..),
|
||||||
|
SessionToken (..),
|
||||||
|
|
||||||
-- ** Credential providers
|
-- ** Credential Loaders
|
||||||
|
|
||||||
-- | Run actions that retrieve 'Credentials' from the environment or
|
-- | Run actions that retrieve 'CredentialValue's from the environment or
|
||||||
-- files or other custom sources.
|
-- files or other custom sources.
|
||||||
Provider,
|
CredentialLoader,
|
||||||
fromAWSConfigFile,
|
fromAWSConfigFile,
|
||||||
fromAWSEnv,
|
fromAWSEnv,
|
||||||
fromMinioEnv,
|
fromMinioEnv,
|
||||||
@ -54,6 +58,15 @@ module Network.Minio
|
|||||||
awsCI,
|
awsCI,
|
||||||
gcsCI,
|
gcsCI,
|
||||||
|
|
||||||
|
-- ** STS Credential types
|
||||||
|
STSAssumeRole (..),
|
||||||
|
STSAssumeRoleOptions (..),
|
||||||
|
defaultSTSAssumeRoleOptions,
|
||||||
|
requestSTSCredential,
|
||||||
|
setSTSCredential,
|
||||||
|
ExpiryTime (..),
|
||||||
|
STSCredentialProvider,
|
||||||
|
|
||||||
-- * Minio Monad
|
-- * Minio Monad
|
||||||
|
|
||||||
----------------
|
----------------
|
||||||
@ -225,14 +238,15 @@ This module exports the high-level MinIO API for object storage.
|
|||||||
import qualified Data.Conduit as C
|
import qualified Data.Conduit as C
|
||||||
import qualified Data.Conduit.Binary as CB
|
import qualified Data.Conduit.Binary as CB
|
||||||
import qualified Data.Conduit.Combinators as CC
|
import qualified Data.Conduit.Combinators as CC
|
||||||
|
import Network.Minio.API
|
||||||
import Network.Minio.CopyObject
|
import Network.Minio.CopyObject
|
||||||
|
import Network.Minio.Credentials
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Errors
|
import Network.Minio.Errors
|
||||||
import Network.Minio.ListOps
|
import Network.Minio.ListOps
|
||||||
import Network.Minio.PutObject
|
import Network.Minio.PutObject
|
||||||
import Network.Minio.S3API
|
import Network.Minio.S3API
|
||||||
import Network.Minio.SelectAPI
|
import Network.Minio.SelectAPI
|
||||||
import Network.Minio.Utils
|
|
||||||
|
|
||||||
-- | Lists buckets.
|
-- | Lists buckets.
|
||||||
listBuckets :: Minio [BucketInfo]
|
listBuckets :: Minio [BucketInfo]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -26,6 +26,7 @@ module Network.Minio.API
|
|||||||
checkBucketNameValidity,
|
checkBucketNameValidity,
|
||||||
isValidObjectName,
|
isValidObjectName,
|
||||||
checkObjectNameValidity,
|
checkObjectNameValidity,
|
||||||
|
requestSTSCredential,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -34,7 +35,6 @@ import Control.Retry
|
|||||||
limitRetriesByCumulativeDelay,
|
limitRetriesByCumulativeDelay,
|
||||||
retrying,
|
retrying,
|
||||||
)
|
)
|
||||||
import qualified Data.ByteArray as BA
|
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Data.Char as C
|
import qualified Data.Char as C
|
||||||
import qualified Data.Conduit as C
|
import qualified Data.Conduit as C
|
||||||
@ -42,6 +42,7 @@ import qualified Data.HashMap.Strict as H
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Time.Clock as Time
|
import qualified Data.Time.Clock as Time
|
||||||
import Lib.Prelude
|
import Lib.Prelude
|
||||||
|
import Network.HTTP.Client (defaultManagerSettings)
|
||||||
import qualified Network.HTTP.Client as NClient
|
import qualified Network.HTTP.Client as NClient
|
||||||
import Network.HTTP.Conduit (Response)
|
import Network.HTTP.Conduit (Response)
|
||||||
import qualified Network.HTTP.Conduit as NC
|
import qualified Network.HTTP.Conduit as NC
|
||||||
@ -49,6 +50,7 @@ import Network.HTTP.Types (simpleQueryToQuery)
|
|||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
import Network.HTTP.Types.Header (hHost)
|
import Network.HTTP.Types.Header (hHost)
|
||||||
import Network.Minio.APICommon
|
import Network.Minio.APICommon
|
||||||
|
import Network.Minio.Credentials
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Errors
|
import Network.Minio.Errors
|
||||||
import Network.Minio.Sign.V4
|
import Network.Minio.Sign.V4
|
||||||
@ -145,6 +147,20 @@ getHostPathRegion ri = do
|
|||||||
else return pathStyle
|
else return pathStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- | requestSTSCredential requests temporary credentials using the Security Token
|
||||||
|
-- Service API. The returned credential will include a populated 'SessionToken'
|
||||||
|
-- and an 'ExpiryTime'.
|
||||||
|
requestSTSCredential :: STSCredentialProvider p => p -> IO (CredentialValue, ExpiryTime)
|
||||||
|
requestSTSCredential p = do
|
||||||
|
endpoint <- maybe (throwIO $ MErrValidation MErrVSTSEndpointNotFound) return $ getSTSEndpoint p
|
||||||
|
let endPt = NC.parseRequest_ $ toString endpoint
|
||||||
|
settings
|
||||||
|
| NC.secure endPt = NC.tlsManagerSettings
|
||||||
|
| otherwise = defaultManagerSettings
|
||||||
|
|
||||||
|
mgr <- NC.newManager settings
|
||||||
|
liftIO $ retrieveSTSCredentials p ("", 0, False) mgr
|
||||||
|
|
||||||
buildRequest :: S3ReqInfo -> Minio NC.Request
|
buildRequest :: S3ReqInfo -> Minio NC.Request
|
||||||
buildRequest ri = do
|
buildRequest ri = do
|
||||||
maybe (return ()) checkBucketNameValidity $ riBucket ri
|
maybe (return ()) checkBucketNameValidity $ riBucket ri
|
||||||
@ -175,10 +191,14 @@ buildRequest ri = do
|
|||||||
|
|
||||||
timeStamp <- liftIO Time.getCurrentTime
|
timeStamp <- liftIO Time.getCurrentTime
|
||||||
|
|
||||||
|
mgr <- asks mcConnManager
|
||||||
|
cv <- liftIO $ getCredential (connectCreds ci') (getEndpoint ci') mgr
|
||||||
|
|
||||||
let sp =
|
let sp =
|
||||||
SignParams
|
SignParams
|
||||||
(connectAccessKey ci')
|
(coerce $ cvAccessKey cv)
|
||||||
(BA.convert (encodeUtf8 $ connectSecretKey ci' :: ByteString))
|
(coerce $ cvSecretKey cv)
|
||||||
|
(coerce $ cvSessionToken cv)
|
||||||
ServiceS3
|
ServiceS3
|
||||||
timeStamp
|
timeStamp
|
||||||
(riRegion ri')
|
(riRegion ri')
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2018 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2018-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -70,7 +70,6 @@ import Data.Aeson
|
|||||||
)
|
)
|
||||||
import qualified Data.Aeson as A
|
import qualified Data.Aeson as A
|
||||||
import Data.Aeson.Types (typeMismatch)
|
import Data.Aeson.Types (typeMismatch)
|
||||||
import qualified Data.ByteArray as BA
|
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@ -81,6 +80,7 @@ import qualified Network.HTTP.Conduit as NC
|
|||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
import Network.HTTP.Types.Header (hHost)
|
import Network.HTTP.Types.Header (hHost)
|
||||||
import Network.Minio.APICommon
|
import Network.Minio.APICommon
|
||||||
|
import Network.Minio.Credentials
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Errors
|
import Network.Minio.Errors
|
||||||
import Network.Minio.Sign.V4
|
import Network.Minio.Sign.V4
|
||||||
@ -666,6 +666,9 @@ buildAdminRequest areq = do
|
|||||||
|
|
||||||
timeStamp <- liftIO getCurrentTime
|
timeStamp <- liftIO getCurrentTime
|
||||||
|
|
||||||
|
mgr <- asks mcConnManager
|
||||||
|
cv <- liftIO $ getCredential (connectCreds ci) (getEndpoint ci) mgr
|
||||||
|
|
||||||
let hostHeader = (hHost, getHostAddr ci)
|
let hostHeader = (hHost, getHostAddr ci)
|
||||||
newAreq =
|
newAreq =
|
||||||
areq
|
areq
|
||||||
@ -678,8 +681,9 @@ buildAdminRequest areq = do
|
|||||||
signReq = toRequest ci newAreq
|
signReq = toRequest ci newAreq
|
||||||
sp =
|
sp =
|
||||||
SignParams
|
SignParams
|
||||||
(connectAccessKey ci)
|
(coerce $ cvAccessKey cv)
|
||||||
(BA.convert (encodeUtf8 $ connectSecretKey ci :: ByteString))
|
(coerce $ cvSecretKey cv)
|
||||||
|
(coerce $ cvSessionToken cv)
|
||||||
ServiceS3
|
ServiceS3
|
||||||
timeStamp
|
timeStamp
|
||||||
Nothing
|
Nothing
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2022 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -16,129 +16,62 @@
|
|||||||
|
|
||||||
module Network.Minio.Credentials
|
module Network.Minio.Credentials
|
||||||
( CredentialValue (..),
|
( CredentialValue (..),
|
||||||
CredentialProvider (..),
|
credentialValueText,
|
||||||
AccessKey,
|
STSCredentialProvider (..),
|
||||||
SecretKey,
|
AccessKey (..),
|
||||||
SessionToken,
|
SecretKey (..),
|
||||||
|
SessionToken (..),
|
||||||
|
ExpiryTime (..),
|
||||||
|
STSCredentialStore,
|
||||||
|
initSTSCredential,
|
||||||
|
getSTSCredential,
|
||||||
|
Creds (..),
|
||||||
|
getCredential,
|
||||||
|
Endpoint,
|
||||||
|
|
||||||
|
-- * STS Assume Role
|
||||||
defaultSTSAssumeRoleOptions,
|
defaultSTSAssumeRoleOptions,
|
||||||
STSAssumeRole (..),
|
STSAssumeRole (..),
|
||||||
STSAssumeRoleOptions (..),
|
STSAssumeRoleOptions (..),
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import qualified Data.Time as Time
|
import Data.Time (diffUTCTime, getCurrentTime)
|
||||||
import Data.Time.Units (Second)
|
|
||||||
import Network.HTTP.Client (RequestBody (RequestBodyBS))
|
|
||||||
import qualified Network.HTTP.Client as NC
|
import qualified Network.HTTP.Client as NC
|
||||||
import qualified Network.HTTP.Client.TLS as NC
|
import Network.Minio.Credentials.AssumeRole
|
||||||
import Network.HTTP.Types (hContentType, methodPost, renderSimpleQuery)
|
import Network.Minio.Credentials.Types
|
||||||
import Network.HTTP.Types.Header (hHost)
|
import qualified UnliftIO.MVar as M
|
||||||
import Network.Minio.Data
|
|
||||||
import Network.Minio.Data.Crypto (hashSHA256)
|
|
||||||
import Network.Minio.Sign.V4
|
|
||||||
import Network.Minio.Utils (httpLbs)
|
|
||||||
import Network.Minio.XmlParser (parseSTSAssumeRoleResult)
|
|
||||||
|
|
||||||
class CredentialProvider p where
|
data STSCredentialStore = STSCredentialStore
|
||||||
retrieveCredentials :: p -> IO CredentialValue
|
{ cachedCredentials :: M.MVar (CredentialValue, ExpiryTime),
|
||||||
|
refreshAction :: Endpoint -> NC.Manager -> IO (CredentialValue, ExpiryTime)
|
||||||
stsVersion :: ByteString
|
|
||||||
stsVersion = "2011-06-15"
|
|
||||||
|
|
||||||
defaultDurationSeconds :: Second
|
|
||||||
defaultDurationSeconds = 3600
|
|
||||||
|
|
||||||
data STSAssumeRole = STSAssumeRole
|
|
||||||
{ sarEndpoint :: Text,
|
|
||||||
sarCredentials :: CredentialValue,
|
|
||||||
sarOptions :: STSAssumeRoleOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data STSAssumeRoleOptions = STSAssumeRoleOptions
|
initSTSCredential :: STSCredentialProvider p => p -> IO STSCredentialStore
|
||||||
{ -- | Desired validity for the generated credentials.
|
initSTSCredential p = do
|
||||||
saroDurationSeconds :: Maybe Second,
|
let action = retrieveSTSCredentials p
|
||||||
-- | IAM policy to apply for the generated credentials.
|
-- start with dummy credential, so that refresh happens for first request.
|
||||||
saroPolicyJSON :: Maybe ByteString,
|
now <- getCurrentTime
|
||||||
-- | Location is usually required for AWS.
|
mvar <- M.newMVar (CredentialValue mempty mempty mempty, coerce now)
|
||||||
saroLocation :: Maybe Text,
|
return $
|
||||||
saroRoleARN :: Maybe Text,
|
STSCredentialStore
|
||||||
saroRoleSessionName :: Maybe Text,
|
{ cachedCredentials = mvar,
|
||||||
-- | Optional HTTP connection manager
|
refreshAction = action
|
||||||
saroHTTPManager :: Maybe NC.Manager
|
}
|
||||||
}
|
|
||||||
|
|
||||||
-- | Default STS Assume Role options
|
getSTSCredential :: STSCredentialStore -> Endpoint -> NC.Manager -> IO (CredentialValue, Bool)
|
||||||
defaultSTSAssumeRoleOptions :: STSAssumeRoleOptions
|
getSTSCredential store ep mgr = M.modifyMVar (cachedCredentials store) $ \cc@(v, expiry) -> do
|
||||||
defaultSTSAssumeRoleOptions =
|
now <- getCurrentTime
|
||||||
STSAssumeRoleOptions
|
if diffUTCTime now (coerce expiry) > 0
|
||||||
{ saroDurationSeconds = Just defaultDurationSeconds,
|
then do
|
||||||
saroPolicyJSON = Nothing,
|
res <- refreshAction store ep mgr
|
||||||
saroLocation = Nothing,
|
return (res, (fst res, True))
|
||||||
saroRoleARN = Nothing,
|
else return (cc, (v, False))
|
||||||
saroRoleSessionName = Nothing,
|
|
||||||
saroHTTPManager = Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
instance CredentialProvider STSAssumeRole where
|
data Creds
|
||||||
retrieveCredentials sar = do
|
= CredsStatic CredentialValue
|
||||||
-- Assemble STS request
|
| CredsSTS STSCredentialStore
|
||||||
let requiredParams =
|
|
||||||
[ ("Action", "AssumeRole"),
|
|
||||||
("Version", stsVersion)
|
|
||||||
]
|
|
||||||
opts = sarOptions sar
|
|
||||||
durSecs :: Int =
|
|
||||||
fromIntegral $
|
|
||||||
fromMaybe defaultDurationSeconds $
|
|
||||||
saroDurationSeconds opts
|
|
||||||
otherParams =
|
|
||||||
[ ("RoleArn",) . encodeUtf8 <$> saroRoleARN opts,
|
|
||||||
("RoleSessionName",) . encodeUtf8 <$> saroRoleSessionName opts,
|
|
||||||
Just ("DurationSeconds", show durSecs),
|
|
||||||
("Policy",) <$> saroPolicyJSON opts
|
|
||||||
]
|
|
||||||
parameters = requiredParams ++ catMaybes otherParams
|
|
||||||
(host, port, isSecure) =
|
|
||||||
let endPt = NC.parseRequest_ $ toString $ sarEndpoint sar
|
|
||||||
in (NC.host endPt, NC.port endPt, NC.secure endPt)
|
|
||||||
reqBody = renderSimpleQuery False parameters
|
|
||||||
req =
|
|
||||||
NC.defaultRequest
|
|
||||||
{ NC.host = host,
|
|
||||||
NC.port = port,
|
|
||||||
NC.secure = isSecure,
|
|
||||||
NC.method = methodPost,
|
|
||||||
NC.requestHeaders =
|
|
||||||
[ (hHost, getHostHeader (host, port)),
|
|
||||||
(hContentType, "application/x-www-form-urlencoded")
|
|
||||||
],
|
|
||||||
NC.requestBody = RequestBodyBS reqBody
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Sign the STS request.
|
getCredential :: Creds -> Endpoint -> NC.Manager -> IO CredentialValue
|
||||||
timeStamp <- liftIO Time.getCurrentTime
|
getCredential (CredsStatic v) _ _ = return v
|
||||||
let sp =
|
getCredential (CredsSTS s) ep mgr = fst <$> getSTSCredential s ep mgr
|
||||||
SignParams
|
|
||||||
{ spAccessKey = coerce $ cvAccessKey $ sarCredentials sar,
|
|
||||||
spSecretKey = coerce $ cvSecretKey $ sarCredentials sar,
|
|
||||||
spService = ServiceSTS,
|
|
||||||
spTimeStamp = timeStamp,
|
|
||||||
spRegion = saroLocation opts,
|
|
||||||
spExpirySecs = Nothing,
|
|
||||||
spPayloadHash = Just $ hashSHA256 reqBody
|
|
||||||
}
|
|
||||||
signHeaders = signV4 sp req
|
|
||||||
signedReq =
|
|
||||||
req
|
|
||||||
{ NC.requestHeaders = NC.requestHeaders req ++ signHeaders
|
|
||||||
}
|
|
||||||
settings = bool NC.defaultManagerSettings NC.tlsManagerSettings isSecure
|
|
||||||
|
|
||||||
-- Make the STS request
|
|
||||||
mgr <- maybe (NC.newManager settings) return $ saroHTTPManager opts
|
|
||||||
resp <- httpLbs signedReq mgr
|
|
||||||
result <-
|
|
||||||
parseSTSAssumeRoleResult
|
|
||||||
(toStrict $ NC.responseBody resp)
|
|
||||||
"https://sts.amazonaws.com/doc/2011-06-15/"
|
|
||||||
return $ arcCredentials $ arrRoleCredentials result
|
|
||||||
|
|||||||
264
src/Network/Minio/Credentials/AssumeRole.hs
Normal file
264
src/Network/Minio/Credentials/AssumeRole.hs
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
--
|
||||||
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
|
--
|
||||||
|
-- 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.
|
||||||
|
--
|
||||||
|
|
||||||
|
module Network.Minio.Credentials.AssumeRole where
|
||||||
|
|
||||||
|
import qualified Data.ByteArray as BA
|
||||||
|
import qualified Data.ByteString.Lazy as LB
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Time as Time
|
||||||
|
import Data.Time.Units (Second)
|
||||||
|
import Lib.Prelude (UTCTime, throwIO)
|
||||||
|
import Network.HTTP.Client (RequestBody (RequestBodyBS))
|
||||||
|
import qualified Network.HTTP.Client as NC
|
||||||
|
import Network.HTTP.Types (hContentType, methodPost, renderSimpleQuery)
|
||||||
|
import Network.HTTP.Types.Header (hHost)
|
||||||
|
import Network.Minio.Credentials.Types
|
||||||
|
import Network.Minio.Data.Crypto (hashSHA256)
|
||||||
|
import Network.Minio.Errors (MErrV (..))
|
||||||
|
import Network.Minio.Sign.V4
|
||||||
|
import Network.Minio.Utils (getHostHeader, httpLbs)
|
||||||
|
import Network.Minio.XmlCommon
|
||||||
|
import Text.XML.Cursor hiding (bool)
|
||||||
|
|
||||||
|
stsVersion :: ByteString
|
||||||
|
stsVersion = "2011-06-15"
|
||||||
|
|
||||||
|
defaultDurationSeconds :: Second
|
||||||
|
defaultDurationSeconds = 3600
|
||||||
|
|
||||||
|
-- | Assume Role API argument.
|
||||||
|
data STSAssumeRole = STSAssumeRole
|
||||||
|
{ -- | Credentials to use in the AssumeRole STS API.
|
||||||
|
sarCredentials :: CredentialValue,
|
||||||
|
-- | Optional settings.
|
||||||
|
sarOptions :: STSAssumeRoleOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Options for STS Assume Role API.
|
||||||
|
data STSAssumeRoleOptions = STSAssumeRoleOptions
|
||||||
|
{ -- | STS endpoint to which the request will be made. For MinIO, this is the
|
||||||
|
-- same as the server endpoint. For AWS, this has to be the Security Token
|
||||||
|
-- Service endpoint. If using with 'setSTSCredential', this option can be
|
||||||
|
-- left as 'Nothing' and the endpoint in 'ConnectInfo' will be used.
|
||||||
|
saroEndpoint :: Maybe Text,
|
||||||
|
-- | Desired validity for the generated credentials.
|
||||||
|
saroDurationSeconds :: Maybe Second,
|
||||||
|
-- | IAM policy to apply for the generated credentials.
|
||||||
|
saroPolicyJSON :: Maybe ByteString,
|
||||||
|
-- | Location is usually required for AWS.
|
||||||
|
saroLocation :: Maybe Text,
|
||||||
|
saroRoleARN :: Maybe Text,
|
||||||
|
saroRoleSessionName :: Maybe Text
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Default STS Assume Role options - all options are Nothing, except for
|
||||||
|
-- duration which is set to 1 hour.
|
||||||
|
defaultSTSAssumeRoleOptions :: STSAssumeRoleOptions
|
||||||
|
defaultSTSAssumeRoleOptions =
|
||||||
|
STSAssumeRoleOptions
|
||||||
|
{ saroEndpoint = Nothing,
|
||||||
|
saroDurationSeconds = Just 3600,
|
||||||
|
saroPolicyJSON = Nothing,
|
||||||
|
saroLocation = Nothing,
|
||||||
|
saroRoleARN = Nothing,
|
||||||
|
saroRoleSessionName = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
data AssumeRoleCredentials = AssumeRoleCredentials
|
||||||
|
{ arcCredentials :: CredentialValue,
|
||||||
|
arcExpiration :: UTCTime
|
||||||
|
}
|
||||||
|
deriving stock (Show, Eq)
|
||||||
|
|
||||||
|
data AssumeRoleResult = AssumeRoleResult
|
||||||
|
{ arrSourceIdentity :: Text,
|
||||||
|
arrAssumedRoleArn :: Text,
|
||||||
|
arrAssumedRoleId :: Text,
|
||||||
|
arrRoleCredentials :: AssumeRoleCredentials
|
||||||
|
}
|
||||||
|
deriving stock (Show, Eq)
|
||||||
|
|
||||||
|
-- | parseSTSAssumeRoleResult parses an XML response of the following form:
|
||||||
|
--
|
||||||
|
-- <AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
||||||
|
-- <AssumeRoleResult>
|
||||||
|
-- <SourceIdentity>Alice</SourceIdentity>
|
||||||
|
-- <AssumedRoleUser>
|
||||||
|
-- <Arn>arn:aws:sts::123456789012:assumed-role/demo/TestAR</Arn>
|
||||||
|
-- <AssumedRoleId>ARO123EXAMPLE123:TestAR</AssumedRoleId>
|
||||||
|
-- </AssumedRoleUser>
|
||||||
|
-- <Credentials>
|
||||||
|
-- <AccessKeyId>ASIAIOSFODNN7EXAMPLE</AccessKeyId>
|
||||||
|
-- <SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY</SecretAccessKey>
|
||||||
|
-- <SessionToken>
|
||||||
|
-- AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW
|
||||||
|
-- LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd
|
||||||
|
-- QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU
|
||||||
|
-- 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz
|
||||||
|
-- +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==
|
||||||
|
-- </SessionToken>
|
||||||
|
-- <Expiration>2019-11-09T13:34:41Z</Expiration>
|
||||||
|
-- </Credentials>
|
||||||
|
-- <PackedPolicySize>6</PackedPolicySize>
|
||||||
|
-- </AssumeRoleResult>
|
||||||
|
-- <ResponseMetadata>
|
||||||
|
-- <RequestId>c6104cbe-af31-11e0-8154-cbc7ccf896c7</RequestId>
|
||||||
|
-- </ResponseMetadata>
|
||||||
|
-- </AssumeRoleResponse>
|
||||||
|
parseSTSAssumeRoleResult :: MonadIO m => ByteString -> Text -> m AssumeRoleResult
|
||||||
|
parseSTSAssumeRoleResult xmldata namespace = do
|
||||||
|
r <- parseRoot $ LB.fromStrict xmldata
|
||||||
|
let s3Elem' = s3Elem namespace
|
||||||
|
sourceIdentity =
|
||||||
|
T.concat $
|
||||||
|
r
|
||||||
|
$/ s3Elem' "AssumeRoleResult"
|
||||||
|
&/ s3Elem' "SourceIdentity"
|
||||||
|
&/ content
|
||||||
|
roleArn =
|
||||||
|
T.concat $
|
||||||
|
r
|
||||||
|
$/ s3Elem' "AssumeRoleResult"
|
||||||
|
&/ s3Elem' "AssumedRoleUser"
|
||||||
|
&/ s3Elem' "Arn"
|
||||||
|
&/ content
|
||||||
|
roleId =
|
||||||
|
T.concat $
|
||||||
|
r
|
||||||
|
$/ s3Elem' "AssumeRoleResult"
|
||||||
|
&/ s3Elem' "AssumedRoleUser"
|
||||||
|
&/ s3Elem' "AssumedRoleId"
|
||||||
|
&/ content
|
||||||
|
|
||||||
|
convSB :: Text -> BA.ScrubbedBytes
|
||||||
|
convSB = BA.convert . (encodeUtf8 :: Text -> ByteString)
|
||||||
|
|
||||||
|
credsInfo = do
|
||||||
|
cr <-
|
||||||
|
maybe (Left $ MErrVXmlParse "No Credentials Element found") Right $
|
||||||
|
listToMaybe $
|
||||||
|
r $/ s3Elem' "AssumeRoleResult" &/ s3Elem' "Credentials"
|
||||||
|
let cur = fromNode $ node cr
|
||||||
|
return
|
||||||
|
( CredentialValue
|
||||||
|
{ cvAccessKey =
|
||||||
|
coerce $
|
||||||
|
T.concat $
|
||||||
|
cur $/ s3Elem' "AccessKeyId" &/ content,
|
||||||
|
cvSecretKey =
|
||||||
|
coerce $
|
||||||
|
convSB $
|
||||||
|
T.concat $
|
||||||
|
cur
|
||||||
|
$/ s3Elem' "SecretAccessKey"
|
||||||
|
&/ content,
|
||||||
|
cvSessionToken =
|
||||||
|
Just $
|
||||||
|
coerce $
|
||||||
|
convSB $
|
||||||
|
T.concat $
|
||||||
|
cur
|
||||||
|
$/ s3Elem' "SessionToken"
|
||||||
|
&/ content
|
||||||
|
},
|
||||||
|
T.concat $ cur $/ s3Elem' "Expiration" &/ content
|
||||||
|
)
|
||||||
|
creds <- either throwIO pure credsInfo
|
||||||
|
expiry <- parseS3XMLTime $ snd creds
|
||||||
|
let roleCredentials =
|
||||||
|
AssumeRoleCredentials
|
||||||
|
{ arcCredentials = fst creds,
|
||||||
|
arcExpiration = expiry
|
||||||
|
}
|
||||||
|
return
|
||||||
|
AssumeRoleResult
|
||||||
|
{ arrSourceIdentity = sourceIdentity,
|
||||||
|
arrAssumedRoleArn = roleArn,
|
||||||
|
arrAssumedRoleId = roleId,
|
||||||
|
arrRoleCredentials = roleCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
instance STSCredentialProvider STSAssumeRole where
|
||||||
|
getSTSEndpoint = saroEndpoint . sarOptions
|
||||||
|
retrieveSTSCredentials sar (host', port', isSecure') mgr = do
|
||||||
|
-- Assemble STS request
|
||||||
|
let requiredParams =
|
||||||
|
[ ("Action", "AssumeRole"),
|
||||||
|
("Version", stsVersion)
|
||||||
|
]
|
||||||
|
opts = sarOptions sar
|
||||||
|
|
||||||
|
durSecs :: Int =
|
||||||
|
fromIntegral $
|
||||||
|
fromMaybe defaultDurationSeconds $
|
||||||
|
saroDurationSeconds opts
|
||||||
|
otherParams =
|
||||||
|
[ ("RoleArn",) . encodeUtf8 <$> saroRoleARN opts,
|
||||||
|
("RoleSessionName",) . encodeUtf8 <$> saroRoleSessionName opts,
|
||||||
|
Just ("DurationSeconds", show durSecs),
|
||||||
|
("Policy",) <$> saroPolicyJSON opts
|
||||||
|
]
|
||||||
|
parameters = requiredParams ++ catMaybes otherParams
|
||||||
|
(host, port, isSecure) =
|
||||||
|
case getSTSEndpoint sar of
|
||||||
|
Just ep ->
|
||||||
|
let endPt = NC.parseRequest_ $ toString ep
|
||||||
|
in (NC.host endPt, NC.port endPt, NC.secure endPt)
|
||||||
|
Nothing -> (host', port', isSecure')
|
||||||
|
reqBody = renderSimpleQuery False parameters
|
||||||
|
req =
|
||||||
|
NC.defaultRequest
|
||||||
|
{ NC.host = host,
|
||||||
|
NC.port = port,
|
||||||
|
NC.secure = isSecure,
|
||||||
|
NC.method = methodPost,
|
||||||
|
NC.requestHeaders =
|
||||||
|
[ (hHost, getHostHeader (host, port)),
|
||||||
|
(hContentType, "application/x-www-form-urlencoded")
|
||||||
|
],
|
||||||
|
NC.requestBody = RequestBodyBS reqBody
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Sign the STS request.
|
||||||
|
timeStamp <- liftIO Time.getCurrentTime
|
||||||
|
let sp =
|
||||||
|
SignParams
|
||||||
|
{ spAccessKey = coerce $ cvAccessKey $ sarCredentials sar,
|
||||||
|
spSecretKey = coerce $ cvSecretKey $ sarCredentials sar,
|
||||||
|
spSessionToken = coerce $ cvSessionToken $ sarCredentials sar,
|
||||||
|
spService = ServiceSTS,
|
||||||
|
spTimeStamp = timeStamp,
|
||||||
|
spRegion = saroLocation opts,
|
||||||
|
spExpirySecs = Nothing,
|
||||||
|
spPayloadHash = Just $ hashSHA256 reqBody
|
||||||
|
}
|
||||||
|
signHeaders = signV4 sp req
|
||||||
|
signedReq =
|
||||||
|
req
|
||||||
|
{ NC.requestHeaders = NC.requestHeaders req ++ signHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Make the STS request
|
||||||
|
resp <- httpLbs signedReq mgr
|
||||||
|
result <-
|
||||||
|
parseSTSAssumeRoleResult
|
||||||
|
(toStrict $ NC.responseBody resp)
|
||||||
|
"https://sts.amazonaws.com/doc/2011-06-15/"
|
||||||
|
return
|
||||||
|
( arcCredentials $ arrRoleCredentials result,
|
||||||
|
coerce $ arcExpiration $ arrRoleCredentials result
|
||||||
|
)
|
||||||
85
src/Network/Minio/Credentials/Types.hs
Normal file
85
src/Network/Minio/Credentials/Types.hs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
--
|
||||||
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
|
--
|
||||||
|
-- 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 GeneralizedNewtypeDeriving #-}
|
||||||
|
|
||||||
|
module Network.Minio.Credentials.Types where
|
||||||
|
|
||||||
|
import qualified Data.ByteArray as BA
|
||||||
|
import Lib.Prelude (UTCTime)
|
||||||
|
import qualified Network.HTTP.Client as NC
|
||||||
|
|
||||||
|
-- | Access Key type.
|
||||||
|
newtype AccessKey = AccessKey {unAccessKey :: Text}
|
||||||
|
deriving stock (Show)
|
||||||
|
deriving newtype (Eq, IsString, Semigroup, Monoid)
|
||||||
|
|
||||||
|
-- | Secret Key type - has a show instance that does not print the value.
|
||||||
|
newtype SecretKey = SecretKey {unSecretKey :: BA.ScrubbedBytes}
|
||||||
|
deriving stock (Show)
|
||||||
|
deriving newtype (Eq, IsString, Semigroup, Monoid)
|
||||||
|
|
||||||
|
-- | Session Token type - has a show instance that does not print the value.
|
||||||
|
newtype SessionToken = SessionToken {unSessionToken :: BA.ScrubbedBytes}
|
||||||
|
deriving stock (Show)
|
||||||
|
deriving newtype (Eq, IsString, Semigroup, Monoid)
|
||||||
|
|
||||||
|
-- | Object storage credential data type. It has support for the optional
|
||||||
|
-- <https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
|
||||||
|
-- SessionToken> for using temporary credentials requested via STS.
|
||||||
|
--
|
||||||
|
-- The show instance for this type does not print the value of secrets for
|
||||||
|
-- security.
|
||||||
|
data CredentialValue = CredentialValue
|
||||||
|
{ cvAccessKey :: AccessKey,
|
||||||
|
cvSecretKey :: SecretKey,
|
||||||
|
cvSessionToken :: Maybe SessionToken
|
||||||
|
}
|
||||||
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
|
scrubbedToText :: BA.ScrubbedBytes -> Text
|
||||||
|
scrubbedToText =
|
||||||
|
let b2t :: ByteString -> Text
|
||||||
|
b2t = decodeUtf8
|
||||||
|
s2b :: BA.ScrubbedBytes -> ByteString
|
||||||
|
s2b = BA.convert
|
||||||
|
in b2t . s2b
|
||||||
|
|
||||||
|
-- | Convert a 'CredentialValue' to a text tuple. Use this to output the
|
||||||
|
-- credential to files or other programs.
|
||||||
|
credentialValueText :: CredentialValue -> (Text, Text, Maybe Text)
|
||||||
|
credentialValueText cv =
|
||||||
|
( coerce $ cvAccessKey cv,
|
||||||
|
(scrubbedToText . coerce) $ cvSecretKey cv,
|
||||||
|
scrubbedToText . coerce <$> cvSessionToken cv
|
||||||
|
)
|
||||||
|
|
||||||
|
-- | Endpoint represented by host, port and TLS enabled flag.
|
||||||
|
type Endpoint = (ByteString, Int, Bool)
|
||||||
|
|
||||||
|
-- | Typeclass for STS credential providers.
|
||||||
|
class STSCredentialProvider p where
|
||||||
|
retrieveSTSCredentials ::
|
||||||
|
p ->
|
||||||
|
-- | STS Endpoint (host, port, isSecure)
|
||||||
|
Endpoint ->
|
||||||
|
NC.Manager ->
|
||||||
|
IO (CredentialValue, ExpiryTime)
|
||||||
|
getSTSEndpoint :: p -> Maybe Text
|
||||||
|
|
||||||
|
-- | 'ExpiryTime' represents a time at which a credential expires.
|
||||||
|
newtype ExpiryTime = ExpiryTime {unExpiryTime :: UTCTime}
|
||||||
|
deriving stock (Show)
|
||||||
|
deriving newtype (Eq)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -34,9 +34,9 @@ import qualified Data.Aeson as A
|
|||||||
import qualified Data.ByteArray as BA
|
import qualified Data.ByteArray as BA
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Data.ByteString.Lazy as LB
|
import qualified Data.ByteString.Lazy as LB
|
||||||
import Data.CaseInsensitive (mk)
|
|
||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
import qualified Data.Ini as Ini
|
import qualified Data.Ini as Ini
|
||||||
|
import qualified Data.List as List
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Text.Encoding as TE
|
import qualified Data.Text.Encoding as TE
|
||||||
import Data.Time (defaultTimeLocale, formatTime)
|
import Data.Time (defaultTimeLocale, formatTime)
|
||||||
@ -53,6 +53,7 @@ import Network.HTTP.Types
|
|||||||
hRange,
|
hRange,
|
||||||
)
|
)
|
||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
|
import Network.Minio.Credentials
|
||||||
import Network.Minio.Data.Crypto
|
import Network.Minio.Data.Crypto
|
||||||
( encodeToBase64,
|
( encodeToBase64,
|
||||||
hashMD5ToBase64,
|
hashMD5ToBase64,
|
||||||
@ -62,11 +63,12 @@ import Network.Minio.Errors
|
|||||||
( MErrV (MErrVInvalidEncryptionKeyLength, MErrVMissingCredentials),
|
( MErrV (MErrVInvalidEncryptionKeyLength, MErrVMissingCredentials),
|
||||||
MinioErr (..),
|
MinioErr (..),
|
||||||
)
|
)
|
||||||
|
import Network.Minio.Utils
|
||||||
import System.Directory (doesFileExist, getHomeDirectory)
|
import System.Directory (doesFileExist, getHomeDirectory)
|
||||||
import qualified System.Environment as Env
|
import qualified System.Environment as Env
|
||||||
import System.FilePath.Posix (combine)
|
import System.FilePath.Posix (combine)
|
||||||
import Text.XML (Name (Name))
|
|
||||||
import qualified UnliftIO as U
|
import qualified UnliftIO as U
|
||||||
|
import qualified UnliftIO.MVar as UM
|
||||||
|
|
||||||
-- | max obj size is 5TiB
|
-- | max obj size is 5TiB
|
||||||
maxObjectSize :: Int64
|
maxObjectSize :: Int64
|
||||||
@ -131,14 +133,15 @@ awsRegionMap =
|
|||||||
data ConnectInfo = ConnectInfo
|
data ConnectInfo = ConnectInfo
|
||||||
{ connectHost :: Text,
|
{ connectHost :: Text,
|
||||||
connectPort :: Int,
|
connectPort :: Int,
|
||||||
connectAccessKey :: Text,
|
connectCreds :: Creds,
|
||||||
connectSecretKey :: Text,
|
|
||||||
connectIsSecure :: Bool,
|
connectIsSecure :: Bool,
|
||||||
connectRegion :: Region,
|
connectRegion :: Region,
|
||||||
connectAutoDiscoverRegion :: Bool,
|
connectAutoDiscoverRegion :: Bool,
|
||||||
connectDisableTLSCertValidation :: Bool
|
connectDisableTLSCertValidation :: Bool
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
|
||||||
|
getEndpoint :: ConnectInfo -> Endpoint
|
||||||
|
getEndpoint ci = (encodeUtf8 $ connectHost ci, connectPort ci, connectIsSecure ci)
|
||||||
|
|
||||||
instance IsString ConnectInfo where
|
instance IsString ConnectInfo where
|
||||||
fromString str =
|
fromString str =
|
||||||
@ -146,8 +149,7 @@ instance IsString ConnectInfo where
|
|||||||
in ConnectInfo
|
in ConnectInfo
|
||||||
{ connectHost = TE.decodeUtf8 $ NC.host req,
|
{ connectHost = TE.decodeUtf8 $ NC.host req,
|
||||||
connectPort = NC.port req,
|
connectPort = NC.port req,
|
||||||
connectAccessKey = "",
|
connectCreds = CredsStatic $ CredentialValue mempty mempty mempty,
|
||||||
connectSecretKey = "",
|
|
||||||
connectIsSecure = NC.secure req,
|
connectIsSecure = NC.secure req,
|
||||||
connectRegion = "",
|
connectRegion = "",
|
||||||
connectAutoDiscoverRegion = True,
|
connectAutoDiscoverRegion = True,
|
||||||
@ -161,20 +163,21 @@ data Credentials = Credentials
|
|||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A Provider is an action that may return Credentials. Providers
|
-- | A 'CredentialLoader' is an action that may return a 'CredentialValue'.
|
||||||
-- may be chained together using 'findFirst'.
|
-- Loaders may be chained together using 'findFirst'.
|
||||||
type Provider = IO (Maybe Credentials)
|
type CredentialLoader = IO (Maybe CredentialValue)
|
||||||
|
|
||||||
-- | Combines the given list of providers, by calling each one in
|
-- | Combines the given list of loaders, by calling each one in
|
||||||
-- order until Credentials are found.
|
-- order until a 'CredentialValue' is returned.
|
||||||
findFirst :: [Provider] -> Provider
|
findFirst :: [CredentialLoader] -> IO (Maybe CredentialValue)
|
||||||
findFirst [] = return Nothing
|
findFirst [] = return Nothing
|
||||||
findFirst (f : fs) = do
|
findFirst (f : fs) = do
|
||||||
c <- f
|
c <- f
|
||||||
maybe (findFirst fs) (return . Just) c
|
maybe (findFirst fs) (return . Just) c
|
||||||
|
|
||||||
-- | This Provider loads `Credentials` from @~\/.aws\/credentials@
|
-- | This action returns a 'CredentialValue' populated from
|
||||||
fromAWSConfigFile :: Provider
|
-- @~\/.aws\/credentials@
|
||||||
|
fromAWSConfigFile :: CredentialLoader
|
||||||
fromAWSConfigFile = do
|
fromAWSConfigFile = do
|
||||||
credsE <- runExceptT $ do
|
credsE <- runExceptT $ do
|
||||||
homeDir <- lift getHomeDirectory
|
homeDir <- lift getHomeDirectory
|
||||||
@ -190,29 +193,28 @@ fromAWSConfigFile = do
|
|||||||
ExceptT $
|
ExceptT $
|
||||||
return $
|
return $
|
||||||
Ini.lookupValue "default" "aws_secret_access_key" ini
|
Ini.lookupValue "default" "aws_secret_access_key" ini
|
||||||
return $ Credentials akey skey
|
return $ CredentialValue (coerce akey) (fromString $ T.unpack skey) Nothing
|
||||||
return $ either (const Nothing) Just credsE
|
return $ either (const Nothing) Just credsE
|
||||||
|
|
||||||
-- | This Provider loads `Credentials` from @AWS_ACCESS_KEY_ID@ and
|
-- | This action returns a 'CredentialValue` populated from @AWS_ACCESS_KEY_ID@
|
||||||
-- @AWS_SECRET_ACCESS_KEY@ environment variables.
|
-- and @AWS_SECRET_ACCESS_KEY@ environment variables.
|
||||||
fromAWSEnv :: Provider
|
fromAWSEnv :: CredentialLoader
|
||||||
fromAWSEnv = runMaybeT $ do
|
fromAWSEnv = runMaybeT $ do
|
||||||
akey <- MaybeT $ Env.lookupEnv "AWS_ACCESS_KEY_ID"
|
akey <- MaybeT $ Env.lookupEnv "AWS_ACCESS_KEY_ID"
|
||||||
skey <- MaybeT $ Env.lookupEnv "AWS_SECRET_ACCESS_KEY"
|
skey <- MaybeT $ Env.lookupEnv "AWS_SECRET_ACCESS_KEY"
|
||||||
return $ Credentials (T.pack akey) (T.pack skey)
|
return $ CredentialValue (fromString akey) (fromString skey) Nothing
|
||||||
|
|
||||||
-- | This Provider loads `Credentials` from @MINIO_ACCESS_KEY@ and
|
-- | This action returns a 'CredentialValue' populated from @MINIO_ACCESS_KEY@
|
||||||
-- @MINIO_SECRET_KEY@ environment variables.
|
-- and @MINIO_SECRET_KEY@ environment variables.
|
||||||
fromMinioEnv :: Provider
|
fromMinioEnv :: CredentialLoader
|
||||||
fromMinioEnv = runMaybeT $ do
|
fromMinioEnv = runMaybeT $ do
|
||||||
akey <- MaybeT $ Env.lookupEnv "MINIO_ACCESS_KEY"
|
akey <- MaybeT $ Env.lookupEnv "MINIO_ACCESS_KEY"
|
||||||
skey <- MaybeT $ Env.lookupEnv "MINIO_SECRET_KEY"
|
skey <- MaybeT $ Env.lookupEnv "MINIO_SECRET_KEY"
|
||||||
return $ Credentials (T.pack akey) (T.pack skey)
|
return $ CredentialValue (fromString akey) (fromString skey) Nothing
|
||||||
|
|
||||||
-- | setCredsFrom retrieves access credentials from the first
|
-- | setCredsFrom retrieves access credentials from the first action in the
|
||||||
-- `Provider` form the given list that succeeds and sets it in the
|
-- given list that succeeds and sets it in the 'ConnectInfo'.
|
||||||
-- `ConnectInfo`.
|
setCredsFrom :: [CredentialLoader] -> ConnectInfo -> IO ConnectInfo
|
||||||
setCredsFrom :: [Provider] -> ConnectInfo -> IO ConnectInfo
|
|
||||||
setCredsFrom ps ci = do
|
setCredsFrom ps ci = do
|
||||||
pMay <- findFirst ps
|
pMay <- findFirst ps
|
||||||
maybe
|
maybe
|
||||||
@ -220,14 +222,21 @@ setCredsFrom ps ci = do
|
|||||||
(return . (`setCreds` ci))
|
(return . (`setCreds` ci))
|
||||||
pMay
|
pMay
|
||||||
|
|
||||||
-- | setCreds sets the given `Credentials` in the `ConnectInfo`.
|
-- | setCreds sets the given `CredentialValue` in the `ConnectInfo`.
|
||||||
setCreds :: Credentials -> ConnectInfo -> ConnectInfo
|
setCreds :: CredentialValue -> ConnectInfo -> ConnectInfo
|
||||||
setCreds (Credentials accessKey secretKey) connInfo =
|
setCreds cv connInfo =
|
||||||
connInfo
|
connInfo
|
||||||
{ connectAccessKey = accessKey,
|
{ connectCreds = CredsStatic cv
|
||||||
connectSecretKey = secretKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- | 'setSTSCredential' configures `ConnectInfo` to retrieve temporary
|
||||||
|
-- credentials via the STS API on demand. It is automatically refreshed on
|
||||||
|
-- expiry.
|
||||||
|
setSTSCredential :: STSCredentialProvider p => p -> ConnectInfo -> IO ConnectInfo
|
||||||
|
setSTSCredential p ci = do
|
||||||
|
store <- initSTSCredential p
|
||||||
|
return ci {connectCreds = CredsSTS store}
|
||||||
|
|
||||||
-- | Set the S3 region parameter in the `ConnectInfo`
|
-- | Set the S3 region parameter in the `ConnectInfo`
|
||||||
setRegion :: Region -> ConnectInfo -> ConnectInfo
|
setRegion :: Region -> ConnectInfo -> ConnectInfo
|
||||||
setRegion r connInfo =
|
setRegion r connInfo =
|
||||||
@ -248,12 +257,6 @@ isConnectInfoSecure = connectIsSecure
|
|||||||
disableTLSCertValidation :: ConnectInfo -> ConnectInfo
|
disableTLSCertValidation :: ConnectInfo -> ConnectInfo
|
||||||
disableTLSCertValidation c = c {connectDisableTLSCertValidation = True}
|
disableTLSCertValidation c = c {connectDisableTLSCertValidation = True}
|
||||||
|
|
||||||
getHostHeader :: (ByteString, Int) -> ByteString
|
|
||||||
getHostHeader (host, port) =
|
|
||||||
if port == 80 || port == 443
|
|
||||||
then host
|
|
||||||
else host <> ":" <> show port
|
|
||||||
|
|
||||||
getHostAddr :: ConnectInfo -> ByteString
|
getHostAddr :: ConnectInfo -> ByteString
|
||||||
getHostAddr ci = getHostHeader (encodeUtf8 $ connectHost ci, connectPort ci)
|
getHostAddr ci = getHostHeader (encodeUtf8 $ connectHost ci, connectPort ci)
|
||||||
|
|
||||||
@ -278,7 +281,7 @@ awsCI = "https://s3.amazonaws.com"
|
|||||||
-- ConnectInfo. Credentials are already filled in.
|
-- ConnectInfo. Credentials are already filled in.
|
||||||
minioPlayCI :: ConnectInfo
|
minioPlayCI :: ConnectInfo
|
||||||
minioPlayCI =
|
minioPlayCI =
|
||||||
let playCreds = Credentials "Q3AM3UQ867SPQQA43P2F" "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
let playCreds = CredentialValue "Q3AM3UQ867SPQQA43P2F" "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" Nothing
|
||||||
in setCreds playCreds $
|
in setCreds playCreds $
|
||||||
setRegion
|
setRegion
|
||||||
"us-east-1"
|
"us-east-1"
|
||||||
@ -380,24 +383,6 @@ data PutObjectOptions = PutObjectOptions
|
|||||||
defaultPutObjectOptions :: PutObjectOptions
|
defaultPutObjectOptions :: PutObjectOptions
|
||||||
defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing
|
defaultPutObjectOptions = PutObjectOptions Nothing Nothing Nothing Nothing Nothing Nothing [] Nothing Nothing
|
||||||
|
|
||||||
-- | If the given header name has the @X-Amz-Meta-@ prefix, it is
|
|
||||||
-- stripped and a Just is returned.
|
|
||||||
userMetadataHeaderNameMaybe :: Text -> Maybe Text
|
|
||||||
userMetadataHeaderNameMaybe k =
|
|
||||||
let prefix = T.toCaseFold "X-Amz-Meta-"
|
|
||||||
n = T.length prefix
|
|
||||||
in if T.toCaseFold (T.take n k) == prefix
|
|
||||||
then Just (T.drop n k)
|
|
||||||
else Nothing
|
|
||||||
|
|
||||||
addXAmzMetaPrefix :: Text -> Text
|
|
||||||
addXAmzMetaPrefix s
|
|
||||||
| isJust (userMetadataHeaderNameMaybe s) = s
|
|
||||||
| otherwise = "X-Amz-Meta-" <> s
|
|
||||||
|
|
||||||
mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header]
|
|
||||||
mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix x, encodeUtf8 y))
|
|
||||||
|
|
||||||
pooToHeaders :: PutObjectOptions -> [HT.Header]
|
pooToHeaders :: PutObjectOptions -> [HT.Header]
|
||||||
pooToHeaders poo =
|
pooToHeaders poo =
|
||||||
userMetadata
|
userMetadata
|
||||||
@ -437,6 +422,29 @@ data BucketInfo = BucketInfo
|
|||||||
-- | A type alias to represent a part-number for multipart upload
|
-- | A type alias to represent a part-number for multipart upload
|
||||||
type PartNumber = Int16
|
type PartNumber = Int16
|
||||||
|
|
||||||
|
-- | Select part sizes - the logic is that the minimum part-size will
|
||||||
|
-- be 64MiB.
|
||||||
|
selectPartSizes :: Int64 -> [(PartNumber, Int64, Int64)]
|
||||||
|
selectPartSizes size =
|
||||||
|
uncurry (List.zip3 [1 ..]) $
|
||||||
|
List.unzip $
|
||||||
|
loop 0 size
|
||||||
|
where
|
||||||
|
ceil :: Double -> Int64
|
||||||
|
ceil = ceiling
|
||||||
|
partSize =
|
||||||
|
max
|
||||||
|
minPartSize
|
||||||
|
( ceil $
|
||||||
|
fromIntegral size
|
||||||
|
/ fromIntegral maxMultipartParts
|
||||||
|
)
|
||||||
|
m = partSize
|
||||||
|
loop st sz
|
||||||
|
| st > sz = []
|
||||||
|
| st + m >= sz = [(st, sz - st)]
|
||||||
|
| otherwise = (st, m) : loop (st + m) sz
|
||||||
|
|
||||||
-- | A type alias to represent an upload-id for multipart upload
|
-- | A type alias to represent an upload-id for multipart upload
|
||||||
type UploadId = Text
|
type UploadId = Text
|
||||||
|
|
||||||
@ -1016,47 +1024,6 @@ type Stats = Progress
|
|||||||
-- Select API Related Types End
|
-- Select API Related Types End
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
-- Credentials Start
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
newtype AccessKey = AccessKey {unAccessKey :: Text}
|
|
||||||
deriving stock (Show)
|
|
||||||
deriving newtype (Eq, IsString)
|
|
||||||
|
|
||||||
newtype SecretKey = SecretKey {unSecretKey :: BA.ScrubbedBytes}
|
|
||||||
deriving stock (Show)
|
|
||||||
deriving newtype (Eq, IsString)
|
|
||||||
|
|
||||||
newtype SessionToken = SessionToken {unSessionToken :: BA.ScrubbedBytes}
|
|
||||||
deriving stock (Show)
|
|
||||||
deriving newtype (Eq, IsString)
|
|
||||||
|
|
||||||
data CredentialValue = CredentialValue
|
|
||||||
{ cvAccessKey :: AccessKey,
|
|
||||||
cvSecretKey :: SecretKey,
|
|
||||||
cvSessionToken :: Maybe SessionToken
|
|
||||||
}
|
|
||||||
deriving stock (Eq, Show)
|
|
||||||
|
|
||||||
data AssumeRoleCredentials = AssumeRoleCredentials
|
|
||||||
{ arcCredentials :: CredentialValue,
|
|
||||||
arcExpiration :: UTCTime
|
|
||||||
}
|
|
||||||
deriving stock (Show, Eq)
|
|
||||||
|
|
||||||
data AssumeRoleResult = AssumeRoleResult
|
|
||||||
{ arrSourceIdentity :: Text,
|
|
||||||
arrAssumedRoleArn :: Text,
|
|
||||||
arrAssumedRoleId :: Text,
|
|
||||||
arrRoleCredentials :: AssumeRoleCredentials
|
|
||||||
}
|
|
||||||
deriving stock (Show, Eq)
|
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
-- Credentials End
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
-- | Represents different kinds of payload that are used with S3 API
|
-- | Represents different kinds of payload that are used with S3 API
|
||||||
-- requests.
|
-- requests.
|
||||||
data Payload
|
data Payload
|
||||||
@ -1202,9 +1169,22 @@ runMinioRes ci m = do
|
|||||||
conn <- liftIO $ connect ci
|
conn <- liftIO $ connect ci
|
||||||
runMinioResWith conn m
|
runMinioResWith conn m
|
||||||
|
|
||||||
s3Name :: Text -> Text -> Name
|
|
||||||
s3Name ns s = Name s (Just ns) Nothing
|
|
||||||
|
|
||||||
-- | Format as per RFC 1123.
|
-- | Format as per RFC 1123.
|
||||||
formatRFC1123 :: UTCTime -> T.Text
|
formatRFC1123 :: UTCTime -> T.Text
|
||||||
formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
|
formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
|
||||||
|
|
||||||
|
lookupRegionCache :: Bucket -> Minio (Maybe Region)
|
||||||
|
lookupRegionCache b = do
|
||||||
|
rMVar <- asks mcRegionMap
|
||||||
|
rMap <- UM.readMVar rMVar
|
||||||
|
return $ H.lookup b rMap
|
||||||
|
|
||||||
|
addToRegionCache :: Bucket -> Region -> Minio ()
|
||||||
|
addToRegionCache b region = do
|
||||||
|
rMVar <- asks mcRegionMap
|
||||||
|
UM.modifyMVar_ rMVar $ return . H.insert b region
|
||||||
|
|
||||||
|
deleteFromRegionCache :: Bucket -> Minio ()
|
||||||
|
deleteFromRegionCache b = do
|
||||||
|
rMVar <- asks mcRegionMap
|
||||||
|
UM.modifyMVar_ rMVar $ return . H.delete b
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -49,6 +49,7 @@ data MErrV
|
|||||||
| MErrVInvalidEncryptionKeyLength
|
| MErrVInvalidEncryptionKeyLength
|
||||||
| MErrVStreamingBodyUnexpectedEOF
|
| MErrVStreamingBodyUnexpectedEOF
|
||||||
| MErrVUnexpectedPayload
|
| MErrVUnexpectedPayload
|
||||||
|
| MErrVSTSEndpointNotFound
|
||||||
deriving stock (Show, Eq)
|
deriving stock (Show, Eq)
|
||||||
|
|
||||||
instance Exception MErrV
|
instance Exception MErrV
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +13,7 @@
|
|||||||
-- See the License for the specific language governing permissions and
|
-- See the License for the specific language governing permissions and
|
||||||
-- limitations under the License.
|
-- limitations under the License.
|
||||||
--
|
--
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Network.Minio.PresignedOperations
|
module Network.Minio.PresignedOperations
|
||||||
( UrlExpiry,
|
( UrlExpiry,
|
||||||
@ -39,7 +38,6 @@ where
|
|||||||
|
|
||||||
import Data.Aeson ((.=))
|
import Data.Aeson ((.=))
|
||||||
import qualified Data.Aeson as Json
|
import qualified Data.Aeson as Json
|
||||||
import qualified Data.ByteArray as BA
|
|
||||||
import Data.ByteString.Builder (byteString, toLazyByteString)
|
import Data.ByteString.Builder (byteString, toLazyByteString)
|
||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@ -48,6 +46,7 @@ import Lib.Prelude
|
|||||||
import qualified Network.HTTP.Client as NClient
|
import qualified Network.HTTP.Client as NClient
|
||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
import Network.Minio.API (buildRequest)
|
import Network.Minio.API (buildRequest)
|
||||||
|
import Network.Minio.Credentials
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Data.Time
|
import Network.Minio.Data.Time
|
||||||
import Network.Minio.Errors
|
import Network.Minio.Errors
|
||||||
@ -300,6 +299,8 @@ presignedPostPolicy ::
|
|||||||
presignedPostPolicy p = do
|
presignedPostPolicy p = do
|
||||||
ci <- asks mcConnInfo
|
ci <- asks mcConnInfo
|
||||||
signTime <- liftIO Time.getCurrentTime
|
signTime <- liftIO Time.getCurrentTime
|
||||||
|
mgr <- asks mcConnManager
|
||||||
|
cv <- liftIO $ getCredential (connectCreds ci) (getEndpoint ci) mgr
|
||||||
|
|
||||||
let extraConditions signParams =
|
let extraConditions signParams =
|
||||||
[ PPCEquals "x-amz-date" (toText $ awsTimeFormat signTime),
|
[ PPCEquals "x-amz-date" (toText $ awsTimeFormat signTime),
|
||||||
@ -308,7 +309,7 @@ presignedPostPolicy p = do
|
|||||||
"x-amz-credential"
|
"x-amz-credential"
|
||||||
( T.intercalate
|
( T.intercalate
|
||||||
"/"
|
"/"
|
||||||
[ connectAccessKey ci,
|
[ coerce $ cvAccessKey cv,
|
||||||
decodeUtf8 $ credentialScope signParams
|
decodeUtf8 $ credentialScope signParams
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -319,8 +320,9 @@ presignedPostPolicy p = do
|
|||||||
}
|
}
|
||||||
sp =
|
sp =
|
||||||
SignParams
|
SignParams
|
||||||
(connectAccessKey ci)
|
(coerce $ cvAccessKey cv)
|
||||||
(BA.convert (encodeUtf8 $ connectSecretKey ci :: ByteString))
|
(coerce $ cvSecretKey cv)
|
||||||
|
(coerce $ cvSessionToken cv)
|
||||||
ServiceS3
|
ServiceS3
|
||||||
signTime
|
signTime
|
||||||
(Just $ connectRegion ci)
|
(Just $ connectRegion ci)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -14,6 +14,14 @@
|
|||||||
-- limitations under the License.
|
-- limitations under the License.
|
||||||
--
|
--
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- Module: Network.Minio.S3API
|
||||||
|
-- Copyright: (c) 2017-2023 MinIO Dev Team
|
||||||
|
-- License: Apache 2.0
|
||||||
|
-- Maintainer: MinIO Dev Team <dev@min.io>
|
||||||
|
--
|
||||||
|
-- Lower-level API for S3 compatible object stores. Start with @Network.Minio@
|
||||||
|
-- and use this only if needed.
|
||||||
module Network.Minio.S3API
|
module Network.Minio.S3API
|
||||||
( Region,
|
( Region,
|
||||||
getLocation,
|
getLocation,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -15,7 +15,16 @@
|
|||||||
--
|
--
|
||||||
{-# LANGUAGE BangPatterns #-}
|
{-# LANGUAGE BangPatterns #-}
|
||||||
|
|
||||||
module Network.Minio.Sign.V4 where
|
module Network.Minio.Sign.V4
|
||||||
|
( SignParams (..),
|
||||||
|
signV4QueryParams,
|
||||||
|
signV4,
|
||||||
|
signV4PostPolicy,
|
||||||
|
signV4Stream,
|
||||||
|
Service (..),
|
||||||
|
credentialScope,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import qualified Conduit as C
|
import qualified Conduit as C
|
||||||
import qualified Data.ByteArray as BA
|
import qualified Data.ByteArray as BA
|
||||||
@ -23,6 +32,7 @@ import qualified Data.ByteString as B
|
|||||||
import qualified Data.ByteString.Base64 as Base64
|
import qualified Data.ByteString.Base64 as Base64
|
||||||
import qualified Data.ByteString.Char8 as B8
|
import qualified Data.ByteString.Char8 as B8
|
||||||
import qualified Data.ByteString.Lazy as LB
|
import qualified Data.ByteString.Lazy as LB
|
||||||
|
import Data.CaseInsensitive (mk)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
import qualified Data.HashSet as Set
|
import qualified Data.HashSet as Set
|
||||||
@ -52,17 +62,6 @@ ignoredHeaders =
|
|||||||
H.hUserAgent
|
H.hUserAgent
|
||||||
]
|
]
|
||||||
|
|
||||||
data SignV4Data = SignV4Data
|
|
||||||
{ sv4SignTime :: UTCTime,
|
|
||||||
sv4Scope :: ByteString,
|
|
||||||
sv4CanonicalRequest :: ByteString,
|
|
||||||
sv4HeadersToSign :: [(ByteString, ByteString)],
|
|
||||||
sv4Output :: [(ByteString, ByteString)],
|
|
||||||
sv4StringToSign :: ByteString,
|
|
||||||
sv4SigningKey :: ByteString
|
|
||||||
}
|
|
||||||
deriving stock (Show)
|
|
||||||
|
|
||||||
data Service = ServiceS3 | ServiceSTS
|
data Service = ServiceS3 | ServiceSTS
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
@ -73,6 +72,7 @@ toByteString ServiceSTS = "sts"
|
|||||||
data SignParams = SignParams
|
data SignParams = SignParams
|
||||||
{ spAccessKey :: Text,
|
{ spAccessKey :: Text,
|
||||||
spSecretKey :: BA.ScrubbedBytes,
|
spSecretKey :: BA.ScrubbedBytes,
|
||||||
|
spSessionToken :: Maybe BA.ScrubbedBytes,
|
||||||
spService :: Service,
|
spService :: Service,
|
||||||
spTimeStamp :: UTCTime,
|
spTimeStamp :: UTCTime,
|
||||||
spRegion :: Maybe Text,
|
spRegion :: Maybe Text,
|
||||||
@ -81,23 +81,6 @@ data SignParams = SignParams
|
|||||||
}
|
}
|
||||||
deriving stock (Show)
|
deriving stock (Show)
|
||||||
|
|
||||||
debugPrintSignV4Data :: SignV4Data -> IO ()
|
|
||||||
debugPrintSignV4Data (SignV4Data t s cr h2s o sts sk) = do
|
|
||||||
B8.putStrLn "SignV4Data:"
|
|
||||||
B8.putStr "Timestamp: " >> print t
|
|
||||||
B8.putStr "Scope: " >> B8.putStrLn s
|
|
||||||
B8.putStrLn "Canonical Request:"
|
|
||||||
B8.putStrLn cr
|
|
||||||
B8.putStr "Headers to Sign: " >> print h2s
|
|
||||||
B8.putStr "Output: " >> print o
|
|
||||||
B8.putStr "StringToSign: " >> B8.putStrLn sts
|
|
||||||
B8.putStr "SigningKey: " >> printBytes sk
|
|
||||||
B8.putStrLn "END of SignV4Data ========="
|
|
||||||
where
|
|
||||||
printBytes b = do
|
|
||||||
mapM_ (\x -> B.putStr $ B.singleton x <> " ") $ B.unpack b
|
|
||||||
B8.putStrLn ""
|
|
||||||
|
|
||||||
mkAuthHeader :: Text -> ByteString -> ByteString -> ByteString -> H.Header
|
mkAuthHeader :: Text -> ByteString -> ByteString -> ByteString -> H.Header
|
||||||
mkAuthHeader accessKey scope signedHeaderKeys sign =
|
mkAuthHeader accessKey scope signedHeaderKeys sign =
|
||||||
let authValue =
|
let authValue =
|
||||||
@ -116,6 +99,9 @@ mkAuthHeader accessKey scope signedHeaderKeys sign =
|
|||||||
data IsStreaming = IsStreamingLength Int64 | NotStreaming
|
data IsStreaming = IsStreamingLength Int64 | NotStreaming
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
|
amzSecurityToken :: ByteString
|
||||||
|
amzSecurityToken = "X-Amz-Security-Token"
|
||||||
|
|
||||||
-- | Given SignParams and request details, including request method,
|
-- | Given SignParams and request details, including request method,
|
||||||
-- request path, headers, query params and payload hash, generates an
|
-- request path, headers, query params and payload hash, generates an
|
||||||
-- updated set of headers, including the x-amz-date header and the
|
-- updated set of headers, including the x-amz-date header and the
|
||||||
@ -144,6 +130,7 @@ signV4QueryParams !sp !req =
|
|||||||
("X-Amz-Expires", maybe "" showBS expiry),
|
("X-Amz-Expires", maybe "" showBS expiry),
|
||||||
("X-Amz-SignedHeaders", signedHeaderKeys)
|
("X-Amz-SignedHeaders", signedHeaderKeys)
|
||||||
]
|
]
|
||||||
|
++ maybeToList ((amzSecurityToken,) . BA.convert <$> spSessionToken sp)
|
||||||
finalQP =
|
finalQP =
|
||||||
parseQuery (NC.queryString req)
|
parseQuery (NC.queryString req)
|
||||||
++ if isJust expiry
|
++ if isJust expiry
|
||||||
@ -185,6 +172,7 @@ signV4 !sp !req =
|
|||||||
| spService sp == ServiceS3
|
| spService sp == ServiceS3
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
++ maybeToList ((mk amzSecurityToken,) . BA.convert <$> spSessionToken sp)
|
||||||
|
|
||||||
-- 1. compute canonical request
|
-- 1. compute canonical request
|
||||||
reqHeaders = NC.requestHeaders req ++ extraHeaders
|
reqHeaders = NC.requestHeaders req ++ extraHeaders
|
||||||
@ -347,10 +335,11 @@ signV4PostPolicy !postPolicyJSON !sp =
|
|||||||
let stringToSign = Base64.encode postPolicyJSON
|
let stringToSign = Base64.encode postPolicyJSON
|
||||||
signingKey = getSigningKey sp
|
signingKey = getSigningKey sp
|
||||||
signature = computeSignature stringToSign signingKey
|
signature = computeSignature stringToSign signingKey
|
||||||
in Map.fromList
|
in Map.fromList $
|
||||||
[ ("x-amz-signature", signature),
|
[ ("x-amz-signature", signature),
|
||||||
("policy", stringToSign)
|
("policy", stringToSign)
|
||||||
]
|
]
|
||||||
|
++ maybeToList ((decodeUtf8 amzSecurityToken,) . BA.convert <$> spSessionToken sp)
|
||||||
|
|
||||||
chunkSizeConstant :: Int
|
chunkSizeConstant :: Int
|
||||||
chunkSizeConstant = 64 * 1024
|
chunkSizeConstant = 64 * 1024
|
||||||
@ -401,6 +390,7 @@ signV4Stream !payloadLength !sp !req =
|
|||||||
("content-length", showBS signedContentLength),
|
("content-length", showBS signedContentLength),
|
||||||
("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
|
("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
|
||||||
]
|
]
|
||||||
|
++ maybeToList ((mk amzSecurityToken,) . BA.convert <$> spSessionToken sp)
|
||||||
requestHeaders =
|
requestHeaders =
|
||||||
addContentEncoding $
|
addContentEncoding $
|
||||||
foldr setHeader (NC.requestHeaders req) extraHeaders
|
foldr setHeader (NC.requestHeaders req) extraHeaders
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -24,7 +24,6 @@ import qualified Data.ByteString.Lazy as LB
|
|||||||
import Data.CaseInsensitive (mk, original)
|
import Data.CaseInsensitive (mk, original)
|
||||||
import qualified Data.Conduit.Binary as CB
|
import qualified Data.Conduit.Binary as CB
|
||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
import qualified Data.List as List
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Text.Read (decimal)
|
import Data.Text.Read (decimal)
|
||||||
import Data.Time
|
import Data.Time
|
||||||
@ -37,14 +36,12 @@ import Network.HTTP.Conduit (Response)
|
|||||||
import qualified Network.HTTP.Conduit as NC
|
import qualified Network.HTTP.Conduit as NC
|
||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
import qualified Network.HTTP.Types.Header as Hdr
|
import qualified Network.HTTP.Types.Header as Hdr
|
||||||
import Network.Minio.Data
|
|
||||||
import Network.Minio.Data.ByteString
|
import Network.Minio.Data.ByteString
|
||||||
import Network.Minio.JsonParser (parseErrResponseJSON)
|
import Network.Minio.JsonParser (parseErrResponseJSON)
|
||||||
import Network.Minio.XmlParser (parseErrResponse)
|
import Network.Minio.XmlCommon (parseErrResponse)
|
||||||
import qualified System.IO as IO
|
import qualified System.IO as IO
|
||||||
import qualified UnliftIO as U
|
import qualified UnliftIO as U
|
||||||
import qualified UnliftIO.Async as A
|
import qualified UnliftIO.Async as A
|
||||||
import qualified UnliftIO.MVar as UM
|
|
||||||
|
|
||||||
allocateReadFile ::
|
allocateReadFile ::
|
||||||
(MonadUnliftIO m, R.MonadResource m) =>
|
(MonadUnliftIO m, R.MonadResource m) =>
|
||||||
@ -115,6 +112,16 @@ getMetadata :: [HT.Header] -> [(Text, Text)]
|
|||||||
getMetadata =
|
getMetadata =
|
||||||
map (\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y))
|
map (\(x, y) -> (decodeUtf8Lenient $ original x, decodeUtf8Lenient $ stripBS y))
|
||||||
|
|
||||||
|
-- | If the given header name has the @X-Amz-Meta-@ prefix, it is
|
||||||
|
-- stripped and a Just is returned.
|
||||||
|
userMetadataHeaderNameMaybe :: Text -> Maybe Text
|
||||||
|
userMetadataHeaderNameMaybe k =
|
||||||
|
let prefix = T.toCaseFold "X-Amz-Meta-"
|
||||||
|
n = T.length prefix
|
||||||
|
in if T.toCaseFold (T.take n k) == prefix
|
||||||
|
then Just (T.drop n k)
|
||||||
|
else Nothing
|
||||||
|
|
||||||
toMaybeMetadataHeader :: (Text, Text) -> Maybe (Text, Text)
|
toMaybeMetadataHeader :: (Text, Text) -> Maybe (Text, Text)
|
||||||
toMaybeMetadataHeader (k, v) =
|
toMaybeMetadataHeader (k, v) =
|
||||||
(,v) <$> userMetadataHeaderNameMaybe k
|
(,v) <$> userMetadataHeaderNameMaybe k
|
||||||
@ -128,6 +135,14 @@ getNonUserMetadataMap =
|
|||||||
. fst
|
. fst
|
||||||
)
|
)
|
||||||
|
|
||||||
|
addXAmzMetaPrefix :: Text -> Text
|
||||||
|
addXAmzMetaPrefix s
|
||||||
|
| isJust (userMetadataHeaderNameMaybe s) = s
|
||||||
|
| otherwise = "X-Amz-Meta-" <> s
|
||||||
|
|
||||||
|
mkHeaderFromMetadata :: [(Text, Text)] -> [HT.Header]
|
||||||
|
mkHeaderFromMetadata = map (\(x, y) -> (mk $ encodeUtf8 $ addXAmzMetaPrefix x, encodeUtf8 y))
|
||||||
|
|
||||||
-- | This function collects all headers starting with `x-amz-meta-`
|
-- | This function collects all headers starting with `x-amz-meta-`
|
||||||
-- and strips off this prefix, and returns a map.
|
-- and strips off this prefix, and returns a map.
|
||||||
getUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
|
getUserMetadataMap :: [(Text, Text)] -> H.HashMap Text Text
|
||||||
@ -135,6 +150,12 @@ getUserMetadataMap =
|
|||||||
H.fromList
|
H.fromList
|
||||||
. mapMaybe toMaybeMetadataHeader
|
. mapMaybe toMaybeMetadataHeader
|
||||||
|
|
||||||
|
getHostHeader :: (ByteString, Int) -> ByteString
|
||||||
|
getHostHeader (host_, port_) =
|
||||||
|
if port_ == 80 || port_ == 443
|
||||||
|
then host_
|
||||||
|
else host_ <> ":" <> show port_
|
||||||
|
|
||||||
getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime
|
getLastModifiedHeader :: [HT.Header] -> Maybe UTCTime
|
||||||
getLastModifiedHeader hs = do
|
getLastModifiedHeader hs = do
|
||||||
modTimebs <- decodeUtf8Lenient <$> lookupHeader Hdr.hLastModified hs
|
modTimebs <- decodeUtf8Lenient <$> lookupHeader Hdr.hLastModified hs
|
||||||
@ -262,42 +283,3 @@ chunkBSConduit (s : ss) = do
|
|||||||
| B.length bs == s -> C.yield bs >> chunkBSConduit ss
|
| B.length bs == s -> C.yield bs >> chunkBSConduit ss
|
||||||
| B.length bs > 0 -> C.yield bs
|
| B.length bs > 0 -> C.yield bs
|
||||||
| otherwise -> return ()
|
| otherwise -> return ()
|
||||||
|
|
||||||
-- | Select part sizes - the logic is that the minimum part-size will
|
|
||||||
-- be 64MiB.
|
|
||||||
selectPartSizes :: Int64 -> [(PartNumber, Int64, Int64)]
|
|
||||||
selectPartSizes size =
|
|
||||||
uncurry (List.zip3 [1 ..]) $
|
|
||||||
List.unzip $
|
|
||||||
loop 0 size
|
|
||||||
where
|
|
||||||
ceil :: Double -> Int64
|
|
||||||
ceil = ceiling
|
|
||||||
partSize =
|
|
||||||
max
|
|
||||||
minPartSize
|
|
||||||
( ceil $
|
|
||||||
fromIntegral size
|
|
||||||
/ fromIntegral maxMultipartParts
|
|
||||||
)
|
|
||||||
m = partSize
|
|
||||||
loop st sz
|
|
||||||
| st > sz = []
|
|
||||||
| st + m >= sz = [(st, sz - st)]
|
|
||||||
| otherwise = (st, m) : loop (st + m) sz
|
|
||||||
|
|
||||||
lookupRegionCache :: Bucket -> Minio (Maybe Region)
|
|
||||||
lookupRegionCache b = do
|
|
||||||
rMVar <- asks mcRegionMap
|
|
||||||
rMap <- UM.readMVar rMVar
|
|
||||||
return $ H.lookup b rMap
|
|
||||||
|
|
||||||
addToRegionCache :: Bucket -> Region -> Minio ()
|
|
||||||
addToRegionCache b region = do
|
|
||||||
rMVar <- asks mcRegionMap
|
|
||||||
UM.modifyMVar_ rMVar $ return . H.insert b region
|
|
||||||
|
|
||||||
deleteFromRegionCache :: Bucket -> Minio ()
|
|
||||||
deleteFromRegionCache b = do
|
|
||||||
rMVar <- asks mcRegionMap
|
|
||||||
UM.modifyMVar_ rMVar $ return . H.delete b
|
|
||||||
|
|||||||
65
src/Network/Minio/XmlCommon.hs
Normal file
65
src/Network/Minio/XmlCommon.hs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
--
|
||||||
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
|
--
|
||||||
|
-- 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.
|
||||||
|
--
|
||||||
|
|
||||||
|
module Network.Minio.XmlCommon where
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Data.Text.Read (decimal)
|
||||||
|
import Data.Time (UTCTime)
|
||||||
|
import Data.Time.Format.ISO8601 (iso8601ParseM)
|
||||||
|
import Lib.Prelude (throwIO)
|
||||||
|
import Network.Minio.Errors
|
||||||
|
import Text.XML (Name (Name), def, parseLBS)
|
||||||
|
import Text.XML.Cursor (Axis, Cursor, content, element, fromDocument, laxElement, ($/), (&/))
|
||||||
|
|
||||||
|
s3Name :: Text -> Text -> Name
|
||||||
|
s3Name ns s = Name s (Just ns) Nothing
|
||||||
|
|
||||||
|
uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
|
||||||
|
uncurry4 f (a, b, c, d) = f a b c d
|
||||||
|
|
||||||
|
uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g
|
||||||
|
uncurry6 f (a, b, c, d, e, g) = f a b c d e g
|
||||||
|
|
||||||
|
-- | Parse time strings from XML
|
||||||
|
parseS3XMLTime :: MonadIO m => Text -> m UTCTime
|
||||||
|
parseS3XMLTime t =
|
||||||
|
maybe (throwIO $ MErrVXmlParse $ "timestamp parse failure: " <> t) return $
|
||||||
|
iso8601ParseM $
|
||||||
|
toString t
|
||||||
|
|
||||||
|
parseDecimal :: (MonadIO m, Integral a) => Text -> m a
|
||||||
|
parseDecimal numStr =
|
||||||
|
either (throwIO . MErrVXmlParse . show) return $
|
||||||
|
fst <$> decimal numStr
|
||||||
|
|
||||||
|
parseDecimals :: (MonadIO m, Integral a) => [Text] -> m [a]
|
||||||
|
parseDecimals numStr = forM numStr parseDecimal
|
||||||
|
|
||||||
|
s3Elem :: Text -> Text -> Axis
|
||||||
|
s3Elem ns = element . s3Name ns
|
||||||
|
|
||||||
|
parseRoot :: (MonadIO m) => LByteString -> m Cursor
|
||||||
|
parseRoot =
|
||||||
|
either (throwIO . MErrVXmlParse . show) (return . fromDocument)
|
||||||
|
. parseLBS def
|
||||||
|
|
||||||
|
parseErrResponse :: (MonadIO m) => LByteString -> m ServiceErr
|
||||||
|
parseErrResponse xmldata = do
|
||||||
|
r <- parseRoot xmldata
|
||||||
|
let code = T.concat $ r $/ laxElement "Code" &/ content
|
||||||
|
message = T.concat $ r $/ laxElement "Message" &/ content
|
||||||
|
return $ toServiceErr code message
|
||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -25,6 +25,7 @@ where
|
|||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
|
import Network.Minio.XmlCommon
|
||||||
import Text.XML
|
import Text.XML
|
||||||
|
|
||||||
-- | Create a bucketConfig request body XML
|
-- | Create a bucketConfig request body XML
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -27,54 +27,18 @@ module Network.Minio.XmlParser
|
|||||||
parseErrResponse,
|
parseErrResponse,
|
||||||
parseNotification,
|
parseNotification,
|
||||||
parseSelectProgress,
|
parseSelectProgress,
|
||||||
parseSTSAssumeRoleResult,
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import qualified Data.ByteArray as BA
|
|
||||||
import qualified Data.ByteString.Lazy as LB
|
import qualified Data.ByteString.Lazy as LB
|
||||||
import qualified Data.HashMap.Strict as H
|
import qualified Data.HashMap.Strict as H
|
||||||
import Data.List (zip4, zip6)
|
import Data.List (zip4, zip6)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Text.Read (decimal)
|
|
||||||
import Data.Time
|
import Data.Time
|
||||||
import Data.Time.Format.ISO8601 (iso8601ParseM)
|
|
||||||
import Lib.Prelude
|
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Errors
|
import Network.Minio.XmlCommon
|
||||||
import Text.XML
|
|
||||||
import Text.XML.Cursor hiding (bool)
|
import Text.XML.Cursor hiding (bool)
|
||||||
|
|
||||||
-- | Helper functions.
|
|
||||||
uncurry4 :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
|
|
||||||
uncurry4 f (a, b, c, d) = f a b c d
|
|
||||||
|
|
||||||
uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g
|
|
||||||
uncurry6 f (a, b, c, d, e, g) = f a b c d e g
|
|
||||||
|
|
||||||
-- | Parse time strings from XML
|
|
||||||
parseS3XMLTime :: MonadIO m => Text -> m UTCTime
|
|
||||||
parseS3XMLTime t =
|
|
||||||
maybe (throwIO $ MErrVXmlParse $ "timestamp parse failure: " <> t) return $
|
|
||||||
iso8601ParseM $
|
|
||||||
toString t
|
|
||||||
|
|
||||||
parseDecimal :: (MonadIO m, Integral a) => Text -> m a
|
|
||||||
parseDecimal numStr =
|
|
||||||
either (throwIO . MErrVXmlParse . show) return $
|
|
||||||
fst <$> decimal numStr
|
|
||||||
|
|
||||||
parseDecimals :: (MonadIO m, Integral a) => [Text] -> m [a]
|
|
||||||
parseDecimals numStr = forM numStr parseDecimal
|
|
||||||
|
|
||||||
s3Elem :: Text -> Text -> Axis
|
|
||||||
s3Elem ns = element . s3Name ns
|
|
||||||
|
|
||||||
parseRoot :: (MonadIO m) => LByteString -> m Cursor
|
|
||||||
parseRoot =
|
|
||||||
either (throwIO . MErrVXmlParse . show) (return . fromDocument)
|
|
||||||
. parseLBS def
|
|
||||||
|
|
||||||
-- | Parse the response XML of a list buckets call.
|
-- | Parse the response XML of a list buckets call.
|
||||||
parseListBuckets :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m [BucketInfo]
|
parseListBuckets :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m [BucketInfo]
|
||||||
parseListBuckets xmldata = do
|
parseListBuckets xmldata = do
|
||||||
@ -219,13 +183,6 @@ parseListPartsResponse xmldata = do
|
|||||||
|
|
||||||
return $ ListPartsResult hasMore (listToMaybe nextPartNum) partInfos
|
return $ ListPartsResult hasMore (listToMaybe nextPartNum) partInfos
|
||||||
|
|
||||||
parseErrResponse :: (MonadIO m) => LByteString -> m ServiceErr
|
|
||||||
parseErrResponse xmldata = do
|
|
||||||
r <- parseRoot xmldata
|
|
||||||
let code = T.concat $ r $/ laxElement "Code" &/ content
|
|
||||||
message = T.concat $ r $/ laxElement "Message" &/ content
|
|
||||||
return $ toServiceErr code message
|
|
||||||
|
|
||||||
parseNotification :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m Notification
|
parseNotification :: (MonadReader env m, HasSvcNamespace env, MonadIO m) => LByteString -> m Notification
|
||||||
parseNotification xmldata = do
|
parseNotification xmldata = do
|
||||||
r <- parseRoot xmldata
|
r <- parseRoot xmldata
|
||||||
@ -271,102 +228,3 @@ parseSelectProgress xmldata = do
|
|||||||
<$> parseDecimal bScanned
|
<$> parseDecimal bScanned
|
||||||
<*> parseDecimal bProcessed
|
<*> parseDecimal bProcessed
|
||||||
<*> parseDecimal bReturned
|
<*> parseDecimal bReturned
|
||||||
|
|
||||||
-- <AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
|
||||||
-- <AssumeRoleResult>
|
|
||||||
-- <SourceIdentity>Alice</SourceIdentity>
|
|
||||||
-- <AssumedRoleUser>
|
|
||||||
-- <Arn>arn:aws:sts::123456789012:assumed-role/demo/TestAR</Arn>
|
|
||||||
-- <AssumedRoleId>ARO123EXAMPLE123:TestAR</AssumedRoleId>
|
|
||||||
-- </AssumedRoleUser>
|
|
||||||
-- <Credentials>
|
|
||||||
-- <AccessKeyId>ASIAIOSFODNN7EXAMPLE</AccessKeyId>
|
|
||||||
-- <SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY</SecretAccessKey>
|
|
||||||
-- <SessionToken>
|
|
||||||
-- AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW
|
|
||||||
-- LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd
|
|
||||||
-- QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU
|
|
||||||
-- 9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz
|
|
||||||
-- +scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==
|
|
||||||
-- </SessionToken>
|
|
||||||
-- <Expiration>2019-11-09T13:34:41Z</Expiration>
|
|
||||||
-- </Credentials>
|
|
||||||
-- <PackedPolicySize>6</PackedPolicySize>
|
|
||||||
-- </AssumeRoleResult>
|
|
||||||
-- <ResponseMetadata>
|
|
||||||
-- <RequestId>c6104cbe-af31-11e0-8154-cbc7ccf896c7</RequestId>
|
|
||||||
-- </ResponseMetadata>
|
|
||||||
-- </AssumeRoleResponse>
|
|
||||||
|
|
||||||
parseSTSAssumeRoleResult :: MonadIO m => ByteString -> Text -> m AssumeRoleResult
|
|
||||||
parseSTSAssumeRoleResult xmldata namespace = do
|
|
||||||
r <- parseRoot $ LB.fromStrict xmldata
|
|
||||||
let s3Elem' = s3Elem namespace
|
|
||||||
sourceIdentity =
|
|
||||||
T.concat $
|
|
||||||
r
|
|
||||||
$/ s3Elem' "AssumeRoleResult"
|
|
||||||
&/ s3Elem' "SourceIdentity"
|
|
||||||
&/ content
|
|
||||||
roleArn =
|
|
||||||
T.concat $
|
|
||||||
r
|
|
||||||
$/ s3Elem' "AssumeRoleResult"
|
|
||||||
&/ s3Elem' "AssumedRoleUser"
|
|
||||||
&/ s3Elem' "Arn"
|
|
||||||
&/ content
|
|
||||||
roleId =
|
|
||||||
T.concat $
|
|
||||||
r
|
|
||||||
$/ s3Elem' "AssumeRoleResult"
|
|
||||||
&/ s3Elem' "AssumedRoleUser"
|
|
||||||
&/ s3Elem' "AssumedRoleId"
|
|
||||||
&/ content
|
|
||||||
|
|
||||||
convSB :: Text -> BA.ScrubbedBytes
|
|
||||||
convSB = BA.convert . (encodeUtf8 :: Text -> ByteString)
|
|
||||||
|
|
||||||
credsInfo = do
|
|
||||||
cr <-
|
|
||||||
maybe (Left $ MErrVXmlParse "No Credentials Element found") Right $
|
|
||||||
listToMaybe $
|
|
||||||
r $/ s3Elem' "AssumeRoleResult" &/ s3Elem' "Credentials"
|
|
||||||
let cur = fromNode $ node cr
|
|
||||||
return
|
|
||||||
( CredentialValue
|
|
||||||
{ cvAccessKey =
|
|
||||||
coerce $
|
|
||||||
T.concat $
|
|
||||||
cur $/ s3Elem' "AccessKeyId" &/ content,
|
|
||||||
cvSecretKey =
|
|
||||||
coerce $
|
|
||||||
convSB $
|
|
||||||
T.concat $
|
|
||||||
cur
|
|
||||||
$/ s3Elem' "SecretAccessKey"
|
|
||||||
&/ content,
|
|
||||||
cvSessionToken =
|
|
||||||
Just $
|
|
||||||
coerce $
|
|
||||||
convSB $
|
|
||||||
T.concat $
|
|
||||||
cur
|
|
||||||
$/ s3Elem' "SessionToken"
|
|
||||||
&/ content
|
|
||||||
},
|
|
||||||
T.concat $ cur $/ s3Elem' "Expiration" &/ content
|
|
||||||
)
|
|
||||||
creds <- either throwIO pure credsInfo
|
|
||||||
expiry <- parseS3XMLTime $ snd creds
|
|
||||||
let roleCredentials =
|
|
||||||
AssumeRoleCredentials
|
|
||||||
{ arcCredentials = fst creds,
|
|
||||||
arcExpiration = expiry
|
|
||||||
}
|
|
||||||
return
|
|
||||||
AssumeRoleResult
|
|
||||||
{ arrSourceIdentity = sourceIdentity,
|
|
||||||
arrAssumedRoleArn = roleArn,
|
|
||||||
arrAssumedRoleId = roleId,
|
|
||||||
arrRoleCredentials = roleCredentials
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017-2019 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -32,6 +30,7 @@ import qualified Network.HTTP.Client.MultipartFormData as Form
|
|||||||
import qualified Network.HTTP.Conduit as NC
|
import qualified Network.HTTP.Conduit as NC
|
||||||
import qualified Network.HTTP.Types as HT
|
import qualified Network.HTTP.Types as HT
|
||||||
import Network.Minio
|
import Network.Minio
|
||||||
|
import Network.Minio.Credentials (Creds (CredsStatic))
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.Data.Crypto
|
import Network.Minio.Data.Crypto
|
||||||
import Network.Minio.S3API
|
import Network.Minio.S3API
|
||||||
@ -77,15 +76,35 @@ mkRandFile size = do
|
|||||||
funTestBucketPrefix :: Text
|
funTestBucketPrefix :: Text
|
||||||
funTestBucketPrefix = "miniohstest-"
|
funTestBucketPrefix = "miniohstest-"
|
||||||
|
|
||||||
loadTestServer :: IO ConnectInfo
|
loadTestServerConnInfo :: IO ConnectInfo
|
||||||
loadTestServer = do
|
loadTestServerConnInfo = do
|
||||||
val <- Env.lookupEnv "MINIO_LOCAL"
|
val <- Env.lookupEnv "MINIO_LOCAL"
|
||||||
isSecure <- Env.lookupEnv "MINIO_SECURE"
|
isSecure <- Env.lookupEnv "MINIO_SECURE"
|
||||||
return $ case (val, isSecure) of
|
return $ case (val, isSecure) of
|
||||||
(Just _, Just _) -> setCreds (Credentials "minio" "minio123") "https://localhost:9000"
|
(Just _, Just _) -> setCreds (CredentialValue "minio" "minio123" mempty) "https://localhost:9000"
|
||||||
(Just _, Nothing) -> setCreds (Credentials "minio" "minio123") "http://localhost:9000"
|
(Just _, Nothing) -> setCreds (CredentialValue "minio" "minio123" mempty) "http://localhost:9000"
|
||||||
(Nothing, _) -> minioPlayCI
|
(Nothing, _) -> minioPlayCI
|
||||||
|
|
||||||
|
loadTestServerConnInfoSTS :: IO ConnectInfo
|
||||||
|
loadTestServerConnInfoSTS = do
|
||||||
|
val <- Env.lookupEnv "MINIO_LOCAL"
|
||||||
|
isSecure <- Env.lookupEnv "MINIO_SECURE"
|
||||||
|
let cv = CredentialValue "minio" "minio123" mempty
|
||||||
|
assumeRole =
|
||||||
|
STSAssumeRole
|
||||||
|
{ sarCredentials = cv,
|
||||||
|
sarOptions = defaultSTSAssumeRoleOptions
|
||||||
|
}
|
||||||
|
case (val, isSecure) of
|
||||||
|
(Just _, Just _) -> setSTSCredential assumeRole "https://localhost:9000"
|
||||||
|
(Just _, Nothing) -> setSTSCredential assumeRole "http://localhost:9000"
|
||||||
|
(Nothing, _) -> do
|
||||||
|
cv' <- case connectCreds minioPlayCI of
|
||||||
|
CredsStatic c -> return c
|
||||||
|
_ -> error "unexpected play creds"
|
||||||
|
let assumeRole' = assumeRole {sarCredentials = cv'}
|
||||||
|
setSTSCredential assumeRole' minioPlayCI
|
||||||
|
|
||||||
funTestWithBucket ::
|
funTestWithBucket ::
|
||||||
TestName ->
|
TestName ->
|
||||||
(([Char] -> Minio ()) -> Bucket -> Minio ()) ->
|
(([Char] -> Minio ()) -> Bucket -> Minio ()) ->
|
||||||
@ -95,7 +114,7 @@ funTestWithBucket t minioTest = testCaseSteps t $ \step -> do
|
|||||||
bktSuffix <- liftIO $ generate $ Q.vectorOf 10 (Q.choose ('a', 'z'))
|
bktSuffix <- liftIO $ generate $ Q.vectorOf 10 (Q.choose ('a', 'z'))
|
||||||
let b = T.concat [funTestBucketPrefix, T.pack bktSuffix]
|
let b = T.concat [funTestBucketPrefix, T.pack bktSuffix]
|
||||||
liftStep = liftIO . step
|
liftStep = liftIO . step
|
||||||
connInfo <- loadTestServer
|
connInfo <- loadTestServerConnInfo
|
||||||
ret <- runMinio connInfo $ do
|
ret <- runMinio connInfo $ do
|
||||||
liftStep $ "Creating bucket for test - " ++ t
|
liftStep $ "Creating bucket for test - " ++ t
|
||||||
foundBucket <- bucketExists b
|
foundBucket <- bucketExists b
|
||||||
@ -105,6 +124,17 @@ funTestWithBucket t minioTest = testCaseSteps t $ \step -> do
|
|||||||
deleteBucket b
|
deleteBucket b
|
||||||
isRight ret @? ("Functional test " ++ t ++ " failed => " ++ show ret)
|
isRight ret @? ("Functional test " ++ t ++ " failed => " ++ show ret)
|
||||||
|
|
||||||
|
connInfoSTS <- loadTestServerConnInfoSTS
|
||||||
|
let t' = t ++ " (with AssumeRole Credentials)"
|
||||||
|
ret' <- runMinio connInfoSTS $ do
|
||||||
|
liftStep $ "Creating bucket for test - " ++ t'
|
||||||
|
foundBucket <- bucketExists b
|
||||||
|
liftIO $ foundBucket @?= False
|
||||||
|
makeBucket b Nothing
|
||||||
|
minioTest liftStep b
|
||||||
|
deleteBucket b
|
||||||
|
isRight ret' @? ("Functional test " ++ t' ++ " failed => " ++ show ret')
|
||||||
|
|
||||||
liveServerUnitTests :: TestTree
|
liveServerUnitTests :: TestTree
|
||||||
liveServerUnitTests =
|
liveServerUnitTests =
|
||||||
testGroup
|
testGroup
|
||||||
@ -125,7 +155,8 @@ liveServerUnitTests =
|
|||||||
presignedUrlFunTest,
|
presignedUrlFunTest,
|
||||||
presignedPostPolicyFunTest,
|
presignedPostPolicyFunTest,
|
||||||
bucketPolicyFunTest,
|
bucketPolicyFunTest,
|
||||||
getNPutSSECTest
|
getNPutSSECTest,
|
||||||
|
assumeRoleRequestTest
|
||||||
]
|
]
|
||||||
|
|
||||||
basicTests :: TestTree
|
basicTests :: TestTree
|
||||||
@ -1187,3 +1218,30 @@ getNPutSSECTest =
|
|||||||
step "Cleanup"
|
step "Cleanup"
|
||||||
deleteObject bucket obj
|
deleteObject bucket obj
|
||||||
else step "Skipping encryption test as server is not using TLS"
|
else step "Skipping encryption test as server is not using TLS"
|
||||||
|
|
||||||
|
assumeRoleRequestTest :: TestTree
|
||||||
|
assumeRoleRequestTest = testCaseSteps "Assume Role STS API" $ \step -> do
|
||||||
|
step "Load credentials"
|
||||||
|
val <- Env.lookupEnv "MINIO_LOCAL"
|
||||||
|
isSecure <- Env.lookupEnv "MINIO_SECURE"
|
||||||
|
let localMinioCred = Just $ CredentialValue "minio" "minio123" mempty
|
||||||
|
playCreds =
|
||||||
|
case connectCreds minioPlayCI of
|
||||||
|
CredsStatic c -> Just c
|
||||||
|
_ -> Nothing
|
||||||
|
(cvMay, loc) =
|
||||||
|
case (val, isSecure) of
|
||||||
|
(Just _, Just _) -> (localMinioCred, "https://localhost:9000")
|
||||||
|
(Just _, Nothing) -> (localMinioCred, "http://localhost:9000")
|
||||||
|
(Nothing, _) -> (playCreds, "https://play.min.io:9000")
|
||||||
|
cv <- maybe (assertFailure "bad creds") return cvMay
|
||||||
|
let assumeRole =
|
||||||
|
STSAssumeRole cv $
|
||||||
|
defaultSTSAssumeRoleOptions
|
||||||
|
{ saroLocation = Just "us-east-1",
|
||||||
|
saroEndpoint = Just loc
|
||||||
|
}
|
||||||
|
step "AssumeRole request"
|
||||||
|
res <- requestSTSCredential assumeRole
|
||||||
|
let v = credentialValueText $ fst res
|
||||||
|
print (v, snd res)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- MinIO Haskell SDK, (C) 2017, 2018 MinIO, Inc.
|
-- MinIO Haskell SDK, (C) 2017-2023 MinIO, Inc.
|
||||||
--
|
--
|
||||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
-- you may not use this file except in compliance with the License.
|
-- you may not use this file except in compliance with the License.
|
||||||
@ -20,7 +20,6 @@ import Lib.Prelude
|
|||||||
import Network.Minio.API.Test
|
import Network.Minio.API.Test
|
||||||
import Network.Minio.CopyObject
|
import Network.Minio.CopyObject
|
||||||
import Network.Minio.Data
|
import Network.Minio.Data
|
||||||
import Network.Minio.PutObject
|
|
||||||
import Network.Minio.Utils.Test
|
import Network.Minio.Utils.Test
|
||||||
import Network.Minio.XmlGenerator.Test
|
import Network.Minio.XmlGenerator.Test
|
||||||
import Network.Minio.XmlParser.Test
|
import Network.Minio.XmlParser.Test
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user