amazonka-iam-policy (empty) → 0.0.1
raw patch · 19 files changed
+2531/−0 lines, 19 filesdep +aesondep +aeson-prettydep +amazonka-iam-policy
Dependencies added: aeson, aeson-pretty, amazonka-iam-policy, base, base64-bytestring, bytestring, doctest, hspec, profunctors, scientific, text, time
Files
- CHANGELOG.md +0/−0
- LICENSE +367/−0
- README.md +97/−0
- amazonka-iam-policy.cabal +97/−0
- src/Amazonka/IAM/Policy.hs +1263/−0
- src/Amazonka/IAM/Policy/Lens.hs +127/−0
- test/Doctest.hs +6/−0
- test/Golden.hs +217/−0
- test/golden/iam-policy-simulator.json +29/−0
- test/golden/iam-user-rotate-credentials.json +24/−0
- test/golden/policy-simulator-api.json +15/−0
- test/golden/policy-simulator-console.json +24/−0
- test/golden/rds-region-admin.json +21/−0
- test/golden/rds-tagged-resources.json +108/−0
- test/golden/s3-bucket-management.json +21/−0
- test/golden/s3-bucket-read-write.json +18/−0
- test/golden/s3-cognito-bucket-objects.json +23/−0
- test/golden/s3-iam-user-home-directory.json +31/−0
- test/golden/self-managed-mfa.json +43/−0
+ CHANGELOG.md view
+ LICENSE view
@@ -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.
+ README.md view
@@ -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/).
+ amazonka-iam-policy.cabal view
@@ -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
+ src/Amazonka/IAM/Policy.hs view
@@ -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:++@+{-\# LANGUAGE OverloadedStrings \#-}++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:++@+{-\# LANGUAGE OverloadedLists \#-}+{-\# LANGUAGE OverloadedStrings \#-}++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 #-}
+ src/Amazonka/IAM/Policy/Lens.hs view
@@ -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 #-}
+ test/Doctest.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Test.DocTest (doctest)++main :: IO ()+main = doctest ["-isrc", "--fast", "src/Amazonka/IAM/Policy.hs"]
+ test/Golden.hs view
@@ -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)
+ test/golden/iam-policy-simulator.json view
@@ -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":"*"+ }+ ]+}
+ test/golden/iam-user-rotate-credentials.json view
@@ -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}"]+ }+ ]+}
+ test/golden/policy-simulator-api.json view
@@ -0,0 +1,15 @@+{+ "Version" : "2012-10-17",+ "Statement" : [+ {+ "Action" : [+ "iam:GetContextKeysForCustomPolicy",+ "iam:GetContextKeysForPrincipalPolicy",+ "iam:SimulateCustomPolicy",+ "iam:SimulatePrincipalPolicy"+ ],+ "Effect" : "Allow",+ "Resource" : ["*"]+ }+ ]+}
+ test/golden/policy-simulator-console.json view
@@ -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>/*"+ }+ ]+}
+ test/golden/rds-region-admin.json view
@@ -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": [+ "*"+ ]+ }+ ]+}
+ test/golden/rds-tagged-resources.json view
@@ -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}"+ }+ }+ }+ ]+}
+ test/golden/s3-bucket-management.json view
@@ -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>/*"+ ]+ }+ ]+}
+ test/golden/s3-bucket-read-write.json view
@@ -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>/*"]+ }+ ]+ }
+ test/golden/s3-cognito-bucket-objects.json view
@@ -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}/*"+ ]+ }+ ]+}
+ test/golden/s3-iam-user-home-directory.json view
@@ -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}/*"+ ]+ }+ ]+}
+ test/golden/self-managed-mfa.json view
@@ -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": "*"+ }+ ]+}