diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,367 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+# Amazon IAM Policy Documents
+
+* [Description](#description)
+* [Example](#example)
+* [Contribute](#contribute)
+* [Licence](#licence)
+
+
+## Description
+
+This library provides data types and combinators that allow you to declare,
+encode, and decode the [IAM JSON policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html)
+language with a modicum of safety, minus any extreme type-level features.
+
+The IAM policy documents can be safely constructed via the provided datatypes
+and mapped, folded, and traversed via the provided instances, combinators,
+and lenses. The resulting structure can then be encoded as a valid IAM JSON
+policy document for using with Amazon IAM and related services.
+
+The details of what goes into a policy vary for each service, depending on what
+actions the service makes available, what types of resources it contains, and
+so on. When you're writing policies for a specific service, it's helpful to see
+examples of policies for that service. View the [AWS Services That Work with IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)
+documentation for more information.
+
+
+## Example
+
+The following example sets up S3 bucket management:
+
+```haskell
+{-# LANGUAGE OverloadedLists   #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import qualified Amazonka-Iam-Policy.IAM.Policy as Policy
+
+main :: IO ()
+main =
+    print . Policy.encode $
+        Policy.document
+            [ Policy.allow
+                { Policy.action   = Policy.some ["s3:*"]
+                , Policy.resource =
+                    Policy.some
+                        [ "arn:aws:s3:::<BUCKET-NAME>"
+                        , "arn:aws:s3:::<BUCKET-NAME>/*"
+                        ]
+                }
+            , Policy.deny
+                { Policy.action   = Policy.not ["s3:*"]
+                , Policy.resource =
+                    Policy.not
+                        [
+                        , "arn:aws:s3:::<BUCKET-NAME>"
+                        , "arn:aws:s3:::<BUCKET-NAME>/*"
+                        ]
+                }
+            ]
+```
+
+Resulting in the following encoded IAM JSON policy document:
+
+```json
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "s3:*",
+      "Resource": [
+        "arn:aws:s3:::<BUCKET-NAME>",
+        "arn:aws:s3:::<BUCKET-NAME>/*"
+      ]
+    },
+    {
+      "Effect": "Deny",
+      "NotAction": "s3:*",
+      "NotResource": [
+        "arn:aws:s3:::<BUCKET-NAME>",
+        "arn:aws:s3:::<BUCKET-NAME>/*"
+      ]
+    }
+  ]
+}
+```
+
+
+## Contribute
+
+For any problems, comments, or feedback please create an issue [here on GitHub](https://github.com/brendanhay/amazonka-iam-policy/issues).
+
+
+## Licence
+
+`amazonka-iam-policy` is released under the [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/).
diff --git a/amazonka-iam-policy.cabal b/amazonka-iam-policy.cabal
new file mode 100644
--- /dev/null
+++ b/amazonka-iam-policy.cabal
@@ -0,0 +1,97 @@
+name:                  amazonka-iam-policy
+version:               0.0.1
+synopsis:              Amazon IAM Policy Document DSL and Combinators.
+homepage:              https://github.com/brendanhay/amazonka-iam-policy
+bug-reports:           https://github.com/brendanhay/amazonka-iam-policy/issues
+license:               MPL-2.0
+license-file:          LICENSE
+author:                Brendan Hay
+maintainer:            Brendan Hay <brendan.g.hay+amazonka@gmail.com>
+copyright:             Copyright (c) 2017 Brendan Hay
+category:              Network, AWS, Cloud
+build-type:            Simple
+extra-source-files:    README.md CHANGELOG.md
+cabal-version:         >= 1.10
+data-files:            test/golden/*.json
+
+description:
+    This library provides data types and combinators that allow you to declare,
+    encode, and decode the <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html IAM JSON policy>
+    language with a modicum of safety, minus any extreme type-level features.
+    .
+    The IAM policy documents can be safely constructed via the provided datatypes
+    and mapped, folded, and traversed via the provided instances, combinators,
+    and lenses. The resulting structure can then be encoded as a valid IAM JSON
+    policy document for using with Amazon IAM and related services.
+    .
+    The details of what goes into a policy vary for each service, depending on what
+    actions the service makes available, what types of resources it contains, and
+    so on. When you're writing policies for a specific service, it's helpful to see
+    examples of policies for that service. View the
+    <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html AWS Services That Work with IAM> documentation for more information.
+    .
+    See "Amazonka.IAM.Policy" to get started.
+
+source-repository head
+    type:              git
+    location:          git://github.com/brendanhay/amazonka-iam-policy.git
+    subdir:            amazonka-iam-policy
+
+library
+    default-language:  Haskell2010
+    hs-source-dirs:    src
+
+    ghc-options:
+        -Wall
+        -fwarn-incomplete-uni-patterns
+        -fwarn-incomplete-record-updates
+        -funbox-strict-fields
+
+    exposed-modules:
+        Amazonka.IAM.Policy
+        Amazonka.IAM.Policy.Lens
+
+    build-depends:
+          aeson
+        , base >= 4.7 && < 5
+        , base64-bytestring
+        , bytestring
+        , profunctors
+        , scientific
+        , text
+        , time
+
+test-suite doctest
+    type:              exitcode-stdio-1.0
+    default-language:  Haskell2010
+    hs-source-dirs:    test
+    main-is:           Doctest.hs
+
+    ghc-options:       -Wall -threaded
+
+    other-modules:
+        Paths_amazonka_iam_policy
+
+    build-depends:
+          aeson-pretty
+        , amazonka-iam-policy
+        , doctest
+        , base
+
+test-suite golden
+    type:              exitcode-stdio-1.0
+    default-language:  Haskell2010
+    hs-source-dirs:    test
+    main-is:           Golden.hs
+
+    ghc-options:       -Wall -threaded
+
+    other-modules:
+        Paths_amazonka_iam_policy
+
+    build-depends:
+          aeson
+        , amazonka-iam-policy
+        , base
+        , bytestring
+        , hspec
diff --git a/src/Amazonka/IAM/Policy.hs b/src/Amazonka/IAM/Policy.hs
new file mode 100644
--- /dev/null
+++ b/src/Amazonka/IAM/Policy.hs
@@ -0,0 +1,1263 @@
+{-# LANGUAGE DeriveFoldable             #-}
+{-# LANGUAGE DeriveFunctor              #-}
+{-# LANGUAGE DeriveTraversable          #-}
+{-# LANGUAGE FlexibleContexts           #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase                 #-}
+{-# LANGUAGE OverloadedLists            #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE RecordWildCards            #-}
+{-# LANGUAGE TupleSections              #-}
+{-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE ViewPatterns               #-}
+
+{- | This module provides data types and combinators that allow you to declare,
+encode, and decode the
+<https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html IAM JSON policy>
+language. The available 'Action' and 'Condition' keys can be found under the
+<https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actionsconditions.html AWS Service Actions and Condition Context Keys for Use in IAM policies>
+documentation.
+
+Lenses and prisms for those wanting to traverse and manipulate policy documents
+and their constituent elements can be found in the "Amazonka.IAM.Policy.Lens"
+module.
+-}
+module Amazonka.IAM.Policy
+    (
+    -- * Usage
+    -- $usage
+
+    -- * Policy Documents
+      Policy    (..)
+
+    -- ** Constructing Policies
+    , document
+    , singleton
+
+    -- ** Encoding
+    , encode
+
+    -- * Language Elements
+    -- $elements
+
+    , Block     (..)
+    , Match     (..)
+
+    -- ** Specifying and Negating Elements
+    , some
+    , not
+    , any
+    , none
+
+    -- ** Version
+    , Version   (..)
+
+    -- ** Id
+    , Id        (..)
+
+    -- ** Statement
+    , Statement (..)
+
+    -- *** Constructing Statements
+    , allow
+    , deny
+
+    -- ** Sid
+    , Sid       (..)
+
+    -- ** Principal
+    , Principal (..)
+
+    -- ** Effect
+    , Effect    (..)
+
+    -- ** Action
+    , Action    (..)
+
+    -- ** Resource
+    , Resource  (..)
+
+    -- ** Condition
+    , Key       (..)
+    , Condition (..)
+    ) where
+
+import Prelude hiding (any, id, not)
+
+import Control.Applicative (optional, (<|>))
+
+import Data.Aeson         (FromJSON, ToJSON (toJSON), (.:), (.:?), (.=))
+import Data.Bifunctor     (second)
+import Data.ByteString    (ByteString)
+import Data.Function      (on)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Maybe         (catMaybes)
+import Data.Scientific    (Scientific, scientificP)
+import Data.Semigroup     (Semigroup ((<>)))
+import Data.String        (IsString)
+import Data.Text          (Text)
+import Data.Time          (UTCTime)
+
+import GHC.Exts (IsList (..))
+
+import qualified Data.Aeson                   as JSON
+import qualified Data.Aeson.Types             as JSON
+import qualified Data.ByteString.Base64       as Base64
+import qualified Data.ByteString.Lazy         as LBS
+import qualified Data.Text.Encoding           as Text
+import qualified Data.Time.Clock.POSIX        as POSIX
+import qualified Data.Time.Format             as Time
+import qualified Text.ParserCombinators.ReadP as Read
+
+{- $setup
+
+>>> :set -XOverloadedStrings
+
+>>> import Data.Aeson.Encode.Pretty (encodePretty)
+>>> import Data.ByteString.Lazy.Char8 (unpack)
+
+>>> let encode' = putStrLn . unpack . encodePretty
+
+-}
+
+{- $usage
+It's recommended that you import and use this module qualified to avoid any
+ambiguity with prelude functions.
+
+The following is an example of using the 'Semigroup' instance of 'Policy' to
+construct a document from multiple statements. This example creates a policy
+that would allow an IAM user sufficient privilege to rotate their credentials:
+
+@
+&#x7b;-\# LANGUAGE OverloadedStrings \#-&#x7d;
+
+import qualified Amazonka.IAM.Policy as Policy
+
+ ( Policy.singleton
+       (Policy.allow
+           { Policy.action   =
+               Policy.some
+                   [ "iam:ListUsers"
+                   , "iam:GetAccountPasswordPolicy"
+                   ]
+           , Policy.resource = Policy.any
+           })
+
+<> Policy.singleton
+       (Policy.allow
+           { Policy.action   =
+               Policy.some
+                   [ "iam:*AccessKey*"
+                   , "iam:ChangePassword"
+                   , "iam:GetUser"
+                   , "iam:*ServiceSpecificCredential*"
+                   , "iam:*SigningCertificate*"
+                   ]
+           , Policy.resource =
+               Policy.some
+                   [ "arn:aws:iam::*:user/${aws:username}"
+                   ]
+           })
+ )
+@
+
+Which results in the following encoded IAM JSON policy document:
+
+> {
+>   "Version": "2012-10-17",
+>   "Statement": [
+>     {
+>       "Effect": "Allow",
+>       "Action": [
+>         "iam:ListUsers",
+>         "iam:GetAccountPasswordPolicy"
+>       ],
+>       "Resource": "*"
+>     },
+>     {
+>       "Effect": "Allow",
+>       "Action": [
+>         "iam:*AccessKey*",
+>         "iam:ChangePassword",
+>         "iam:GetUser",
+>         "iam:*ServiceSpecificCredential*",
+>         "iam:*SigningCertificate*"
+>       ],
+>       "Resource": "arn:aws:iam::*:user/${aws:username}"
+>     }
+>   ]
+> }
+
+You can also use the @OverloadedLists@ extension with the 'document' smart constructor
+to create a 'Policy' using the 'IsList' instance for 'NonEmpty'. The following example
+sets up S3 bucket management:
+
+@
+&#x7b;-\# LANGUAGE OverloadedLists   \#-&#x7d;
+&#x7b;-\# LANGUAGE OverloadedStrings \#-&#x7d;
+
+import qualified Amazonka.IAM.Policy as Policy
+
+Policy.document
+    [ Policy.allow
+        { Policy.action   = Policy.some ["s3:*"]
+        , Policy.resource =
+            Policy.some
+                [ "arn:aws:s3:::<BUCKET-NAME>"
+                , "arn:aws:s3:::<BUCKET-NAME>/*"
+                ]
+        }
+    , Policy.deny
+        { Policy.action   = Policy.not ["s3:*"]
+        , Policy.resource =
+            Policy.not
+                [
+                , "arn:aws:s3:::<BUCKET-NAME>"
+                , "arn:aws:s3:::<BUCKET-NAME>/*"
+                ]
+        }
+    ]
+@
+
+Resulting in the following encoded IAM JSON policy document:
+
+> {
+>   "Version": "2012-10-17",
+>   "Statement": [
+>     {
+>       "Effect": "Allow",
+>       "Action": "s3:*",
+>       "Resource": [
+>         "arn:aws:s3:::<BUCKET-NAME>",
+>         "arn:aws:s3:::<BUCKET-NAME>/*"
+>       ]
+>     },
+>     {
+>       "Effect": "Deny",
+>       "NotAction": "s3:*",
+>       "NotResource": [
+>         "arn:aws:s3:::<BUCKET-NAME>",
+>         "arn:aws:s3:::<BUCKET-NAME>/*"
+>       ]
+>     }
+>   ]
+> }
+
+Please be aware that the use of @OverloadedLists@ and 'NonEmpty' will error if
+'document' is passed an empty list.
+-}
+
+{- $elements
+IAM JSON policy documents are made up of elements. The elements are listed here
+roughly in the general order you use them in a policy.
+
+The details of what goes into a policy vary for each service, depending on what
+actions the service makes available, what types of resources it contains, and
+so on. When you're writing policies for a specific service, it's helpful to see
+examples of policies for that service. View the <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html AWS Services That Work with IAM>
+documentation for more information.
+
+This library attempts to stick as closely as possible to the IAM policy language grammar
+and its specific terminology while providing a modicum of safety without going
+overboard with type-level features. To support this, the concept of @Element@ vs
+@NotElement@ and @"*"@ wildcards are generalised by the introduction of two
+additional types not specified directly in the <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html language grammar>.
+
+Firstly there is the 'Block' type which allows us to negate @Principal@,
+@Action@, and @Resource@ elements via the related @NotPrincipal@, @NotAction@,
+and @NotResource@ elements. Secondly the @Match@ type provides a way to
+generally specific @Some@ values for an element, or the @Wildcard@ match
+equivalent to @"*"@. Combinators are then provided for the common cases.
+
+==== Example
+
+>>> encode' $ singleton (allow { action = some ["foo", "bar"] })
+...
+            "Effect": "Allow",
+            "Action": [
+                "foo",
+                "bar"
+            ],
+            "Resource": []
+...
+
+>>> encode' $ singleton (allow { action = not ["baz"] })
+...
+            "Effect": "Allow",
+            "Resource": [],
+            "NotAction": "baz"
+...
+
+>>> encode' $ singleton (allow { action = any })
+...
+            "Effect": "Allow",
+            "Action": "*",
+            "Resource": []
+...
+
+>>> encode' $ singleton (allow { action = none })
+...
+            "Effect": "Allow",
+            "Resource": [],
+            "NotAction": "*"
+...
+
+The above examples work identically for the 'action', 'resource', and
+'principal' statement elements.
+
+-}
+
+{- | A 'Block' is a shared type denoting blocks of the policy language grammar.
+
+This is used by the 'Principal', 'Action', 'Resource' elements of the policy
+language.
+
+==== Grammar
+
+> <name_block> = ("Name" | "NotName") : <match_block>
+-}
+data Block a
+    = Match !(Match a)
+    | Not   !(Match a)
+      deriving (Show, Eq, Functor, Foldable, Traversable)
+
+blockToJSON :: ToJSON a => Text -> Block a -> JSON.Pair
+blockToJSON k = \case
+    Match v -> k .= v
+    Not   v -> ("Not" <> k) .= v
+{-# INLINE blockToJSON #-}
+
+blockParseJSON :: FromJSON a => Text -> JSON.Object -> JSON.Parser (Block a)
+blockParseJSON k o =
+        Match <$> o .: k
+    <|> Not   <$> o .: ("Not" <> k)
+{-# INLINE blockParseJSON #-}
+
+-- | Match one or more elements.
+some :: a -> Block a
+some = Match . Some
+{-# INLINEABLE some #-}
+
+-- | Negate a match of one or more elements.
+--
+-- `not` is the negation of `some`.
+not :: a -> Block a
+not = Not . Some
+{-# INLINEABLE not #-}
+
+-- | Match any element with a wildcard.
+any :: Block a
+any = Match Wildcard
+{-# INLINEABLE any #-}
+
+-- | Match no element with a negated wildcard.
+--
+-- `none` is the negation of `any`.
+none :: Block a
+none = Not Wildcard
+{-# INLINEABLE none #-}
+
+{- | A 'Match' is a shared type used to denote wildcards in the policy
+language grammar.
+
+This is used by the 'Principal', 'Action', 'Resource' elements of the policy
+language.
+
+==== Grammar
+
+> <match_block> = ("*" | <item>)
+-}
+data Match a
+    = Some !a
+    | Wildcard
+      deriving (Show, Eq, Functor, Foldable, Traversable)
+
+instance Semigroup a => Semigroup (Match a) where
+    (<>) a b =
+        case (a, b) of
+            (Wildcard, _)        -> Wildcard
+            (_,        Wildcard) -> Wildcard
+            (Some xs,  Some ys)  -> Some (xs <> ys)
+    {-# INLINEABLE (<>) #-}
+
+instance (Semigroup a, Monoid a) => Monoid (Match a) where
+    mempty = Some mempty
+    {-# INLINEABLE mempty #-}
+
+    mappend = (<>)
+    {-# INLINEABLE mappend #-}
+
+instance ToJSON a => ToJSON (Match a) where
+    toJSON = \case
+        Some x   -> toJSON x
+        Wildcard -> JSON.String "*"
+    {-# INLINE toJSON #-}
+
+instance FromJSON a => FromJSON (Match a) where
+    parseJSON = \case
+        JSON.String "*"  -> pure Wildcard
+        JSON.Array ["*"] -> pure Wildcard
+        x                -> Some <$> JSON.parseJSON x
+    {-# INLINE parseJSON #-}
+
+-- | This type is used to provide support for interchangeably encoding or parsing
+-- a single value, or multiple values as a list.
+data OneOrMany a
+    = One  !a
+    | Many ![a]
+      deriving (Show, Eq)
+
+instance ToJSON a => ToJSON (OneOrMany a) where
+    toJSON = \case
+        One  x  -> toJSON x
+        Many xs -> toJSON xs
+    {-# INLINE toJSON #-}
+
+instance FromJSON a => FromJSON (OneOrMany a) where
+    parseJSON o = One <$> JSON.parseJSON o <|> Many <$> JSON.parseJSON o
+    {-# INLINE parseJSON #-}
+
+instance IsList (OneOrMany a) where
+    type Item (OneOrMany a) = a
+
+    toList = \case
+        One  x -> [x]
+        Many xs -> xs
+    {-# INLINE toList #-}
+
+    fromList = \case
+        [x] -> One  x
+        xs  -> Many xs
+    {-# INLINE fromList #-}
+
+{- | A policy document is a non-empty list of IAM statements with a supported version.
+
+The statements of a policy document can be traversed and manipulated via the
+'Functor', 'Foldable', and 'Traverseable' instances. You can use the
+'Semigroup' instance to concatenate multiple 'Policy' documents together,
+preserving the highest supported version and first encountered 'Id'.
+
+==== Grammar
+
+> policy  = {
+>     <version_block?>
+>     <id_block?>
+>     <statement_block>
+> }
+
+-}
+data Policy a = Policy
+    { version   :: !(Maybe Version)
+    , id        :: !(Maybe Id)
+    , statement :: !(NonEmpty a)
+    } deriving (Show, Eq, Functor, Foldable, Traversable)
+
+-- FIXME: Note about pointwise 'First' behaviour of version/id.
+instance Semigroup (Policy a) where
+    (<>) a b = Policy
+        { version   = on (<>)  version   a b
+        , id        = on (<|>) id        a b
+        , statement = on (<>)  statement a b
+        }
+    {-# INLINEABLE (<>) #-}
+
+instance ToJSON a => ToJSON (Policy a) where
+    toJSON Policy{..} =
+        JSON.object $ catMaybes
+            [ fmap ("Id"        .=) id
+            , fmap ("Version"   .=) version
+            , Just ("Statement" .=  statement)
+            ]
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON a => FromJSON (Policy a) where
+    parseJSON = JSON.withObject "Policy" $ \o -> do
+        version   <- o .:? "Version"
+        id        <- o .:? "Id"
+        statement <- o .:  "Statement"
+        pure Policy{..}
+    {-# INLINEABLE parseJSON #-}
+
+{- | Construct a policy from a collection of 'NonEmpty' 'Statement's using the
+ default @2012-10-17@ version and no identifier.
+
+==== Example
+
+>>> encode' $ document (pure allow <> pure deny)
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [],
+            "Resource": []
+        },
+        {
+            "Effect": "Deny",
+            "Action": [],
+            "Resource": []
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+-}
+document :: NonEmpty a -> Policy a
+document xs = Policy
+    { version   = Just Version_2012_10_17
+    , id        = Nothing
+    , statement = xs
+    }
+{-# INLINEABLE document #-}
+
+{- | Construct a policy from a single 'Statement' using the default
+@2012-10-17@ version and no identifier.
+
+==== Example
+
+>>> encode' $ singleton (allow { resource = any })
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [],
+            "Resource": "*"
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+-}
+singleton :: a -> Policy a
+singleton = document . pure
+{-# INLINEABLE singleton #-}
+
+{- | Encode the IAM policy document as JSON.
+
+==== Example
+
+>>> encode $ singleton allow
+"{\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[],\"Resource\":[]}],\"Version\":\"2012-10-17\"}"
+
+-}
+encode :: Policy Statement -> LBS.ByteString
+encode = JSON.encode
+{-# INLINEABLE encode #-}
+
+{- | The 'Version' elements specifies the language syntax rules that are to be
+used to process this policy. If you include features that are not available in
+the specified version, then your policy will generate errors or not work the
+way you intend. As a general rule, you should specify the most recent version
+available, unless you depend on a feature that was deprecated in later
+versions.
+
+==== Grammar
+
+> <version_block> = "Version" : ("2008-10-17" | "2012-10-17")
+
+==== Example
+
+>>> JSON.encode Version_2012_10_17
+"\"2012-10-17\""
+
+-}
+data Version
+    = Version_2008_10_17
+    | Version_2012_10_17
+      deriving (Show, Eq, Ord)
+
+-- | '(<>)' always chooses the newest version.
+instance Semigroup Version where
+    (<>) = max
+    {-# INLINEABLE (<>) #-}
+
+instance ToJSON Version where
+    toJSON = \case
+        Version_2008_10_17 -> JSON.String "2008-10-17"
+        Version_2012_10_17 -> JSON.String "2012-10-17"
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON Version where
+    parseJSON = JSON.withText "Version" $ \case
+        "2008-10-17" -> pure Version_2008_10_17
+        "2012-10-17" -> pure Version_2012_10_17
+        x            -> fail ("Unabled to parse Version from " ++ show x)
+    {-# INLINEABLE parseJSON #-}
+
+{- | The 'Id' element specifies an optional identifier for the policy. The ID
+is used differently in different services.
+
+For services that let you set an ID element, we recommend you use a UUID
+(GUID) for the value, or incorporate a UUID as part of the ID to ensure
+uniqueness.
+
+/Note/: Some AWS services (for example, Amazon SQS or Amazon SNS) might
+require this element and have uniqueness requirements for it. For
+service-specific information about writing policies, refer to the
+documentation for the service you're working with.
+
+Use the 'id' record field to set the 'Id' of a 'Policy' document.
+
+==== Grammar
+
+> <id_block> = "Id" : <policy_id_string>
+
+==== Example
+
+>>> encode' $ (singleton allow) { id = Just "123" }
+{
+...
+    "Id": "123"
+}
+
+-}
+newtype Id = Id { fromId :: Text }
+    deriving (Show, Eq, Ord, ToJSON, FromJSON, IsString)
+
+{- | The 'Statement' element is the main element for a policy. This element is
+required. It can include multiple elements (see the subsequent sections in this
+page). The Statement element contains an array of individual statements.
+
+==== Grammar
+
+> <statement_block> = "Statement" : [ <statement>, <statement>, ... ]
+>
+> <statement> = {
+>     <sid_block?>,
+>     <principal_block?>,
+>     <effect_block>,
+>     <action_block>,
+>     <resource_block>,
+>     <condition_block?>
+> }
+
+-}
+data Statement = Statement
+    { sid       :: !(Maybe Sid)
+    , principal :: !(Maybe (Block Principal))
+    , effect    :: !Effect
+    , action    :: !(Block [Action])
+    , resource  :: !(Block [Resource])
+    , condition :: !(Maybe Condition)
+    } deriving (Show, Eq)
+
+instance ToJSON Statement where
+    toJSON Statement{..} =
+        let oneOrMany = fmap (fromList :: [a] -> OneOrMany a)
+         in JSON.object $ catMaybes
+            [ fmap ("Sid" .=) sid
+            , fmap (blockToJSON "Principal") principal
+            , Just ("Effect" .= effect)
+            , Just (blockToJSON "Action"   $ oneOrMany action)
+            , Just (blockToJSON "Resource" $ oneOrMany resource)
+            , fmap ("Condition" .=) condition
+            ]
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON Statement where
+    parseJSON = JSON.withObject "Statement" $ \o -> do
+        sid       <- o .:? "Sid"
+        principal <- optional (blockParseJSON "Principal" o)
+        effect    <- o .:  "Effect"
+        action    <- oneOrMany <$> blockParseJSON "Action"   o
+        resource  <- oneOrMany <$> blockParseJSON "Resource" o
+        condition <- o .:? "Condition"
+        pure Statement{..}
+      where
+        oneOrMany = fmap (toList :: OneOrMany a -> [a])
+    {-# INLINEABLE parseJSON #-}
+
+-- | Create a new statement with the effect set to 'Allow'.
+allow :: Statement
+allow = Statement
+    { sid       = Nothing
+    , principal = Nothing
+    , effect    = Allow
+    , action    = Match mempty
+    , resource  = Match mempty
+    , condition = Nothing
+    }
+{-# INLINEABLE allow #-}
+
+-- | Create a new statement with the effect set to 'Deny'.
+deny :: Statement
+deny = allow { effect = Deny }
+{-# INLINEABLE deny #-}
+
+{- | The 'Sid' (statement ID) is an optional identifier that you provide for the
+policy statement. You can assign a Sid value to each statement in a statement
+array. In services that let you specify an ID element, such as SQS and SNS, the
+Sid value is just a sub-ID of the policy document's ID. In IAM, the Sid value
+must be unique within a JSON policy.
+
+In IAM, the Sid is not exposed in the IAM API. You can't retrieve a
+particular statement based on this ID.
+
+/Note/: Some AWS services (for example, Amazon SQS or Amazon SNS) might require this
+element and have uniqueness requirements for it. For service-specific
+information about writing policies, refer to the documentation for the service
+you're working with.
+
+Use the 'sid' record field to set the 'Sid' of a 'Statement'.
+
+==== Grammar
+
+> <sid_block> = "Sid" : <sid_string>
+
+==== Example
+
+>>> encode' $ singleton (allow { sid = Just "cd3ad3d9-2776-4ef1-a904-4c229d1642ee" })
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [],
+            "Resource": [],
+            "Sid": "cd3ad3d9-2776-4ef1-a904-4c229d1642ee"
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+-}
+newtype Sid = Sid { fromSid :: Text }
+    deriving (Show, Eq, Ord, ToJSON, FromJSON, IsString)
+
+{- | The 'Effect' element is required and specifies whether the statement
+results in an allow or an explicit deny.
+
+By default, access to resources is denied. To allow access to a resource,
+you must set the Effect element to 'Allow'. To override an allow (for example,
+to override an allow that is otherwise in force), you set the Effect element
+to 'Deny'.
+
+See the 'allow' and 'deny' smart constructors to create a 'Statement' with the
+desired 'Effect' set.
+
+==== Grammar
+
+> <effect_block> = "Effect" : ("Allow" | "Deny")
+-}
+data Effect = Allow | Deny
+    deriving (Show, Eq, Ord, Enum)
+
+instance ToJSON Effect where
+    toJSON = \case
+        Allow -> "Allow"
+        Deny  -> "Deny"
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON Effect where
+    parseJSON = JSON.withText "Effect" $ \case
+        "Allow" -> pure Allow
+        "Deny"  -> pure Deny
+        x       -> fail ("Unabled to parse Effect from " ++ show x)
+    {-# INLINEABLE parseJSON #-}
+
+{- | Use the 'Principal' element to specify the user (IAM user, federated user, or
+assumed-role user), AWS account, AWS service, or other principal entity that
+is allowed or denied access to a resource. You use the Principal element in
+the trust policies for IAM roles and in resource-based policies—that is, in
+policies that you embed directly in a resource. For example, you can embed
+such policies in an Amazon S3 bucket, an Amazon Glacier vault, an Amazon SNS
+topic, an Amazon SQS queue, or an AWS KMS customer master key (CMK).
+
+Use the Principal element in these ways:
+
+In IAM roles, use the Principal element in the role's trust policy to
+specify who can assume the role. For cross-account access, you must specify
+the 12-digit identifier of the trusted account.
+
+/Note/: After you create the role, you can change the account to "*" to
+allow everyone to assume the role. If you do this, we strongly recommend
+that you limit who can access the role through other means, such as a
+Condition element that limits access to only certain IP addresses. Do not
+leave your role accessible to everyone!
+
+In resource-based policies, use the 'Principal' element to specify the
+accounts or users who are allowed to access the resource.
+
+You can use 'Not' to negate the meaning of a 'Principal' element.
+
+==== Grammar
+
+> <principal_block> = ("Principal" | "NotPrincipal") : ("*" | <principal_map>)
+>
+> <principal_map> = { <principal_map_entry>, <principal_map_entry>, ... }
+>
+> <principal_map_entry> = ("AWS" | "Federated" | "Service") :
+>     [<principal_id_string>, <principal_id_string>, ...]
+
+==== Example
+
+>>> encode' $ singleton (allow { principal = Just $ not Everyone })
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "NotPrincipal": "*",
+            "Action": [],
+            "Resource": []
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+>>> encode' $ singleton (allow { principal = Just $ some (AWS (pure "arn:foo:::bar")) })
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [],
+            "Principal": {
+                "AWS": [
+                    "arn:foo:::bar"
+                ]
+            },
+            "Resource": []
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+-}
+data Principal
+    = Everyone
+    | AWS           !(NonEmpty Text)
+    | Federated     !Text
+    | Service       !(NonEmpty Text)
+    | CanonicalUser !Text
+      deriving (Show, Eq)
+
+instance ToJSON Principal where
+    toJSON = \case
+        Everyone         -> JSON.String "*"
+        AWS           ks -> JSON.object ["AWS"           .= ks]
+        Federated     k  -> JSON.object ["Federated"     .= k]
+        Service       ks -> JSON.object ["Service"       .= ks]
+        CanonicalUser k  -> JSON.object ["CanonicalUser" .= k]
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON Principal where
+    parseJSON v = everyone v <|> nested v
+      where
+        everyone =
+            JSON.withText "Principal:*" $ \case
+                "*" -> pure Everyone
+                x   -> fail ("Unable to parse Principal:* from " ++ show x)
+
+        nested =
+            JSON.withObject "Principal" $ \o ->
+                    AWS           <$> o .: "AWS"
+                <|> Federated     <$> o .: "Federated"
+                <|> Service       <$> o .: "Service"
+                <|> CanonicalUser <$> o .: "CanonicalUser"
+    {-# INLINEABLE parseJSON #-}
+
+{- | The 'Action' element describes the specific action or actions that will be
+allowed or denied. Statements must include either an Action or NotAction
+element. Each AWS service has its own set of actions that describe tasks that
+you can perform with that service.
+
+You can use 'Not' to negate the meaning of a list of 'Action' elements.
+
+==== Grammar
+
+> <action_block> = ("Action" | "NotAction") :
+>     ("*" | [<action_string>, <action_string>, ...])
+
+-}
+newtype Action = Action Text
+    deriving (Show, Eq, ToJSON, FromJSON, IsString)
+
+{- | The 'Resource' element specifies the object or objects that the statement
+covers. Statements must include either a Resource or a NotResource element.
+
+Each service has its own set of resources. Although you always use an ARN to
+specify a resource, the details of the ARN for a resource depend on the
+service and the resource. For information about how to specify a resource,
+refer to the documentation for the service whose resources you're writing a
+statement for.
+
+/Note:/ Some services do not let you specify actions for individual
+resources; instead, any actions that you list in the Action or NotAction
+element apply to all resources in that service. In these cases, you use the
+wildcard @"*"@ in the Resource element.
+
+You can use 'Not' to negate the meaning of a list of 'Resource' elements.
+
+==== Grammar
+
+> <resource_block> = ("Resource" | "NotResource") :
+>     ("*" | [<resource_string>, <resource_string>, ...])
+
+-}
+newtype Resource = Resource Text
+    deriving (Show, Eq, ToJSON, FromJSON, IsString)
+
+-- | A key that will be tested as the target of a 'Condition'.
+newtype Key = Key { fromKey :: Text }
+    deriving (Show, Eq, FromJSON, ToJSON, IsString)
+
+{- | The 'Condition' element (or Condition block) lets you specify conditions
+for when a policy is in effect. The Condition element is optional. In the
+Condition element, you build expressions in which you use condition operators
+(equal, less than, etc.) to match the condition in the policy against values in
+the request. Condition values can include date, time, the IP address of the
+requester, the ARN of the request source, the user name, user ID, and the user
+agent of the requester. Some services let you specify additional values in
+conditions; for example, Amazon S3 lets you write a condition using the
+@s3:VersionId@ key, which is unique to that service.
+
+    * __String:__
+      String condition operators let you construct 'Condition' elements that
+      restrict access based on comparing a key to a string value.
+
+    * __Numeric:__
+      Numeric condition operators let you construct Condition elements that
+      restrict access based on comparing a key to an integer or decimal value.
+
+    * __Date:__
+      Date condition operators let you construct Condition elements that restrict
+      access based on comparing a key to a date/time value. You use these condition
+      operators with the aws:CurrentTime key or aws:EpochTime keys. You must specify
+      date/time values with one of the W3C implementations of the ISO 8601 date
+      formats or in epoch (UNIX) time.
+      Wildcards are not permitted for date condition operators.
+
+    * __Boolean:__
+      Boolean conditions let you construct Condition elements that restrict access
+      based on comparing a key to "true" or "false."
+
+    * __Binary:__
+      The BinaryEquals condition operator let you construct Condition elements
+      that test key values that are in binary format. It compares the value of the
+      specified key byte for byte against a base-64 encoded representation of the
+      binary value in the policy.
+
+    * __IP Address:__
+      IP address condition operators let you construct Condition elements that
+      restrict access based on comparing a key to an IPv4 or IPv6 address or range
+      of IP addresses. You use these with the aws:SourceIp key. The value must be
+      in the standard CIDR format (for example, 203.0.113.0/24 or
+      2001:DB8:1234:5678::/64). If you specify an IP address without the
+      associated routing prefix, IAM uses the default prefix value of /32.
+      Some AWS services support IPv6, using :: to represent a range of 0s. To
+      learn whether a service supports IPv6, see the documentation for that
+      service.
+
+    * __Amazon Resource Name (ARN):__
+      Amazon Resource Name (ARN) condition operators let you construct Condition
+      elements that restrict access based on comparing a key to an ARN. The ARN is
+      considered a string. This value is available for only some services; not all
+      services support request values that can be compared as ARNs.
+
+    * __Key Existence:__
+      You can add IfExists to the end of any condition operator name except the
+      Null condition—for example, StringLikeIfExists. You do this to say "If the
+      policy key is present in the context of the request, process the key as
+      specified in the policy. If the key is not present, the condition evaluate
+      the condition element as true." Other condition elements in the statement
+      can still result in a nonmatch, but not a missing key when checked with
+      ...IfExists.
+
+    * __Null:__
+      Use a Null condition operator to check if a condition key is present at the
+      time of authorization. In the policy statement, use either true (the key
+      doesn't exist — it is null) or false (the key exists and its value is not null).
+      For example, you can use this condition operator to determine whether a user is
+      using their own credentials for the operation or temporary credentials. If the
+      user is using temporary credentials, then the key aws:TokenIssueTime exists and
+      has a value. The following example shows a condition that states that the user
+      must not be using temporary credentials (the key must not exist) for the user
+      to use the Amazon EC2 API.
+
+==== Grammar
+
+> <condition_block> = "Condition" : { <condition_map> }
+>
+> <condition_map> {
+>   <condition_type_string> : { <condition_key_string> : <condition_value_list> },
+>   <condition_type_string> : { <condition_key_string> : <condition_value_list> }, ...
+> }
+>
+> <condition_value_list> = [<condition_value>, <condition_value>, ...]
+>
+> <condition_value> = ("string" | "number" | "Boolean")
+
+==== Example
+
+>>> encode' $ singleton (allow { condition = Just (Bool "aws:MultiFactorAuthPresent" True) })
+{
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [],
+            "Resource": [],
+            "Condition": {
+                "Bool": {
+                    "aws:MultiFactorAuthPresent": true
+                }
+            }
+        }
+    ],
+    "Version": "2012-10-17"
+}
+
+-}
+data Condition
+    = StringEquals              !Key !Text
+    -- ^ Exact matching, case sensitive.
+    | StringNotEquals           !Key !Text
+    -- ^ Negated matching.
+    | StringEqualsIgnoreCase    !Key !Text
+    -- ^ Exact matching, ignoring case.
+    | StringNotEqualsIgnoreCase !Key !Text
+    -- ^ Negated matching, ignoring case
+    | StringLike                !Key ![Text]
+    -- ^ Case-sensitive matching. The values can include a multi-character match
+    -- wildcard (*) or a single-character match wildcard (?) anywhere in the
+    -- string.
+    --
+    -- If a key contains multiple values, StringLike can be qualified with set
+    -- operators—ForAllValues:StringLike and ForAnyValue:StringLike. For more
+    -- information, see Creating a Condition That Tests Multiple Key Values (Set
+    -- Operations).
+    | StringNotLike             !Key ![Text]
+    -- ^ Negated case-sensitive matching. The values can include a multi-character
+    -- match wildcard (*) or a single-character match wildcard (?) anywhere in the
+    -- string.
+
+    | NumericEquals             !Key !Scientific
+    -- ^ Matching.
+    | NumericNotEquals          !Key !Scientific
+    -- ^ Negated matching.
+    | NumericLessThan           !Key !Scientific
+    -- ^ "Less than" matching.
+    | NumericLessThanEquals     !Key !Scientific
+    -- ^ "Less than or equals" matching.
+    | NumericGreaterThan        !Key !Scientific
+    -- ^ "Greater than" matching.
+    | NumericGreaterThanEquals  !Key !Scientific
+    -- ^ "Greater than or equals" matching.
+
+    | DateEquals                !Key !UTCTime
+    -- ^ Matching a specific date.
+    | DateNotEquals             !Key !UTCTime
+    -- ^ Negated matching.
+    | DateLessThan              !Key !UTCTime
+    -- ^ Matching before a specific date and time.
+    | DateLessThanEquals        !Key !UTCTime
+    -- ^ Matching at or before a specific date and time.
+    | DateGreaterThan           !Key !UTCTime
+    -- ^ Matching after a specific a date and time.
+    | DateGreaterThanEquals     !Key !UTCTime
+    -- ^ Matching at or after a specific date and time.
+
+    | Bool                      !Key !Bool
+    -- ^ Boolean matching.
+
+    | BinaryEquals              !Key !ByteString
+    -- ^ The BinaryEquals condition operator let you construct Condition elements
+    -- that test key values that are in binary format. It compares the value of the
+    -- specified key byte for byte against a base-64 encoded representation of the
+    -- binary value in the policy.
+
+    | IpAddress                 !Key !ByteString
+    -- ^ The specified IP address or range.
+    | NotIpAddress              !Key !ByteString
+    -- ^ All IP addresses except the specified IP address or range.
+
+    | ArnEquals                 !Key !Text
+    -- ^ Case-sensitive matching of the ARN. Each of the six colon-delimited
+    -- components of the ARN is checked separately and each can include a
+    -- multi-character match wildcard (*) or a single-character match wildcard
+    -- (?). These behave identically.
+    | ArnLike                   !Key !Text
+    -- ^ Case-sensitive matching of the ARN. Each of the six colon-delimited
+    -- components of the ARN is checked separately and each can include a
+    -- multi-character match wildcard (*) or a single-character match wildcard
+    -- (?). These behave identically.
+    | ArnNotEquals              !Key !Text
+    -- ^ Negated matching for ARN. These behave identically.
+    | ArnNotLike                !Key !Text
+    -- ^ Negated matching for ARN. These behave identically.
+      deriving (Show, Eq)
+
+instance ToJSON Condition where
+    toJSON x = JSON.object [op .= JSON.object [key .= val]]
+      where
+        (op, fromKey -> key, val) =
+            case x of
+                StringEquals              k v  ->
+                    ("StringEquals",              k, toJSON v)
+                StringNotEquals           k v  ->
+                    ("StringNotEquals",           k, toJSON v)
+                StringEqualsIgnoreCase    k v  ->
+                    ("StringEqualsIgnoreCase",    k, toJSON v)
+                StringNotEqualsIgnoreCase k v  ->
+                    ("StringNotEqualsIgnoreCase", k, toJSON v)
+                StringLike                k vs ->
+                    ("StringLike",                k, toJSON vs)
+                StringNotLike             k vs ->
+                    ("StringNotLike",             k, toJSON vs)
+
+                NumericEquals             k v  ->
+                    ("NumericEquals",             k, toJSON v)
+                NumericNotEquals          k v  ->
+                    ("NumericNotEquals",          k, toJSON v)
+                NumericLessThan           k v  ->
+                    ("NumericLessThan",           k, toJSON v)
+                NumericLessThanEquals     k v  ->
+                    ("NumericLessThanEquals",     k, toJSON v)
+                NumericGreaterThan        k v  ->
+                    ("NumericGreaterThan",        k, toJSON v)
+                NumericGreaterThanEquals  k v  ->
+                    ("NumericGreaterThanEquals",  k, toJSON v)
+
+                DateEquals                k v  ->
+                    ("DateEquals",                k, toJSON v)
+                DateNotEquals             k v  ->
+                    ("DateNotEquals",             k, toJSON v)
+                DateLessThan              k v  ->
+                    ("DateLessThan",              k, toJSON v)
+                DateLessThanEquals        k v  ->
+                    ("DateLessThanEquals",        k, toJSON v)
+                DateGreaterThan           k v  ->
+                    ("DateGreaterThan",           k, toJSON v)
+                DateGreaterThanEquals     k v  ->
+                    ("DateGreaterThanEquals",     k, toJSON v)
+
+                Bool                      k v  ->
+                    ("Bool",                      k, toJSON v)
+
+                BinaryEquals              k v  ->
+                    ("BinaryEquals",              k,
+                        toJSON (Text.decodeUtf8 (Base64.encode v)))
+
+                IpAddress                 k v  ->
+                    ("IpAddress",                 k,
+                        toJSON (Text.decodeUtf8 v))
+
+                NotIpAddress              k v  ->
+                    ("NotIpAddress",              k,
+                        toJSON (Text.decodeUtf8 v))
+
+                ArnEquals                 k v  ->
+                    ("ArnEquals",                 k, toJSON v)
+                ArnLike                   k v  ->
+                    ("ArnLike",                   k, toJSON v)
+                ArnNotEquals              k v  ->
+                    ("ArnNotEquals",              k, toJSON v)
+                ArnNotLike                k v  ->
+                    ("ArnNotLike",                k, toJSON v)
+    {-# INLINEABLE toJSON #-}
+
+instance FromJSON Condition where
+    parseJSON = JSON.withObject "Condition" $ \o ->
+        let parse :: FromJSON a => Text -> JSON.Parser (Key, a)
+            parse op = do
+                vs <- o .: op
+                case toList (vs :: JSON.Object) of
+                    [(k, v)] -> (Key k,) <$> JSON.parseJSON v
+                    _        ->
+                        fail ("Expected one Condition key in " ++ show vs)
+
+            parseNumber op = do
+                (k, x) <- parse op
+                case Read.readP_to_S scientificP x of
+                    [(n, "")] -> pure (k, n)
+                    _         ->
+                        fail ("Unable to parse numeric Condition from: " ++ show x)
+
+            parseISO8601 op = do
+                (k, x) <- parse op
+                let fmt    = Time.iso8601DateFormat (Just "%H:%M:%S")
+                    locale = Time.defaultTimeLocale
+                case Read.readP_to_S (Time.readPTime False locale fmt) x of
+                    [(t, "")] -> pure (k, t)
+                    _         ->
+                        fail ("Unable to parse date Condition from: " ++ show x)
+
+            parsePOSIX op = do
+                (k, x) <- parseNumber op
+                pure (k, POSIX.posixSecondsToUTCTime (realToFrac x))
+
+            parseDate op =
+                parseISO8601 op <|> parsePOSIX op
+
+            parseBool op = do
+                (k, x) <- parse op
+                case x :: Text of
+                    "true"  -> pure (k, True)
+                    "false" -> pure (k, False)
+                    _       ->
+                        fail ("Unable to parse boolean Condition from: " ++ show x)
+
+            parseBinary =
+                fmap (second Text.encodeUtf8) . parse
+
+            parseBase64 =
+                fmap (second Base64.decodeLenient) . parseBinary
+
+         in uncurry StringEquals
+              <$> parse "StringEquals"
+        <|> uncurry StringNotEquals
+              <$> parse "StringNotEquals"
+        <|> uncurry StringEqualsIgnoreCase
+              <$> parse "StringEqualsIgnoreCase"
+        <|> uncurry StringNotEqualsIgnoreCase
+              <$> parse "StringNotEqualsIgnoreCase"
+        <|> uncurry StringLike
+              <$> parse "StringLike"
+
+        <|> uncurry StringNotLike
+              <$> parse "StringNotLike"
+
+        <|> uncurry NumericEquals
+             <$> parseNumber "NumericEquals"
+        <|> uncurry NumericNotEquals
+             <$> parseNumber "NumericNotEquals"
+        <|> uncurry NumericLessThan
+              <$> parseNumber "NumericLessThan"
+        <|> uncurry NumericLessThanEquals
+              <$> parseNumber "NumericLessThanEquals"
+        <|> uncurry NumericGreaterThan
+              <$> parseNumber "NumericGreaterThan"
+        <|> uncurry NumericGreaterThanEquals
+              <$> parseNumber "NumericGreaterThanEquals"
+
+        <|> uncurry DateEquals
+              <$> parseDate "DateEquals"
+        <|> uncurry DateNotEquals
+              <$> parseDate "DateNotEquals"
+        <|> uncurry DateLessThan
+              <$> parseDate "DateLessThan"
+        <|> uncurry DateLessThanEquals
+              <$> parseDate "DateLessThanEquals"
+        <|> uncurry DateGreaterThan
+              <$> parseDate "DateGreaterThan"
+        <|> uncurry DateGreaterThanEquals
+              <$> parseDate "DateGreaterThanEquals"
+
+        <|> uncurry Bool
+              <$> parseBool "Bool"
+
+        <|> uncurry BinaryEquals
+              <$> parseBase64 "BinaryEquals"
+
+        <|> uncurry IpAddress
+              <$> parseBinary "IpAddress"
+        <|> uncurry NotIpAddress
+              <$> parseBinary "NotIpAddress"
+
+        <|> uncurry ArnEquals
+              <$> parse "ArnEquals"
+        <|> uncurry ArnLike
+              <$> parse "ArnLike"
+        <|> uncurry ArnNotEquals
+              <$> parse "ArnNotEquals"
+        <|> uncurry ArnNotLike
+              <$> parse "ArnNotLike"
+
+        <|> fail ("Unrecognized Condition: " ++ show o)
+    {-# INLINEABLE parseJSON #-}
diff --git a/src/Amazonka/IAM/Policy/Lens.hs b/src/Amazonka/IAM/Policy/Lens.hs
new file mode 100644
--- /dev/null
+++ b/src/Amazonka/IAM/Policy/Lens.hs
@@ -0,0 +1,127 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | This module contains lenses and prisms that can be used with any of the
+-- various lens libraries. It's recommended that you import this module
+-- qualified to avoid ambiguity with "Amazonka.IAM.Policy":
+--
+-- @
+-- import qualified Amazonka.IAM.Policy.Lens as Lens
+-- @
+module Amazonka.IAM.Policy.Lens
+    (
+    -- * Lenses
+
+    -- ** Policy
+      version
+    , id
+    , statement
+
+    -- ** Statement
+    , sid
+    , effect
+    , principal
+    , action
+    , resource
+    , condition
+
+    -- * Prisms
+
+    -- ** Block
+    , _Match
+    , _Not
+
+    -- ** Match
+    , _Some
+    , _Wildcard
+    ) where
+
+import Prelude hiding (id)
+
+import Data.List.NonEmpty     (NonEmpty)
+import Data.Profunctor        (dimap)
+import Data.Profunctor.Choice (Choice (right'))
+
+import Amazonka.IAM.Policy (Action, Block (..), Condition, Effect, Id,
+                            Match (..), Policy, Principal, Resource, Sid,
+                            Statement, Version)
+
+import qualified Amazonka.IAM.Policy as Policy
+
+-- Lenses
+
+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
+
+type Lens' s a = Lens s s a a
+
+id :: Lens' (Policy a) (Maybe Id)
+id f s = (\a -> s { Policy.id = a }) <$> f (Policy.id s)
+{-# INLINEABLE id #-}
+
+version :: Lens' (Policy a) (Maybe Version)
+version f s = (\a -> s { Policy.version = a }) <$> f (Policy.version s)
+{-# INLINEABLE version #-}
+
+statement :: Lens (Policy a) (Policy b) (NonEmpty a) (NonEmpty b)
+statement f s = (\a -> s { Policy.statement = a }) <$> f (Policy.statement s)
+{-# INLINEABLE statement #-}
+
+sid :: Lens' Statement (Maybe Sid)
+sid f s = (\a -> s { Policy.sid = a }) <$> f (Policy.sid s)
+{-# INLINEABLE sid #-}
+
+principal :: Lens' Statement (Maybe (Block Principal))
+principal f s = (\a -> s { Policy.principal = a }) <$> f (Policy.principal s)
+{-# INLINEABLE principal #-}
+
+effect :: Lens' Statement Effect
+effect f s = (\a -> s { Policy.effect = a }) <$> f (Policy.effect s)
+{-# INLINEABLE effect #-}
+
+action :: Lens' Statement (Block [Action])
+action f s = (\a -> s { Policy.action = a }) <$> f (Policy.action s)
+{-# INLINEABLE action #-}
+
+resource :: Lens' Statement (Block [Resource])
+resource f s = (\a -> s { Policy.resource = a }) <$> f (Policy.resource s)
+{-# INLINEABLE resource #-}
+
+condition :: Lens' Statement (Maybe Condition)
+condition f s = (\a -> s { Policy.condition = a }) <$> f (Policy.condition s)
+{-# INLINEABLE condition #-}
+
+-- Prisms
+
+type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)
+
+prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b
+prism bt seta = dimap seta (either pure (fmap bt)) . right'
+{-# INLINE prism #-}
+
+_Match :: Prism (Block a) (Block a) (Match a) (Match a)
+_Match =
+    prism Match $ \case
+        Match x -> Right x
+        y       -> Left  y
+{-# INLINEABLE _Match #-}
+
+_Not :: Prism (Block a) (Block a) (Match a) (Match a)
+_Not =
+    prism Not $ \case
+        Not x -> Right x
+        y     -> Left  y
+{-# INLINEABLE _Not #-}
+
+_Some :: Prism (Match a) (Match b) a b
+_Some =
+    prism Some $ \case
+        Some   x -> Right x
+        Wildcard -> Left  Wildcard
+{-# INLINE _Some #-}
+
+_Wildcard :: Prism (Match a) (Match a) () a
+_Wildcard =
+    prism (const Wildcard) $ \case
+        Wildcard -> Right ()
+        Some x   -> Left  (Some x)
+{-# INLINE _Wildcard #-}
diff --git a/test/Doctest.hs b/test/Doctest.hs
new file mode 100644
--- /dev/null
+++ b/test/Doctest.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Test.DocTest (doctest)
+
+main :: IO ()
+main = doctest ["-isrc", "--fast", "src/Amazonka/IAM/Policy.hs"]
diff --git a/test/Golden.hs b/test/Golden.hs
new file mode 100644
--- /dev/null
+++ b/test/Golden.hs
@@ -0,0 +1,217 @@
+{-# LANGUAGE OverloadedLists   #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import Amazonka.IAM.Policy (Policy, Statement)
+
+import Data.Aeson     (FromJSON)
+import Data.Semigroup ((<>))
+
+import qualified Amazonka.IAM.Policy       as Policy
+import qualified Data.Aeson                as JSON
+import qualified Data.ByteString.Lazy      as LBS
+import qualified Paths_amazonka_iam_policy as Path
+import qualified System.IO.Error           as IO
+import qualified Test.Hspec                as Hspec
+
+main :: IO ()
+main = Hspec.hspec $ do
+    test "iam-policy-simulator.json" $
+        Policy.singleton
+            (Policy.allow
+                { Policy.action =
+                    Policy.some
+                        [ "iam:GetGroup"
+                        , "iam:GetGroupPolicy"
+                        , "iam:GetPolicy"
+                        , "iam:GetPolicyVersion"
+                        , "iam:GetRole"
+                        , "iam:GetRolePolicy"
+                        , "iam:GetUser"
+                        , "iam:GetUserPolicy"
+                        , "iam:ListAttachedGroupPolicies"
+                        , "iam:ListAttachedRolePolicies"
+                        , "iam:ListAttachedUserPolicies"
+                        , "iam:ListGroups"
+                        , "iam:ListGroupPolicies"
+                        , "iam:ListGroupsForUser"
+                        , "iam:ListRolePolicies"
+                        , "iam:ListRoles"
+                        , "iam:ListUserPolicies"
+                        , "iam:ListUsers"
+                        ]
+                , Policy.resource = Policy.any
+                })
+
+    test "iam-policy-simulator.json" $
+        Policy.singleton
+            (Policy.allow
+                { Policy.action =
+                    Policy.some
+                        [ "iam:GetGroup"
+                        , "iam:GetGroupPolicy"
+                        , "iam:GetPolicy"
+                        , "iam:GetPolicyVersion"
+                        , "iam:GetRole"
+                        , "iam:GetRolePolicy"
+                        , "iam:GetUser"
+                        , "iam:GetUserPolicy"
+                        , "iam:ListAttachedGroupPolicies"
+                        , "iam:ListAttachedRolePolicies"
+                        , "iam:ListAttachedUserPolicies"
+                        , "iam:ListGroups"
+                        , "iam:ListGroupPolicies"
+                        , "iam:ListGroupsForUser"
+                        , "iam:ListRolePolicies"
+                        , "iam:ListRoles"
+                        , "iam:ListUserPolicies"
+                        , "iam:ListUsers"
+                        ]
+                , Policy.resource = Policy.any
+                })
+
+    test "iam-user-rotate-credentials.json" $
+          Policy.singleton
+              (Policy.allow
+                  { Policy.action   =
+                      Policy.some
+                          [ "iam:ListUsers"
+                          , "iam:GetAccountPasswordPolicy"
+                          ]
+                  , Policy.resource = Policy.any
+                  })
+
+        <> Policy.singleton
+              (Policy.allow
+                  { Policy.action   =
+                      Policy.some
+                          [ "iam:*AccessKey*"
+                          , "iam:ChangePassword"
+                          , "iam:GetUser"
+                          , "iam:*ServiceSpecificCredential*"
+                          , "iam:*SigningCertificate*"
+                          ]
+                  , Policy.resource =
+                      Policy.some
+                          [ "arn:aws:iam::*:user/${aws:username}"
+                          ]
+                  })
+
+    test "policy-simulator-api.json" $
+        Policy.singleton
+            (Policy.allow
+                { Policy.action   =
+                    Policy.some
+                        [ "iam:GetContextKeysForCustomPolicy"
+                        , "iam:GetContextKeysForPrincipalPolicy"
+                        , "iam:SimulateCustomPolicy"
+                        , "iam:SimulatePrincipalPolicy"
+                        ]
+                , Policy.resource = Policy.any
+                })
+
+    test "policy-simulator-console.json" $
+           Policy.singleton
+               (Policy.allow
+                   { Policy.action  =
+                       Policy.some
+                           [ "iam:GetPolicy"
+                           , "iam:GetUserPolicy"
+                           ]
+                   , Policy.resource = Policy.any
+                   })
+
+        <> Policy.singleton
+               (Policy.allow
+                   { Policy.action   =
+                       Policy.some
+                           [ "iam:GetUser"
+                           , "iam:ListAttachedUserPolicies"
+                           , "iam:ListGroupsForUser"
+                           , "iam:ListUserPolicies"
+                           , "iam:ListUsers"
+                           ]
+                   , Policy.resource =
+                       Policy.some
+                           [ "arn:aws:iam::<ACCOUNTNUMBER>:user/<USER-PATH-NAME>/*"
+                           ]
+                   })
+
+    test "rds-region-admin.json" $
+           Policy.singleton
+               (Policy.allow
+                   { Policy.action   = Policy.some ["rds:*"]
+                   , Policy.resource =
+                       Policy.some
+                           [ "arn:aws:rds:<REGION>:<ACCOUNTNUMBER>:*"
+                           ]
+                   })
+
+        <> Policy.singleton
+               (Policy.allow
+                   { Policy.action   = Policy.some ["rds:Describe*"]
+                   , Policy.resource = Policy.any
+                   })
+
+    test "self-managed-mfa.json" $
+           Policy.singleton
+               (Policy.allow
+                   { Policy.action =
+                       Policy.some
+                           [ "iam:CreateVirtualMFADevice"
+                           , "iam:EnableMFADevice"
+                           , "iam:ResyncMFADevice"
+                           , "iam:DeleteVirtualMFADevice"
+                           ]
+                   , Policy.resource =
+                       Policy.some
+                           [ "arn:aws:iam::*:mfa/${aws:username}"
+                           , "arn:aws:iam::*:user/${aws:username}"
+                           ]
+                   })
+
+        <> Policy.singleton
+               (Policy.allow
+                   { Policy.sid       =
+                       Just "AllowUsersToDeactivateTheirOwnVirtualMFADevice"
+                   , Policy.action    =
+                       Policy.some
+                           [ "iam:DeactivateMFADevice"
+                           ]
+                   , Policy.resource  =
+                       Policy.some
+                           [ "arn:aws:iam::*:mfa/${aws:username}"
+                           , "arn:aws:iam::*:user/${aws:username}"
+                           ]
+                   , Policy.condition =
+                       Just $ Policy.Bool "aws:MultiFactorAuthPresent" True
+                   })
+
+        <> Policy.singleton
+               (Policy.allow
+                   { Policy.action   =
+                       Policy.some
+                           [ "iam:ListMFADevices"
+                           , "iam:ListVirtualMFADevices"
+                           , "iam:ListUsers"
+                           ]
+                   , Policy.resource = Policy.any
+                   })
+
+test :: String -> Policy Statement -> Hspec.Spec
+test name actual =
+    Hspec.describe name $
+        Hspec.it "should equal the serialized haskell value" $
+            parseFile ("test/golden/" ++ name)
+                >>= Hspec.shouldBe actual
+
+parseFile :: FromJSON a => String -> IO a
+parseFile name = do
+    path <- Path.getDataFileName name
+    lbs  <- LBS.readFile path
+    case JSON.eitherDecode' lbs of
+        Right x -> pure x
+        Left  e ->
+            IO.ioError $
+                IO.userError ("Failed parsing " ++ path ++ ": " ++ e)
diff --git a/test/golden/iam-policy-simulator.json b/test/golden/iam-policy-simulator.json
new file mode 100644
--- /dev/null
+++ b/test/golden/iam-policy-simulator.json
@@ -0,0 +1,29 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": [
+                "iam:GetGroup",
+                "iam:GetGroupPolicy",
+                "iam:GetPolicy",
+                "iam:GetPolicyVersion",
+                "iam:GetRole",
+                "iam:GetRolePolicy",
+                "iam:GetUser",
+                "iam:GetUserPolicy",
+                "iam:ListAttachedGroupPolicies",
+                "iam:ListAttachedRolePolicies",
+                "iam:ListAttachedUserPolicies",
+                "iam:ListGroups",
+                "iam:ListGroupPolicies",
+                "iam:ListGroupsForUser",
+                "iam:ListRolePolicies",
+                "iam:ListRoles",
+                "iam:ListUserPolicies",
+                "iam:ListUsers"
+       ],
+      "Effect": "Allow",
+      "Resource":"*"
+    }
+  ]
+}
diff --git a/test/golden/iam-user-rotate-credentials.json b/test/golden/iam-user-rotate-credentials.json
new file mode 100644
--- /dev/null
+++ b/test/golden/iam-user-rotate-credentials.json
@@ -0,0 +1,24 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [
+                "iam:ListUsers",
+                "iam:GetAccountPasswordPolicy"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "iam:*AccessKey*",
+                "iam:ChangePassword",
+                "iam:GetUser",
+                "iam:*ServiceSpecificCredential*",
+                "iam:*SigningCertificate*"
+            ],
+            "Resource": ["arn:aws:iam::*:user/${aws:username}"]
+        }
+    ]
+}
diff --git a/test/golden/policy-simulator-api.json b/test/golden/policy-simulator-api.json
new file mode 100644
--- /dev/null
+++ b/test/golden/policy-simulator-api.json
@@ -0,0 +1,15 @@
+{
+  "Version" : "2012-10-17",
+  "Statement" : [
+    {
+      "Action" : [
+        "iam:GetContextKeysForCustomPolicy",
+        "iam:GetContextKeysForPrincipalPolicy",
+        "iam:SimulateCustomPolicy",
+        "iam:SimulatePrincipalPolicy"
+      ],
+      "Effect" : "Allow",
+      "Resource" : ["*"]
+    }
+  ]
+}
diff --git a/test/golden/policy-simulator-console.json b/test/golden/policy-simulator-console.json
new file mode 100644
--- /dev/null
+++ b/test/golden/policy-simulator-console.json
@@ -0,0 +1,24 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": [
+        "iam:GetPolicy",
+        "iam:GetUserPolicy"
+      ],
+      "Effect": "Allow",
+      "Resource": "*"
+    },
+    {
+      "Action": [
+        "iam:GetUser",
+        "iam:ListAttachedUserPolicies",
+        "iam:ListGroupsForUser",
+        "iam:ListUserPolicies",
+        "iam:ListUsers"
+      ],
+      "Effect": "Allow",
+      "Resource": "arn:aws:iam::<ACCOUNTNUMBER>:user/<USER-PATH-NAME>/*"
+    }
+  ]
+}
diff --git a/test/golden/rds-region-admin.json b/test/golden/rds-region-admin.json
new file mode 100644
--- /dev/null
+++ b/test/golden/rds-region-admin.json
@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+      {
+          "Effect": "Allow",
+          "Action": "rds:*",
+          "Resource": [
+              "arn:aws:rds:<REGION>:<ACCOUNTNUMBER>:*"
+          ]
+      },
+      {
+          "Effect": "Allow",
+          "Action": [
+              "rds:Describe*"
+          ],
+          "Resource": [
+              "*"
+          ]
+      }
+  ]
+}
diff --git a/test/golden/rds-tagged-resources.json b/test/golden/rds-tagged-resources.json
new file mode 100644
--- /dev/null
+++ b/test/golden/rds-tagged-resources.json
@@ -0,0 +1,108 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+      {
+          "Action": [
+              "rds:Describe*",
+              "rds:List*"
+          ],
+          "Effect": "Allow",
+          "Resource": "*"
+      },
+      {
+          "Action": [
+              "rds:DeleteDBInstance",
+              "rds:RebootDBInstance",
+              "rds:ModifyDBInstance"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:db-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:ModifyOptionGroup",
+              "rds:DeleteOptionGroup"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:og-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:ModifyDBParameterGroup",
+              "rds:ResetDBParameterGroup"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:pg-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:AuthorizeDBSecurityGroupIngress",
+              "rds:RevokeDBSecurityGroupIngress",
+              "rds:DeleteDBSecurityGroup"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:secgrp-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:DeleteDBSnapshot",
+              "rds:RestoreDBInstanceFromDBSnapshot"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:snapshot-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:ModifyDBSubnetGroup",
+              "rds:DeleteDBSubnetGroup"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:subgrp-tag/Owner": "${aws:username}"
+              }
+          }
+      },
+      {
+          "Action": [
+              "rds:ModifyEventSubscription",
+              "rds:AddSourceIdentifierToSubscription",
+              "rds:RemoveSourceIdentifierFromSubscription",
+              "rds:DeleteEventSubscription"
+          ],
+          "Effect": "Allow",
+          "Resource": "*",
+          "Condition": {
+              "StringEqualsIgnoreCase": {
+                  "rds:es-tag/Owner": "${aws:username}"
+              }
+          }
+      }
+  ]
+}
diff --git a/test/golden/s3-bucket-management.json b/test/golden/s3-bucket-management.json
new file mode 100644
--- /dev/null
+++ b/test/golden/s3-bucket-management.json
@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "s3:*",
+      "Resource": [
+        "arn:aws:s3:::<BUCKET-NAME>",
+        "arn:aws:s3:::<BUCKET-NAME>/*"
+      ]
+    },
+    {
+      "Effect": "Deny",
+      "NotAction": "s3:*",
+      "NotResource": [
+        "arn:aws:s3:::<BUCKET-NAME>",
+        "arn:aws:s3:::<BUCKET-NAME>/*"
+      ]
+    }
+  ]
+}
diff --git a/test/golden/s3-bucket-read-write.json b/test/golden/s3-bucket-read-write.json
new file mode 100644
--- /dev/null
+++ b/test/golden/s3-bucket-read-write.json
@@ -0,0 +1,18 @@
+{
+   "Version": "2012-10-17",
+   "Statement": [
+     {
+       "Effect": "Allow",
+       "Action": ["s3:ListBucket"],
+       "Resource": ["arn:aws:s3:::<BUCKET-NAME>"]
+     },
+     {
+       "Effect": "Allow",
+       "Action": [
+         "s3:PutObject",
+         "s3:GetObject"
+       ],
+       "Resource": ["arn:aws:s3:::<BUCKET-NAME>/*"]
+     }
+   ]
+ }
diff --git a/test/golden/s3-cognito-bucket-objects.json b/test/golden/s3-cognito-bucket-objects.json
new file mode 100644
--- /dev/null
+++ b/test/golden/s3-cognito-bucket-objects.json
@@ -0,0 +1,23 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": ["s3:ListBucket"],
+      "Resource": ["arn:aws:s3:::<BUCKET-NAME>"],
+      "Condition": {"StringLike": {"s3:prefix": ["cognito/<APPLICATION-NAME>/"]}}
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "s3:GetObject",
+        "s3:PutObject",
+        "s3:DeleteObject"
+      ],
+      "Resource": [
+        "arn:aws:s3:::<BUCKET-NAME>/cognito/<APPLICATION-NAME>/${cognito-identity.amazonaws.com:sub}",
+        "arn:aws:s3:::<BUCKET-NAME>/cognito/<APPLICATION-NAME>/${cognito-identity.amazonaws.com:sub}/*"
+      ]
+    }
+  ]
+}
diff --git a/test/golden/s3-iam-user-home-directory.json b/test/golden/s3-iam-user-home-directory.json
new file mode 100644
--- /dev/null
+++ b/test/golden/s3-iam-user-home-directory.json
@@ -0,0 +1,31 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "s3:ListAllMyBuckets",
+        "s3:GetBucketLocation"
+      ],
+      "Resource": "arn:aws:s3:::*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "s3:ListBucket",
+      "Resource": "arn:aws:s3:::<BUCKET-NAME>",
+      "Condition": {"StringLike": {"s3:prefix": [
+        "",
+        "home/",
+        "home/${aws:username}/*"
+      ]}}
+    },
+    {
+      "Effect": "Allow",
+      "Action": "s3:*",
+      "Resource": [
+        "arn:aws:s3:::<BUCKET-NAME>/home/${aws:username}",
+        "arn:aws:s3:::<BUCKET-NAME>/home/${aws:username}/*"
+      ]
+    }
+  ]
+}
diff --git a/test/golden/self-managed-mfa.json b/test/golden/self-managed-mfa.json
new file mode 100644
--- /dev/null
+++ b/test/golden/self-managed-mfa.json
@@ -0,0 +1,43 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:CreateVirtualMFADevice",
+        "iam:EnableMFADevice",
+        "iam:ResyncMFADevice",
+        "iam:DeleteVirtualMFADevice"
+      ],
+      "Resource": [
+        "arn:aws:iam::*:mfa/${aws:username}",
+        "arn:aws:iam::*:user/${aws:username}"
+      ]
+    },
+    {
+      "Sid": "AllowUsersToDeactivateTheirOwnVirtualMFADevice",
+      "Effect": "Allow",
+      "Action": [
+        "iam:DeactivateMFADevice"
+      ],
+      "Resource": [
+        "arn:aws:iam::*:mfa/${aws:username}",
+        "arn:aws:iam::*:user/${aws:username}"
+      ],
+      "Condition": {
+        "Bool": {
+          "aws:MultiFactorAuthPresent": "true"
+        }
+      }
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:ListMFADevices",
+        "iam:ListVirtualMFADevices",
+        "iam:ListUsers"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
