packages feed

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 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+```