aws-cloudfront-signed-cookies (empty) → 0.1.0.0
raw patch · 12 files changed
+842/−0 lines, 12 filesdep +aesondep +asn1-encodingdep +asn1-typessetup-changed
Dependencies added: aeson, asn1-encoding, asn1-types, aws-cloudfront-signed-cookies, base, base64-bytestring, bytestring, cookie, cryptonite, optparse-applicative, pem, text, time, unordered-containers, vector
Files
- Setup.hs +2/−0
- aws-cloudfront-signed-cookies.cabal +67/−0
- executables/aws-cloudfront-signed-cookies.hs +3/−0
- library/Network/AWS/CloudFront/SignedCookies.hs +124/−0
- library/Network/AWS/CloudFront/SignedCookies/CLI.hs +94/−0
- library/Network/AWS/CloudFront/SignedCookies/Crypto.hs +76/−0
- library/Network/AWS/CloudFront/SignedCookies/Crypto/Internal.hs +84/−0
- library/Network/AWS/CloudFront/SignedCookies/Encoding.hs +51/−0
- library/Network/AWS/CloudFront/SignedCookies/Policy.hs +121/−0
- library/Network/AWS/CloudFront/SignedCookies/Types.hs +125/−0
- license.txt +18/−0
- readme.md +77/−0
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aws-cloudfront-signed-cookies.cabal view
@@ -0,0 +1,67 @@+name: aws-cloudfront-signed-cookies+version: 0.1.0.0++synopsis: Generate signed cookies for AWS CloudFront+category: Network, AWS, Cloud++description: One way to [serve private content through AWS CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html) is to use [signed cookies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html). This package helps you generate signed cookies [using a custom IAM policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html) which may include a range of time for which the cookie is valid and an IP address restriction.++homepage: https://github.com/typeclasses/aws-cloudfront-signed-cookies+bug-reports: https://github.com/typeclasses/aws-cloudfront-signed-cookies/issues++author: Chris Martin+maintainer: Chris Martin, Julie Moronuki++copyright: 2018 Typeclass Consulting, LLC+license: MIT+license-file: license.txt++build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ readme.md++source-repository head+ type: git+ location: https://github.com/typeclasses/aws-cloudfront-signed-cookies++library+ default-language: Haskell2010+ hs-source-dirs: library++ exposed-modules:+ Network.AWS.CloudFront.SignedCookies+ , Network.AWS.CloudFront.SignedCookies.CLI+ , Network.AWS.CloudFront.SignedCookies.Crypto+ , Network.AWS.CloudFront.SignedCookies.Encoding+ , Network.AWS.CloudFront.SignedCookies.Policy+ , Network.AWS.CloudFront.SignedCookies.Types++ other-modules:+ Network.AWS.CloudFront.SignedCookies.Crypto.Internal++ build-depends:+ aeson+ , asn1-encoding+ , asn1-types+ , base >=4.7 && <5+ , base64-bytestring+ , bytestring+ , cookie+ , cryptonite+ , optparse-applicative+ , pem+ , text+ , time+ , unordered-containers+ , vector++executable aws-cloudfront-signed-cookies+ default-language: Haskell2010+ hs-source-dirs: executables+ main-is: aws-cloudfront-signed-cookies.hs++ build-depends:+ aws-cloudfront-signed-cookies+ , base >=4.7 && <5
+ executables/aws-cloudfront-signed-cookies.hs view
@@ -0,0 +1,3 @@+module Main (main) where++import Network.AWS.CloudFront.SignedCookies.CLI (main)
+ library/Network/AWS/CloudFront/SignedCookies.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, TypeApplications #-}++-- |+-- Example usage:+--+-- > {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+-- >+-- > import Network.AWS.CloudFront.SignedCookies+-- >+-- > import qualified Data.Text.IO+-- >+-- > main :: IO ()+-- > main = do+-- >+-- > -- Construct an IAM policy that expires three days from now+-- > policy :: Policy <- simplePolicy+-- > (Resource "https://example.com/secrets/*")+-- > (Lifespan (3 * nominalDay))+-- >+-- > -- Parse the .pem file to get the private key+-- > key :: PrivateKey <- readPrivateKeyPemFile+-- > (PemFilePath "./pk-APKAIATXN3RCIOVT5WRQ.pem")+-- >+-- > -- Construct signed cookies+-- > cookies :: CookiesText <- createSignedCookies+-- > (KeyPairId "APKAIATXN3RCIOVT5WRQ") key policy+-- >+-- > Data.Text.IO.putStrLn (renderCookiesText cookies)+--+-- Output:+--+-- > Cookie: CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc29...+-- > Cookie: CloudFront-Signature=wMN6V3Okxk7sdSPZeebMh-wo...+-- > Cookie: CloudFront-Key-Pair-Id=APKAIATXN3RCIOVT5WRQ+--+-- You can see a very similar example in action in the+-- "Network.AWS.CloudFront.SignedCookies.CLI" module.++module Network.AWS.CloudFront.SignedCookies++ (+ -- * Creating signed cookies+ createSignedCookies++ -- ** Defining a CloudFront policy+ , simplePolicy+ , Policy (..)+ , Resource (..)+ , Lifespan (..)+ , StartTime (..)+ , EndTime (..)+ , IpAddress (..)++ -- ** Getting your private key+ , readPrivateKeyPemFile+ , PemFilePath (..)+ , KeyPairId (..)+ , PrivateKey++ -- * Micellaneous++ -- ** Cookies+ , CookiesText+ , renderCookiesText++ -- ** Time+ , NominalDiffTime+ , POSIXTime+ , nominalDay+ , getPOSIXTime++ -- ** Text+ , Text++ ) where++import Network.AWS.CloudFront.SignedCookies.Crypto+import Network.AWS.CloudFront.SignedCookies.Encoding+import Network.AWS.CloudFront.SignedCookies.Policy+import Network.AWS.CloudFront.SignedCookies.Types++-- base+import Data.Coerce (coerce)+import Data.Semigroup ((<>))++-- text+import qualified Data.Text as Text++-- time+import Data.Time.Clock.POSIX (getPOSIXTime)+import Data.Time.Clock (nominalDay)++createSignedCookies+ :: KeyPairId+ -- ^ A CloudFront key pair ID, which must be associated with a+ -- trusted signer in the CloudFront distribution that you+ -- specify in the 'policyResource'.+ -> PrivateKey+ -- ^ The private key associated with the 'KeyPairId'. See+ -- 'readPrivateKeyPemFile' for how to read this key from a+ -- @.pem@ file you downloaded from AWS.+ -> Policy+ -- ^ The policy specifies what resource is being granted, for what+ -- time period, and to what IP addresses. Construct a policy+ -- using the 'Policy' constructor or with the 'simplePolicy'+ -- function.+ -> IO CookiesText++createSignedCookies kpid key policy = do++ let+ policyBS :: ByteString = policyJSON policy++ sigBS <- sign key policyBS++ pure+ [ ( "CloudFront-Policy" , base64Encode policyBS )+ , ( "CloudFront-Signature" , base64Encode sigBS )+ , ( "CloudFront-Key-Pair-Id" , coerce @KeyPairId @Text kpid )+ ]++renderCookiesText :: CookiesText -> Text+renderCookiesText =+ Text.unlines . map (\(k, v) -> "Cookie: " <> k <> "=" <> v)
+ library/Network/AWS/CloudFront/SignedCookies/CLI.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE FlexibleContexts, OverloadedStrings, RecordWildCards #-}++-- | Command-line interface for generating AWS CloudFront signed cookies.++module Network.AWS.CloudFront.SignedCookies.CLI+ ( main, Opts (..), optsParser+ ) where++import Network.AWS.CloudFront.SignedCookies++-- base+import Data.Coerce (Coercible, coerce)+import Data.Foldable (for_)+import Data.Semigroup ((<>))++-- optparse-applicative+import qualified Options.Applicative as Opt++-- text+import qualified Data.Text as Text+import qualified Data.Text.IO as Text++-- time+import Data.Time.Clock (nominalDay)++-- | The entry point for the AWS CloudFront cookie-signing+-- command-line interface.++main :: IO ()+main = do++ -- Parse the command-line arguments+ Opts{..} <- Opt.execParser $+ Opt.info (Opt.helper <*> optsParser) $+ Opt.header "Generator of signed cookies for AWS CloudFront"++ -- Construct an IAM policy+ policy <- simplePolicy opt_resource opt_lifespan++ -- Parse the .pem file to get the private key+ key <- readPrivateKeyPemFile opt_pemFilePath++ -- Construct signed cookies+ cookies <- createSignedCookies opt_keyPairId key policy++ -- Print the cookies to stdout+ (Text.putStr . renderCookiesText) cookies++-- | The parse result for the command-line arguments.++data Opts =+ Opts+ { opt_pemFilePath :: PemFilePath+ -- ^ Location in the filesystem where a .pem file+ -- containing an RSA secret key can be found+ , opt_keyPairId :: KeyPairId+ -- ^ CloudFront key pair ID for the key pair that+ -- you are using to generate signature+ , opt_resource :: Resource+ -- ^ URL that the policy will grant access to,+ -- optionally containing asterisks for wildcards+ , opt_lifespan :: Lifespan+ -- ^ Amount of time until the policy expires+ }++-- | Parser for all of the command-line arguments.+--+-- See "Options.Applicative", 'Opt.info', and 'Opt.execParser'+-- to learn how to use a 'Opt.Parser'.++optsParser :: Opt.Parser Opts+optsParser =+ Opts+ <$> text "pem-file" "Location in the filesystem where a .pem file \+ \containing an RSA secret key can be found"++ <*> text "key-pair-id" "CloudFront key pair ID for the key pair that \+ \you are using to generate signature"++ <*> text "resource" "URL that the policy will grant access to, \+ \optionally containing asterisks for wildcards"++ <*> days "days" "Integer number of days until the policy expires"++ where+ text :: Coercible Text a => String -> String -> Opt.Parser a+ text long help = fmap (coerce . Text.pack) $ Opt.strOption $ mod long help++ days :: Coercible NominalDiffTime a => String -> String -> Opt.Parser a+ days long help = fmap (coerce . (* nominalDay) . fromInteger) $+ Opt.option Opt.auto $ mod long help++ mod :: Opt.HasName f => String -> String -> Opt.Mod f a+ mod long help = Opt.long long <> Opt.help help
+ library/Network/AWS/CloudFront/SignedCookies/Crypto.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}++module Network.AWS.CloudFront.SignedCookies.Crypto+ (+ -- * Reading the private key+ readPrivateKeyPemFile++ -- * Generating signatures+ , sign++ -- * Types+ , PrivateKey+ , ByteString++ ) where++import Network.AWS.CloudFront.SignedCookies.Crypto.Internal+import Network.AWS.CloudFront.SignedCookies.Types++-- asn1-encoding+import Data.ASN1.BinaryEncoding (DER (DER))+import Data.ASN1.Encoding (decodeASN1')+import Data.ASN1.Error (ASN1Error)++-- asn1-types+import Data.ASN1.Types (ASN1)++-- bytestring+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS++-- cryptonite+import Crypto.PubKey.RSA (PrivateKey)+import qualified Crypto.PubKey.RSA.PKCS15 as RSA+import Crypto.Hash.Algorithms (SHA1 (SHA1))++-- pem+import qualified Data.PEM as PEM++-- text+import qualified Data.Text as Text++-- | Construct the signature that will go into the+-- @CloudFront-Signature@ cookie.++sign+ :: PrivateKey -- ^ The RSA private key that you read from the @.pem@ file+ -> ByteString -- ^ The JSON representation of the 'Policy'+ -- (see "Network.AWS.CloudFront.SignedCookies.Policy")+ -> IO ByteString++sign key bs =+ RSA.signSafer (Just SHA1) key bs >>= either (fail . show) pure++-- | Read an RSA private key from a @.pem@ file you downloaded from AWS.++readPrivateKeyPemFile+ :: PemFilePath -- ^ The filesystem path of the @.pem@ file+ -> IO PrivateKey++readPrivateKeyPemFile (PemFilePath path) = do++ lbs <- BS.readFile (Text.unpack path)++ pemSections <- either fail pure (PEM.pemParseBS lbs)++ pemBs <- PEM.pemContent <$> case pemSections of+ [x] -> pure x+ xs ->+ let msg = "Expected exactly 1 PEM section but found " +++ show @Int (length xs)+ in fail msg++ asn1s :: [ASN1] <- either (fail . show) pure (decodeASN1' DER pemBs)++ either fail pure (rsaPrivateKeyFromASN1 asn1s)
+ library/Network/AWS/CloudFront/SignedCookies/Crypto/Internal.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE LambdaCase, RecordWildCards, TypeApplications #-}++{-++This module is based on a part of snaplet-saml. If my hs-certificate PR gets merged, then we probably ought to remove this and use hs-certificate instead.++https://github.com/duairc/snaplet-saml/blob/dd89116f8f0b3d755d2feea73f2c40beb78c409c/src/Data/X509/IO.hs#L87-L102++https://github.com/vincenthz/hs-certificate/pull/98++-}++module Network.AWS.CloudFront.SignedCookies.Crypto.Internal+ ( rsaPrivateKeyFromASN1+ ) where++import Data.Bifunctor (first)+import Data.Semigroup ((<>))++import Data.ASN1.Types+import Data.ASN1.Encoding+import Data.ASN1.BinaryEncoding+import Data.ASN1.BitArray++import qualified Crypto.PubKey.RSA as RSA++rsaPrivateKeyFromASN1 :: [ASN1] -> Either String RSA.PrivateKey++rsaPrivateKeyFromASN1 =+ \case+ ( Start Sequence+ : IntVal version+ : IntVal n+ : IntVal e+ : IntVal d+ : IntVal p+ : IntVal q+ : IntVal dP+ : IntVal dQ+ : IntVal qinv+ : End Sequence+ : [] ) ->++ case version of+ 0 -> Right (buildKey Params{..})+ v -> Left $ "rsaPrivateKeyFromASN1: unexpected version " <>+ show @Integer version++ _ -> Left "rsaPrivateKeyFromASN1: unexpected format"++-- https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem+data Params =+ Params+ { n :: Integer -- modulus+ , e :: Integer -- publicExponent+ , d :: Integer -- privateExponent+ , p :: Integer -- prime1+ , q :: Integer -- prime2+ , dP :: Integer -- exponent1 = d mod (p-1)+ , dQ :: Integer -- exponent2 = d mod (q-1)+ , qinv :: Integer -- coefficient = (inverse of q) mod p+ }++buildKey :: Params -> RSA.PrivateKey+buildKey Params{..} =+ let+ size = head [i | i <- [1..], 2 ^ (i * 8) > n]++ pub = RSA.PublicKey+ { public_size = size+ , public_n = n+ , public_e = e+ }++ in+ RSA.PrivateKey+ { RSA.private_pub = pub+ , RSA.private_d = d+ , RSA.private_p = p+ , RSA.private_q = q+ , RSA.private_dP = dP+ , RSA.private_dQ = dQ+ , RSA.private_qinv = qinv+ }
+ library/Network/AWS/CloudFront/SignedCookies/Encoding.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE LambdaCase #-}++module Network.AWS.CloudFront.SignedCookies.Encoding+ ( base64Encode+ ) where++import Network.AWS.CloudFront.SignedCookies.Types++-- base64-bytestring+import qualified Data.ByteString.Base64 as Base64++-- text+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text++{- |++The base 64 encoding scheme used for AWS CloudFront signed cookies.+We use this to encode both the policy and the signature.++Excerpts from [Setting Signed Cookies Using a Custom Policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html):++1. Base64-encode the string using MIME base64 encoding. For more information, see+ [Section 6.8, Base64 Content-Transfer-Encoding](https://tools.ietf.org/html/rfc2045#section-6.8)+ in RFC 2045, MIME (Multipurpose Internet Mail Extensions) Part One:+ Format of Internet Message Bodies.++2. Replace characters that are invalid in a URL query string with+ characters that are valid. The following table lists invalid and+ valid characters.++ > Replace these With these+ > invalid characters valid characters+ > ------------------ ----------------+ > + - (hyphen)+ > = _ (underscore)+ > / ~ (tilde)++-}++base64Encode :: ByteString -> Text+base64Encode =+ Text.map charSubstitution . Text.decodeUtf8 . Base64.encode++charSubstitution :: Char -> Char+charSubstitution =+ \case+ '+' -> '-'+ '=' -> '_'+ '/' -> '~'+ x -> x
+ library/Network/AWS/CloudFront/SignedCookies/Policy.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}++module Network.AWS.CloudFront.SignedCookies.Policy+ (+ -- * Defining a policy+ Policy (..)+ , simplePolicy++ -- * Components of a policy+ , Resource (..)+ , StartTime (..)+ , EndTime (..)+ , Lifespan (..)+ , IpAddress (..)++ -- * JSON representation+ , policyJSON++ ) where++import Network.AWS.CloudFront.SignedCookies.Types++-- aeson+import qualified Data.Aeson as A++-- base+import Data.Semigroup ((<>))++-- bytestring+import qualified Data.ByteString.Lazy as LBS++-- time+import Data.Time.Clock.POSIX (getPOSIXTime)++-- unordered-containers+import qualified Data.HashMap.Strict as Map++-- vector+import qualified Data.Vector as Vec++{- |++Encode a 'Policy' as JSON, with no whitespace, as AWS requires.++Excerpt from [Setting Signed Cookies Using a Custom Policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html):++* "Remove all whitespace (including tabs and newline characters) from the policy statement."++-}++policyJSON :: Policy -> ByteString+policyJSON =+ LBS.toStrict . A.encode . policyValue++policyValue :: Policy -> A.Value+policyValue policy =+ A.Object $ Map.singleton "Statement" $+ A.Array $ Vec.singleton $+ A.Object $ "Resource" .= resourceValue policy <>+ "Condition" .= conditionValue policy++resourceValue :: Policy -> A.Value+resourceValue (Policy (Resource x) _ _ _) = A.String x++conditionValue :: Policy -> A.Value+conditionValue (Policy _ start end ip) =+ A.Object $ startCondition <> endCondition <> ipCondition+ where++ startCondition :: A.Object =+ case start of+ StartImmediately -> mempty+ StartTime x -> "DateGreaterThan" .= posixTimeValue x++ endCondition :: A.Object =+ case end of+ EndTime x -> "DateLessThan" .= posixTimeValue x++ ipCondition :: A.Object =+ case ip of+ AnyIp -> mempty+ IpAddress x -> "IpAddress" .= sourceIpValue x++posixTimeValue :: POSIXTime -> A.Value+posixTimeValue =+ A.Object . ("AWS:EpochTime" .=) . A.Number . fromInteger . round++sourceIpValue :: Text -> A.Value+sourceIpValue =+ A.Object . ("AWS:SourceIp" .=) . A.String++(.=) :: Text -> A.Value -> A.Object+(.=) = Map.singleton++{- |++This function provides one convenient way to construct a simple 'Policy'.++For the full set of policy options, use the 'Policy' constructor directly.++-}++simplePolicy+ :: Resource -- ^ URL that the policy will grant access to,+ -- optionally containing asterisks for wildcards+ -> Lifespan -- ^ How long from now the credentials expire+ -> IO Policy++simplePolicy res life = do++ now :: POSIXTime <- getPOSIXTime++ let end = case life of Lifespan x -> EndTime (now + x)++ pure+ Policy+ { policyResource = res+ , policyEnd = end+ , policyStart = StartImmediately+ , policyIpAddress = AnyIp+ }
+ library/Network/AWS/CloudFront/SignedCookies/Types.hs view
@@ -0,0 +1,125 @@+module Network.AWS.CloudFront.SignedCookies.Types++ (+ -- * Policy+ Policy (..), Resource (..)++ -- * Crypto+ , PemFilePath (..), KeyPairId (..)++ -- * Cookies+ , CookiesText, SetCookie, CookieDomain (..), CookiePath (..)++ -- * Time+ , NominalDiffTime, POSIXTime, Lifespan (..), StartTime (..), EndTime (..)++ -- * IP address+ , IpAddress (..)++ -- * Strings+ , Text, ByteString++ -- * Crypto+ , PrivateKey++ ) where++-- bytestring+import Data.ByteString (ByteString)++-- cookie+import Web.Cookie (CookiesText, SetCookie)++-- cryptonite+import Crypto.PubKey.RSA (PrivateKey (..), PublicKey (..))++-- text+import Data.Text (Text)++-- time+import Data.Time.Clock (NominalDiffTime)+import Data.Time.Clock.POSIX (POSIXTime)++{- |++Location in the filesystem where a .pem file containing an+RSA secret key can be found.++The filename downloaded from AWS looks like this:++* @"pk-APKAIATX@@N3RCIOVT5WRQ.pem"@++-}+newtype PemFilePath = PemFilePath Text++{- |++CloudFront key pair ID for the key pair that you are using to+generate signature.++The key pair ID can be found in the name of key files that you+download, and looks like this:++* @APKAIATXN3@@RCIOVT5WRQ@++-}+newtype KeyPairId = KeyPairId Text++{- |++Examples:++* @"d123example.cl@@oudfront.net"@+* @"cloudfrontalia@@s.example.com"@++-}+newtype CookieDomain = CookieDomain Text++-- | Usually @"/"@+newtype CookiePath = CookiePath Text++{- |++URL that a policy will grant access to, optionally containing+asterisks for wildcards.++Examples:++* @"https:\/@@\/d123example.cloudfront.net/index.html"@+* @"https:\/@@\/d123example.cloudfront.net/*.jpeg"@++-}+newtype Resource = Resource Text++-- | How long from now the credentials expire+newtype Lifespan = Lifespan NominalDiffTime++-- | The time at which credentials begin to take effect+data StartTime = StartImmediately | StartTime POSIXTime++-- | The time at which credentials expire+newtype EndTime = EndTime POSIXTime++-- | The IP address or address range of clients allowed to make requests+data IpAddress = AnyIp | IpAddress Text++{- |++A policy specifies what resource is being granted, for what time period,+and to what IP addresses.++For AWS's documentation on what going into a CloudFront policy statement, see [Values That You Specify in the Policy Statement for a Custom Policy for Signed Cookies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html#private-content-custom-policy-statement-cookies-values).++-}+data Policy =+ Policy+ { policyResource :: Resource+ -- ^ URL that the policy will grant access to,+ -- optionally containing asterisks for wildcards+ , policyStart :: StartTime+ -- ^ The time at which credentials begin to take effect+ , policyEnd :: EndTime+ -- ^ The time at which credentials expire+ , policyIpAddress :: IpAddress+ -- ^ The IP address or address range of clients allowed to make requests+ }
+ license.txt view
@@ -0,0 +1,18 @@+Copyright 2018 Typeclass Consulting, LLC++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of+the Software, and to permit persons to whom the Software is furnished to do so,+subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ readme.md view
@@ -0,0 +1,77 @@+# aws-cloudfront-signed-cookies++Generate signed cookies for AWS CloudFront++One way to [serve private content through AWS CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html) is to use [signed cookies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html). This package helps you generate signed cookies [using a custom IAM policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html) which may include a range of time for which the cookie is valid and an IP address restriction.++## The library++Example usage:++```haskell+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}++import Network.AWS.CloudFront.SignedCookies++import qualified Data.Text.IO++main :: IO ()+main = do++ -- Construct an IAM policy that expires three days from now+ policy :: Policy <- simplePolicy+ (Resource "https://example.com/secrets/*")+ (Lifespan (3 * nominalDay))++ -- Parse the .pem file to get the private key+ key :: PrivateKey <- readPrivateKeyPemFile+ (PemFilePath "./pk-APKAIATXN3RCIOVT5WRQ.pem")++ -- Construct signed cookies+ cookies :: CookiesText <- createSignedCookies+ (KeyPairId "APKAIATXN3RCIOVT5WRQ") key policy++ Data.Text.IO.putStrLn (renderCookiesText cookies)+```++The output should look something like this:++```haskell+Cookie: CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc29...+Cookie: CloudFront-Signature=wMN6V3Okxk7sdSPZeebMh-wo...+Cookie: CloudFront-Key-Pair-Id=APKAIATXN3RCIOVT5WRQ+```++You can see a very similar example in action in the `Network.AWS.CloudFront.SignedCookies.CLI` module which defines the command-line interface.++## The executable++You can also generate cookies using the command-line interface.++```+$ aws-cloudfront-signed-cookies --help+Generator of signed cookies for AWS CloudFront++Usage: aws-cloudfront-signed-cookies --pem-file ARG --key-pair-id ARG+ --resource ARG --days ARG++Available options:+ -h,--help Show this help text+ --pem-file ARG Location in the filesystem where a .pem file+ containing an RSA secret key can be found+ --key-pair-id ARG CloudFront key pair ID for the key pair that you are+ using to generate signature+ --resource ARG URL that the policy will grant access to, optionally+ containing asterisks for wildcards+ --days ARG Integer number of days until the policy expires+```++Example usage:++```+$ aws-cloudfront-signed-cookies \+ --pem-file pk-APKAIATXN3RCIOVT5WRQ.pem \+ --key-pair-id APKAIATXN3RCIOVT5WRQ \+ --resource "https://example.com/secrets/*" \+ --days 2+```