Repository: salesforce/policy_sentry
Pull request #131 (2020-02-19 21:38:50)

Feat/wildcard only with access levels
* Template is modified again. This allows easy additions of wildcard-only actions with access levels specific to services (such as "S3 actions at read access level that do not support resource constraints"), so you never have to look at individual IAM actions again.
* Docs are heavily restructured


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7caec34c..61b3957b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,8 @@
 Other Pending:
 * Update the GIF
 
-## Next release (Unreleased)
+## 0.7.1 (Unreleased)
+* Template is modified again. This allows easy additions of wildcard-only actions with access levels, so you never have to look at individual IAM actions again.
 * Fixes #124. Output will be in UpperCamelCase rather than all lowercase, for human readability. Note that if `--minimize` is specified for `write-policy`, it will give me lowercase. Otherwise, it will be UpperCamelCase.
 * Terraform module - #112
 * Small bugs - #126
diff --git a/README.md b/README.md
index 4b91fbfc..3310f51a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
 # Policy Sentry
 
 IAM Least Privilege Policy Generator and analysis database.
@@ -12,7 +11,7 @@ IAM Least Privilege Policy Generator and analysis database.
 
 - [Documentation](#documentation)
 - [Overview](#overview)
-  * [Authoring Secure IAM Policies](#authoring-secure-iam-policies)
+  * [Writing Secure Policies based on Resource Constraints and Access Levels](#writing-secure-policies-based-on-resource-constraints-and-access-levels)
 - [Quickstart](#quickstart)
     + [Installation](#installation)
     + [Shell completion](#shell-completion)
@@ -43,7 +42,11 @@ Writing security-conscious IAM Policies by hand can be very tedious and ineffici
 
 Such a process is not ideal for security or for Infrastructure as Code developers. We need to make it easier to write IAM Policies securely and abstract the complexity of writing least-privilege IAM policies. That's why I made this tool.
 
-### Authoring Secure IAM Policies
+Policy Sentry allows users to create least-privilege IAM policies in a matter of seconds, rather than tediously writing IAM policies by hand. These policies are scoped down according to access levels and resources. In the case of a breach, this helps to limit the blast radius of compromised credentials by only giving IAM principals access to what they need.
+
+**Before this tool, it could take hours to craft the perfect IAM Policy — but now it can take a matter of seconds**. This way, developers only have to determine the resources that they need to access, and **Policy Sentry abstracts the complexity of IAM policies** away from their development processes.
+
+### Writing Secure Policies based on Resource Constraints and Access Levels
 
 Policy Sentry's flagship feature is that it can create IAM policies based on resource ARNs and access levels. Our CRUD functionality takes the opinionated approach that IAC developers shouldn't have to understand the complexities of AWS IAM - we should abstract the complexity for them. In fact, developers should just be able to say...
 
@@ -107,10 +110,7 @@ It will generate a file like this:
 ```yaml
 mode: crud
 name: myRole
-description: '' # For human auditability
-role_arn: '' # For human auditability
-# Insert ARNs under each access level below
-# If you do not need to use certain access levels, delete them.
+# Specify resource ARNs
 read:
 - ''
 write:
@@ -121,10 +121,21 @@ tagging:
 - ''
 permissions-management:
 - ''
-# If the policy needs to use IAM actions that cannot be restricted to ARNs,
-# like ssm:DescribeParameters, specify those actions here.
-wildcard:
-- ''
+# Actions that do not support resource constraints
+wildcard-only:
+  single-actions: # standalone actions
+  - ''
+  # Service-wide, per access level - like 's3' or 'ec2'
+  service-read:
+  - ''
+  service-write:
+  - ''
+  service-list:
+  - ''
+  service-tagging:
+  - ''
+  service-permissions-management:
+  - ''
 ```
 
 Then just fill it out:
@@ -132,8 +143,6 @@ Then just fill it out:
 ```yaml
 mode: crud
 name: myRole
-description: 'Justification for privileges'
-role_arn: 'arn:aws:iam::123456789102:role/myRole'
 read:
 - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
 write:
diff --git a/docs/introduction/comparison-to-similar-tools.rst b/docs/appendices/comparison-to-related-tools.rst
similarity index 99%
rename from docs/introduction/comparison-to-similar-tools.rst
rename to docs/appendices/comparison-to-related-tools.rst
index d0782afe..b4cf7fff 100644
--- a/docs/introduction/comparison-to-similar-tools.rst
+++ b/docs/appendices/comparison-to-related-tools.rst
@@ -1,5 +1,5 @@
-Comparison to other tools
--------------------------
+Comparison to related tools
+---------------------------
 
 Policy Revocation Tools
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/iam-knowledge/iam-policies.rst b/docs/appendices/iam-policies.rst
similarity index 100%
rename from docs/iam-knowledge/iam-policies.rst
rename to docs/appendices/iam-policies.rst
diff --git a/docs/appendix/implementation-strategy.rst b/docs/appendices/implementation-strategy.rst
similarity index 94%
rename from docs/appendix/implementation-strategy.rst
rename to docs/appendices/implementation-strategy.rst
index 04e19855..33e0262b 100644
--- a/docs/appendix/implementation-strategy.rst
+++ b/docs/appendices/implementation-strategy.rst
@@ -4,18 +4,18 @@ Implementation Strategy
 In the context of your overall organization strategy for AWS IAM, we recommend using a few measures for locking down your AWS environments with IAM:
 
 
-#. Use `policy_sentry <https://github.com/salesforce/policy_sentry/>`_ to create `Identity-based policies <https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html>`_
+#. Use `Policy Sentry <https://github.com/salesforce/policy_sentry/>`_ to create `Identity-based policies <https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html>`_
 #. Use Service Control Policies (SCPs) to lock down available API calls per account.
 
    * A great collection of SCPs can be found `on asecure.cloud <https://asecure.cloud/l/scp/>`_.
    * Control Tower has some excellent guidance on strategy for SCPs in their documentation. Note that they call it "Guardrails" but they are mostly SCPs. See the docs `here <https://docs.aws.amazon.com/controltower/latest/userguide/guardrails-reference.html>`_
 
 #. Use `Repokid <https://medium.com/netflix-techblog/introducing-aardvark-and-repokid-53b081bf3a7e>`_ to revoke out of date policies as your application/roles mature.
-#. Use `Resource-based policies <https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html>`_ for all services that support them. 
+#. Use `Resource-based policies <https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html>`_ for all services that support them.
 
    * A list of which services support resource-based policies can be found `in the AWS documentation here <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html>`_.
 
-#. Never provision infrastructure manually; use Infrastructure as Code 
+#. Never provision infrastructure manually; use Infrastructure as Code
 
    * I highly suggest Terraform for IAC over other alternatives such as CloudFormation, Chef, or Puppet. Yevgeniy Brikman explains the reasons very well in `this Gruntwork.io blog post <https://blog.gruntwork.io/why-we-use-terraform-and-not-chef-puppet-ansible-saltstack-or-cloudformation-7989dad2865c>`_.
    * I also suggest reading HashiCorp's `Unlocking the Cloud Operating Model Whitepaper <https://www.hashicorp.com/cloud-operating-model>`_.
diff --git a/docs/appendices/index.rst b/docs/appendices/index.rst
new file mode 100644
index 00000000..36a9dfbf
--- /dev/null
+++ b/docs/appendices/index.rst
@@ -0,0 +1,14 @@
+Appendices
+=============
+
+Additional knowledge bases and relevant documentation are covered here.
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Appendices
+
+   implementation-strategy
+   comparison-to-related-tools
+   iam-policies
+   minimization
diff --git a/docs/iam-knowledge/minimization.rst b/docs/appendices/minimization.rst
similarity index 100%
rename from docs/iam-knowledge/minimization.rst
rename to docs/appendices/minimization.rst
diff --git a/docs/index.rst b/docs/index.rst
index 59b7d3bf..05ef21ba 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,9 +16,7 @@ Policy Sentry's policy writing templates are expressed in YAML and include the f
 
 Policy Sentry can also be used to:
 
-* `Audit IAM Policies <https://policy-sentry.readthedocs.io/en/latest/user-guide/analyze-policy.html>`__ based on access levels
 * `Query the IAM database <https://policy-sentry.readthedocs.io/en/latest/user-guide/querying-the-database.html>`__ to reduce manual search time
-* `Download live policies <https://policy-sentry.readthedocs.io/en/latest/user-guide/downloading-policies.html>`__ from an AWS account auditing purposes
 * `Generate IAM Policies based on Terraform output <https://policy-sentry.readthedocs.io/en/latest/terraform/terraform-demo.html>`__
 * `Write least-privilege IAM Policies <https://policy-sentry.readthedocs.io/en/latest/user-guide/write-policy.html>`__ based on a list of IAM actions (or CRUD levels)
 
@@ -30,29 +28,21 @@ Navigate below to get started with Policy Sentry!
    :maxdepth: 1
    :caption: Introduction
 
-   introduction/home
-   introduction/comparison-to-similar-tools
+   introduction/introduction.rst
 
 .. toctree::
-   :maxdepth: 1
+   :maxdepth: 3
    :caption: User Guide
 
-   user-guide/installation
-   user-guide/command-cheat-sheet
-   user-guide/write-policy
-   user-guide/querying-the-database
-   user-guide/docker
-   user-guide/initialize
-
-.. toctree::
-   :maxdepth: 1
-   :caption: Terraform
-
-   terraform/terraform-demo
+   user-guide/installation/index.rst
+   user-guide/writing-policies/index.rst
+   user-guide/querying/index.rst
+   user-guide/terraform/index.rst
+   user-guide/cheat-sheet
 
 
 .. toctree::
-   :maxdepth: 3
+   :maxdepth: 2
    :caption: Contributing
 
    contributing/index.rst
@@ -74,15 +64,7 @@ Navigate below to get started with Policy Sentry!
    :maxdepth: 1
    :caption: Appendix
 
-   appendix/implementation-strategy
-
-
-.. toctree::
-   :maxdepth: 1
-   :caption: IAM Background
-
-   iam-knowledge/iam-policies
-   iam-knowledge/minimization
+   appendices/index.rst
 
 
 Indices and tables
diff --git a/docs/introduction/home.rst b/docs/introduction/introduction.rst
similarity index 94%
rename from docs/introduction/home.rst
rename to docs/introduction/introduction.rst
index 2ffac281..df81e3d1 100644
--- a/docs/introduction/home.rst
+++ b/docs/introduction/introduction.rst
@@ -1,8 +1,6 @@
 Overview
 =============
 
-`Policy Sentry <https://github.com/salesforce/policy_sentry>`__ is an IAM Least Privilege Policy Generator, auditor, and analysis database.
-
 Motivation
 ----------
 
@@ -222,23 +220,3 @@ Usage
   - Option 3: Query the Conditions Table (``condition-table``)
 
 * ``initialize``\ : (Optional) Create a SQLite database that contains all of the services available through the `Actions, Resources, and Condition Keys documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html>`_. See the `documentation <https://policy-sentry.readthedocs.io/en/latest/user-guide/initialize.html>`__.
-
-
-Author Information
-------------------
-
-Author:
-
-* `Kinnaird McQuade <https://github.com/kmcquade>`_
-
-  * `Twitter <https://twitter.com/kmcquade3>`__
-  * `Keybase <https://keybase.io/kmcquade>`__
-  * `LinkedIn <https://www.linkedin.com/in/kinnairdmcquade/>`__
-
-Contributors:
-
-* `Matt Jones <https://github.com/mattyjones>`__
-
-  * `Twitter <https://twitter.com/CaffeinatedEng>`__
-  * `Keybase <https://keybase.io/urlugal>`__
-  * `LinkedIn <https://www.linkedin.com/in/mattyjones/>`__
diff --git a/docs/user-guide/command-cheat-sheet.rst b/docs/user-guide/cheat-sheet.rst
similarity index 96%
rename from docs/user-guide/command-cheat-sheet.rst
rename to docs/user-guide/cheat-sheet.rst
index 9e35c9c1..794e1b6c 100644
--- a/docs/user-guide/command-cheat-sheet.rst
+++ b/docs/user-guide/cheat-sheet.rst
@@ -1,4 +1,4 @@
-Command cheat sheet
+Cheat sheet
 -------------------
 
 Commands
@@ -20,9 +20,9 @@ Commands
 
 * ``query``: Query the IAM database tables. This can help when filling out the Policy Sentry templates, or just querying the database for quick knowledge.
 
-  * Option 1: Query the Actions Table (``--table action``)
-  * Option 2: Query the ARNs Table (``--table arn``)
-  * Option 3: Query the Conditions Table (``--table condition``)
+  * Option 1: Query the Actions Table (``action-table``)
+  * Option 2: Query the ARNs Table (``arn-table``)
+  * Option 3: Query the Conditions Table (``condition-table``)
 
 *
   ``initialize``\ : (Optional) Create a SQLite database that contains all of the services available through the `Actions, Resources, and Condition Keys documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html>`__. See the `documentation <./initialize.html>`__.
diff --git a/docs/user-guide/docker.rst b/docs/user-guide/installation/docker.rst
similarity index 90%
rename from docs/user-guide/docker.rst
rename to docs/user-guide/installation/docker.rst
index 1ae1e836..4bfc3e51 100644
--- a/docs/user-guide/docker.rst
+++ b/docs/user-guide/installation/docker.rst
@@ -1,5 +1,5 @@
-Docker
-##################
+Building the Docker Image
+##########################
 
 If you prefer using Docker instead of installing the script with Python, we support that as well.
 
@@ -9,6 +9,8 @@ Use this to build the docker image:
 
     docker build -t kmcquade/policy_sentry .
 
+Using the Docker Image
+######################
 
 Use this to run some basic commands:
 
diff --git a/docs/user-guide/installation.rst b/docs/user-guide/installation/index.rst
similarity index 76%
rename from docs/user-guide/installation.rst
rename to docs/user-guide/installation/index.rst
index 83c855f1..88809237 100644
--- a/docs/user-guide/installation.rst
+++ b/docs/user-guide/installation/index.rst
@@ -26,3 +26,13 @@ To enable ZSH completion, put this in your `.zshrc`:
    eval "$(_POLICY_SENTRY_COMPLETE=source_zsh policy_sentry)"
 
 
+Docker
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: docker.rst
+
+
+Rebuilding the IAM Database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: initialize.rst
diff --git a/docs/user-guide/initialize.rst b/docs/user-guide/installation/initialize.rst
similarity index 98%
rename from docs/user-guide/initialize.rst
rename to docs/user-guide/installation/initialize.rst
index 55405ec1..4963c7aa 100644
--- a/docs/user-guide/initialize.rst
+++ b/docs/user-guide/installation/initialize.rst
@@ -1,5 +1,5 @@
-Initialization (Optional)
-#########################
+Initialize
+#############
 
 `initialize`: This will create a SQLite database that contains all of the services available through the `Actions, Resources, and Condition Keys documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html>`__.
 
diff --git a/docs/user-guide/querying-the-database.rst b/docs/user-guide/querying-the-database.rst
deleted file mode 100644
index 4f2090d0..00000000
--- a/docs/user-guide/querying-the-database.rst
+++ /dev/null
@@ -1,125 +0,0 @@
-Querying the IAM Policy Database
---------------------------------
-
-Policy Sentry relies on a SQLite database, generated at `initialize` time, which contains all of the services available through the `Actions, Resources, and Condition Keys documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html>`__. The HTML files from that AWS documentation is scraped and stored in the SQLite database, which is then stored in ``$HOME/.policy_sentry/aws.sqlite3``.
-
-Policy Sentry supports querying that database through the CLI. This can help with writing policies and generally knowing what values to supply in your policies.
-
----------
-Commands
----------
-
-* Query the **Action**\  table:
-
-.. code-block:: bash
-
-    # NOTE: Use --fmt yaml or --fmt json to change the output format. Defaults to json for querying
-
-    # Get a list of actions that do not support resource constraints
-    policy_sentry query action-table --service s3 --wildcard-only --fmt yaml
-
-    # Get a list of actions at the "Read" level in S3 that do not support resource constraints
-    policy_sentry query action-table --service s3 --access-level read --wildcard-only --fmt yaml
-
-    # Get a list of all IAM Actions available to the RAM service
-    policy_sentry query action-table --service ram
-
-    # Get details about the `ram:TagResource` IAM Action
-    policy_sentry query action-table --service ram --name tagresource
-
-    # Get a list of all IAM actions under the RAM service that have the Permissions management access level.
-    policy_sentry query action-table --service ram --access-level permissions-management
-
-    # Get a list of all IAM actions under the SES service that support the `ses:FeedbackAddress` condition key.
-    policy_sentry query action-table --service ses --condition ses:FeedbackAddress
-
-* Query the **ARN**\  table:
-
-.. code-block:: bash
-
-    # Get a list of all RAW ARN formats available through the SSM service.
-    policy_sentry query arn-table --service ssm
-
-    # Get the raw ARN format for the `cloud9` ARN with the short name `environment`
-    policy_sentry query arn-table --service cloud9 --name environment
-
-    # Get key/value pairs of all RAW ARN formats plus their short names
-    policy_sentry query arn-table --service cloud9 --list-arn-types
-
-* Query the **Condition Keys**\  table:
-
-.. code-block:: bash
-
-    # Get a list of all condition keys available to the Cloud9 service
-    policy_sentry query condition-table --service cloud9
-
-    # Get details on the condition key titled `cloud9:Permissions`
-    policy_sentry query condition-table --service cloud9 --name cloud9:Permissions
-
----------
-Options
----------
-
-* action-table
-
-.. code-block:: text
-
-    Usage: policy_sentry query action-table [OPTIONS]
-
-    Options:
-      --service TEXT                  Filter according to AWS service.  [required]
-      --name TEXT                     The name of IAM Action. For example, if the
-                                      action is "iam:ListUsers", supply
-                                      "ListUsers" here.
-      --access-level [read|write|list|tagging|permissions-management]
-                                      If action table is chosen, you can use this
-                                      to filter according to CRUD levels.
-                                      Acceptable values are read, write, list,
-                                      tagging, permissions-management
-      --condition TEXT                If action table is chosen, you can supply a
-                                      condition key to show a list of all IAM
-                                      actions that support the condition key.
-      --wildcard-only                 If action table is chosen, show the IAM
-                                      actions that only support wildcard resources
-                                      - i.e., cannot support ARNs in the resource
-                                      block.
-      --fmt [yaml|json]               Format output as YAML or JSON. Defaults to
-                                      "yaml"
-      --log-level                     Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-      --help                          Show this message and exit.
-
-* arn-table
-
-.. code-block:: text
-
-    Usage: policy_sentry query arn-table [OPTIONS]
-
-      Query the ARN Table from the Policy Sentry database
-
-    Options:
-      --service TEXT     Filter according to AWS service.  [required]
-      --name TEXT        The short name of the resource ARN type. For example,
-                         `bucket` under service `s3`.
-      --list-arn-types   Show the short names of ARN Types. If empty, this will
-                         show RAW ARNs only.
-      --fmt [yaml|json]  Format output as YAML or JSON. Defaults to "yaml"
-      --log-level        Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-      --help             Show this message and exit.
-
-* condition-table
-
-.. code-block:: text
-
-    Usage: policy_sentry query condition-table [OPTIONS]
-
-      Query the condition keys table from the Policy Sentry database
-
-    Options:
-      --name TEXT        Get details on a specific condition key. Leave this blank
-                         to get a list of all condition keys available to the
-                         service.
-      --service TEXT     Filter according to AWS service.  [required]
-      --fmt [yaml|json]  Format output as YAML or JSON. Defaults to "yaml"
-      --log-level        Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-      --help             Show this message and exit.
-
diff --git a/docs/user-guide/querying/action-table.rst b/docs/user-guide/querying/action-table.rst
new file mode 100644
index 00000000..6e4adbe5
--- /dev/null
+++ b/docs/user-guide/querying/action-table.rst
@@ -0,0 +1,54 @@
+Action table
+===============
+
+.. code-block:: bash
+
+    # NOTE: Use --fmt yaml or --fmt json to change the output format. Defaults to json for querying
+
+    # Get a list of actions that do not support resource constraints
+    policy_sentry query action-table --service s3 --wildcard-only --fmt yaml
+
+    # Get a list of actions at the "Read" level in S3 that do not support resource constraints
+    policy_sentry query action-table --service s3 --access-level read --wildcard-only --fmt yaml
+
+    # Get a list of all IAM Actions available to the RAM service
+    policy_sentry query action-table --service ram
+
+    # Get details about the `ram:TagResource` IAM Action
+    policy_sentry query action-table --service ram --name tagresource
+
+    # Get a list of all IAM actions under the RAM service that have the Permissions management access level.
+    policy_sentry query action-table --service ram --access-level permissions-management
+
+    # Get a list of all IAM actions under the SES service that support the `ses:FeedbackAddress` condition key.
+    policy_sentry query action-table --service ses --condition ses:FeedbackAddress
+
+---------
+Options
+---------
+
+.. code-block:: text
+
+    Usage: policy_sentry query action-table [OPTIONS]
+
+    Options:
+      --service TEXT                  Filter according to AWS service.  [required]
+      --name TEXT                     The name of IAM Action. For example, if the
+                                      action is "iam:ListUsers", supply
+                                      "ListUsers" here.
+      --access-level [read|write|list|tagging|permissions-management]
+                                      If action table is chosen, you can use this
+                                      to filter according to CRUD levels.
+                                      Acceptable values are read, write, list,
+                                      tagging, permissions-management
+      --condition TEXT                If action table is chosen, you can supply a
+                                      condition key to show a list of all IAM
+                                      actions that support the condition key.
+      --wildcard-only                 If action table is chosen, show the IAM
+                                      actions that only support wildcard resources
+                                      - i.e., cannot support ARNs in the resource
+                                      block.
+      --fmt [yaml|json]               Format output as YAML or JSON. Defaults to
+                                      "yaml"
+      --log-level                     Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+      --help                          Show this message and exit.
diff --git a/docs/user-guide/querying/arn-table.rst b/docs/user-guide/querying/arn-table.rst
new file mode 100644
index 00000000..9a1c0aa1
--- /dev/null
+++ b/docs/user-guide/querying/arn-table.rst
@@ -0,0 +1,34 @@
+ARN Table
+===============
+
+.. code-block:: bash
+
+    # Get a list of all RAW ARN formats available through the SSM service.
+    policy_sentry query arn-table --service ssm
+
+    # Get the raw ARN format for the `cloud9` ARN with the short name `environment`
+    policy_sentry query arn-table --service cloud9 --name environment
+
+    # Get key/value pairs of all RAW ARN formats plus their short names
+    policy_sentry query arn-table --service cloud9 --list-arn-types
+
+---------
+Options
+---------
+
+.. code-block:: text
+
+    Usage: policy_sentry query arn-table [OPTIONS]
+
+      Query the ARN Table from the Policy Sentry database
+
+    Options:
+      --service TEXT     Filter according to AWS service.  [required]
+      --name TEXT        The short name of the resource ARN type. For example,
+                         `bucket` under service `s3`.
+      --list-arn-types   Show the short names of ARN Types. If empty, this will
+                         show RAW ARNs only.
+      --fmt [yaml|json]  Format output as YAML or JSON. Defaults to "yaml"
+      --log-level        Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+      --help             Show this message and exit.
+
diff --git a/docs/user-guide/querying/condition-table.rst b/docs/user-guide/querying/condition-table.rst
new file mode 100644
index 00000000..64a215a1
--- /dev/null
+++ b/docs/user-guide/querying/condition-table.rst
@@ -0,0 +1,30 @@
+Condition Table
+===============
+
+.. code-block:: bash
+
+    # Get a list of all condition keys available to the Cloud9 service
+    policy_sentry query condition-table --service cloud9
+
+    # Get details on the condition key titled `cloud9:Permissions`
+    policy_sentry query condition-table --service cloud9 --name cloud9:Permissions
+
+
+---------
+Options
+---------
+
+.. code-block:: text
+
+    Usage: policy_sentry query condition-table [OPTIONS]
+
+      Query the condition keys table from the Policy Sentry database
+
+    Options:
+      --name TEXT        Get details on a specific condition key. Leave this blank
+                         to get a list of all condition keys available to the
+                         service.
+      --service TEXT     Filter according to AWS service.  [required]
+      --fmt [yaml|json]  Format output as YAML or JSON. Defaults to "yaml"
+      --log-level        Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+      --help             Show this message and exit.
diff --git a/docs/user-guide/querying/index.rst b/docs/user-guide/querying/index.rst
new file mode 100644
index 00000000..0e758bad
--- /dev/null
+++ b/docs/user-guide/querying/index.rst
@@ -0,0 +1,15 @@
+Querying the IAM Database
+--------------------------------
+
+Policy Sentry relies on a SQLite database that contains all of the data from the `Actions, Resources, and Condition Keys documentation <https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html>`__ hosted by AWS.
+
+Policy Sentry supports querying that database through the CLI. This can help with writing policies and generally knowing what values to supply in your policies.
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Querying the IAM Database
+
+   action-table
+   arn-table
+   condition-table
diff --git a/docs/terraform/terraform-demo.rst b/docs/user-guide/terraform/index.rst
similarity index 97%
rename from docs/terraform/terraform-demo.rst
rename to docs/user-guide/terraform/index.rst
index be712e9e..f499ef0d 100644
--- a/docs/terraform/terraform-demo.rst
+++ b/docs/user-guide/terraform/index.rst
@@ -1,5 +1,6 @@
-Terraform Demo
-##############
+Terraform
+#####################
+
 
 The Terraform module is published `here <https://github.com/kmcquade/terraform-aws-policy-sentry>`__.
 
@@ -110,9 +111,6 @@ Example
     write_access_level = [
       "arn:aws:kms:us-east-1:123456789012:key/shaq"
     ]
-    permissions_management_access_level = [""]
-    tagging_access_level = [""]
-    wildcard_only = [""]
 
 
 4. Run `terraform apply` once to create the JSON policy file.
diff --git a/docs/user-guide/write-policy.rst b/docs/user-guide/write-policy.rst
deleted file mode 100644
index e9aaebd7..00000000
--- a/docs/user-guide/write-policy.rst
+++ /dev/null
@@ -1,272 +0,0 @@
-Writing IAM Policies
-#####################
-
-
-CRUD Mode
-----------
-
-* **TLDR**: Building IAM policies with resource constraints and access levels.
-
-This is the flagship feature of this tool. You can just specify the CRUD levels (Read, Write, List, Tagging, or Permissions management) for each action in a
-YAML File. The policy will be generated for you. You might need to fiddle with the results for your use in Terraform, but it significantly reduces the level of effort to build least privilege into your policies.
-
-
-Command options
-~~~~~~~~~~~~~~~
-
-
-* ``--input-file``\ : YAML file containing the CRUD levels + Resource ARNs. Required.
-* ``--minimize``\ : Whether or not to minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character length you want. This can be extended for readability. I suggest setting it to ``0``.
-* ``--log-level``\: Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-
-
-Example:
-
-.. code-block:: bash
-
-   policy_sentry write-policy --input-file examples/crud.yml
-
-Instructions
-~~~~~~~~~~~~~~~
-
-
-* To generate a policy according to resources and access levels, start by creating a template with this command so you can just fill out the ARNs:
-
-.. code-block:: bash
-
-    policy_sentry create-template --name myRole --output-file crud.yml --template-type crud
-
-* It will generate a file like this:
-
-.. code-block:: yaml
-
-    mode: crud
-    name: myRole
-    description: ''
-    role_arn: ''
-    # Insert ARNs below
-    read:
-    - ''
-    write:
-    - ''
-    list:
-    - ''
-    tagging:
-    - ''
-    permissions-management:
-    - ''
-    # Provide a list of IAM actions that cannot be restricted to ARNs
-    wildcard:
-    - ''
-
-* Then just fill it out:
-
-.. code-block:: yaml
-
-    mode: crud
-    name: myRole
-    description: 'Justification for privileges'
-    role_arn: 'arn:aws:iam::123456789102:role/myRole'
-    read:
-    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
-    write:
-    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
-    list:
-    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
-    tagging:
-    - 'arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret'
-    permissions-management:
-    - 'arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret'
-
-
-* Run the command:
-
-.. code-block:: bash
-
-   policy_sentry write-policy --input-file examples/crud.yml
-
-
-* It will generate an IAM Policy containing an IAM policy with the actions restricted to the ARNs specified above.
-* The resulting policy (without the ``--minimize command``\ ) will look like this:
-
-.. code-block:: json
-
-    {
-        "Version": "2012-10-17",
-        "Statement": [
-            {
-                "Sid": "SsmReadParameter",
-                "Effect": "Allow",
-                "Action": [
-                    "ssm:getparameter",
-                    "ssm:getparameterhistory",
-                    "ssm:getparameters",
-                    "ssm:getparametersbypath",
-                    "ssm:listtagsforresource"
-                ],
-                "Resource": [
-                    "arn:aws:ssm:us-east-1:123456789012:parameter/myparameter"
-                ]
-            },
-            {
-                "Sid": "SsmWriteParameter",
-                "Effect": "Allow",
-                "Action": [
-                    "ssm:deleteparameter",
-                    "ssm:deleteparameters",
-                    "ssm:putparameter",
-                    "ssm:labelparameterversion"
-                ],
-                "Resource": [
-                    "arn:aws:ssm:us-east-1:123456789012:parameter/myparameter"
-                ]
-            },
-            {
-                "Sid": "SecretsmanagerPermissionsmanagementSecret",
-                "Effect": "Allow",
-                "Action": [
-                    "secretsmanager:deleteresourcepolicy",
-                    "secretsmanager:putresourcepolicy"
-                ],
-                "Resource": [
-                    "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret"
-                ]
-            },
-            {
-                "Sid": "SecretsmanagerTaggingSecret",
-                "Effect": "Allow",
-                "Action": [
-                    "secretsmanager:tagresource",
-                    "secretsmanager:untagresource"
-                ],
-                "Resource": [
-                    "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret"
-                ]
-            }
-        ]
-    }
-
-
-Actions Mode
--------------
-
-* **TLDR**: Supply a list of actions in a YAML file and generate the policy accordingly.
-
-Command options
-~~~~~~~~~~~~~~~
-
-* ``--input-file``\ : YAML file containing the list of actions
-* ``--minimize``\ : Whether or not to minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character lengh you want - for example, 4
-* ``--log-level``\: Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-
-Example:
-
-.. code-block:: bash
-
-   policy_sentry write-policy --input-file examples/actions.yml
-
-Instructions
-~~~~~~~~~~~~
-
-* If you already know the IAM actions, you can just run this command to create a template to fill out:
-
-.. code-block:: bash
-
-    policy_sentry create-template --name myRole --output-file actions.yml --template-type actions
-
-* It will generate a file with contents like this:
-
-.. code-block:: yaml
-
-    mode: actions
-    name: myRole
-    description: '' # Insert value here
-    role_arn: '' # Insert value here
-    actions:
-    - ''  # Fill in your IAM actions here
-
-* Create a yaml file with the following contents:
-
-.. code-block:: yaml
-
-    mode: actions
-    name: 'RoleNameWithActions'
-    description: 'Justification for privileges' # for auditability
-    role_arn: 'arn:aws:iam::123456789102:role/myRole' # for auditability
-    actions:
-    - kms:CreateGrant
-    - kms:CreateCustomKeyStore
-    - ec2:AuthorizeSecurityGroupEgress
-    - ec2:AuthorizeSecurityGroupIngress
-
-
-* Then run this command:
-
-.. code-block:: bash
-
-   policy_sentry write-policy --input-file actions.yml
-
-
-* The output will look like this:
-
-.. code-block:: json
-
-
-    {
-        "Version": "2012-10-17",
-        "Statement": [
-            {
-                "Sid": "KmsPermissionsmanagementKey",
-                "Effect": "Allow",
-                "Action": [
-                    "kms:creategrant"
-                ],
-                "Resource": [
-                    "arn:aws:kms:${Region}:${Account}:key/${KeyId}"
-                ]
-            },
-            {
-                "Sid": "Ec2WriteSecuritygroup",
-                "Effect": "Allow",
-                "Action": [
-                    "ec2:authorizesecuritygroupegress",
-                    "ec2:authorizesecuritygroupingress"
-                ],
-                "Resource": [
-                    "arn:aws:ec2:${Region}:${Account}:security-group/${SecurityGroupId}"
-                ]
-            },
-            {
-                "Sid": "MultMultNone",
-                "Effect": "Allow",
-                "Action": [
-                    "kms:createcustomkeystore",
-                    "cloudhsm:describeclusters"
-                ],
-                "Resource": [
-                    "*"
-                ]
-            }
-        ]
-    }
-
-
-Folder Mode
-------------
-
-* **TLDR**: Write Multiple Policies from CRUD mode templates
-
-This command provides the same function as `write-policy`'s CRUD mode, but it can execute all the CRUD mode files in a folder. This is particularly useful in the Terraform use case, where the Terraform module can export a number of Policy Sentry template files into a folder, which can then be consumed using this command.
-
-See the Terraform demo for more details.
-
-.. code-block:: text
-
-   Usage: policy_sentry write-policy-dir [OPTIONS]
-
-   Options:
-     --input-dir TEXT    Relative path to Input directory that contains policy_sentry .yml files (CRUD mode only)  [required]
-     --output-dir TEXT   Relative path to directory to store AWS JSON policies [required]
-     --minimize INTEGER  Minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character length you want - for example, 4
-     --log-level         Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
-     --help              Show this message and exit.
diff --git a/docs/user-guide/writing-policies/actions-mode.rst b/docs/user-guide/writing-policies/actions-mode.rst
new file mode 100644
index 00000000..09f618b7
--- /dev/null
+++ b/docs/user-guide/writing-policies/actions-mode.rst
@@ -0,0 +1,104 @@
+
+
+Actions Mode
+-------------
+
+* **TLDR**: Supply a list of actions in a YAML file and generate the policy accordingly.
+
+Command options
+~~~~~~~~~~~~~~~
+
+* ``--input-file``\ : YAML file containing the list of actions
+* ``--minimize``\ : Whether or not to minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character lengh you want - for example, 4
+* ``--log-level``\: Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+
+Example:
+
+.. code-block:: bash
+
+   policy_sentry write-policy --input-file examples/actions.yml
+
+Instructions
+~~~~~~~~~~~~
+
+* If you already know the IAM actions, you can just run this command to create a template to fill out:
+
+.. code-block:: bash
+
+    policy_sentry create-template --name myRole --output-file actions.yml --template-type actions
+
+* It will generate a file with contents like this:
+
+.. code-block:: yaml
+
+    mode: actions
+    name: myRole
+    description: '' # Insert value here
+    role_arn: '' # Insert value here
+    actions:
+    - ''  # Fill in your IAM actions here
+
+* Create a yaml file with the following contents:
+
+.. code-block:: yaml
+
+    mode: actions
+    name: 'RoleNameWithActions'
+    description: 'Justification for privileges' # for auditability
+    role_arn: 'arn:aws:iam::123456789102:role/myRole' # for auditability
+    actions:
+    - kms:CreateGrant
+    - kms:CreateCustomKeyStore
+    - ec2:AuthorizeSecurityGroupEgress
+    - ec2:AuthorizeSecurityGroupIngress
+
+
+* Then run this command:
+
+.. code-block:: bash
+
+   policy_sentry write-policy --input-file actions.yml
+
+
+* The output will look like this:
+
+.. code-block:: json
+
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "KmsPermissionsmanagementKey",
+                "Effect": "Allow",
+                "Action": [
+                    "kms:creategrant"
+                ],
+                "Resource": [
+                    "arn:aws:kms:${Region}:${Account}:key/${KeyId}"
+                ]
+            },
+            {
+                "Sid": "Ec2WriteSecuritygroup",
+                "Effect": "Allow",
+                "Action": [
+                    "ec2:authorizesecuritygroupegress",
+                    "ec2:authorizesecuritygroupingress"
+                ],
+                "Resource": [
+                    "arn:aws:ec2:${Region}:${Account}:security-group/${SecurityGroupId}"
+                ]
+            },
+            {
+                "Sid": "MultMultNone",
+                "Effect": "Allow",
+                "Action": [
+                    "kms:createcustomkeystore",
+                    "cloudhsm:describeclusters"
+                ],
+                "Resource": [
+                    "*"
+                ]
+            }
+        ]
+    }
diff --git a/docs/user-guide/writing-policies/crud-mode.rst b/docs/user-guide/writing-policies/crud-mode.rst
new file mode 100644
index 00000000..f7044891
--- /dev/null
+++ b/docs/user-guide/writing-policies/crud-mode.rst
@@ -0,0 +1,402 @@
+CRUD Mode
+=============
+
+* **TLDR**: Building IAM policies with resource constraints and access levels.
+
+This is the flagship feature of this tool. You can just specify the CRUD levels (Read, Write, List, Tagging, or Permissions management) for each action in a
+YAML File. The policy will be generated for you. You might need to fiddle with the results for your use in Terraform, but it significantly reduces the level of effort to build least privilege into your policies.
+
+
+Command options
+----------------
+
+
+* ``--input-file``\ : YAML file containing the CRUD levels + Resource ARNs. Required.
+* ``--minimize``\ : Whether or not to minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character length you want. This can be extended for readability. I suggest setting it to ``0``.
+* ``--log-level``\: Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+
+
+Example:
+
+.. code-block:: bash
+
+   policy_sentry write-policy --input-file examples/crud.yml
+
+Instructions
+------------
+
+
+* To generate a policy according to resources and access levels, start by creating a template with this command so you can just fill out the ARNs:
+
+.. code-block:: bash
+
+    policy_sentry create-template --name myRole --output-file crud.yml --template-type crud
+
+* It will generate a file like this:
+
+.. code-block:: yaml
+
+    mode: crud
+    name: myRole
+    # Specify resource ARNs
+    read:
+    - ''
+    write:
+    - ''
+    list:
+    - ''
+    tagging:
+    - ''
+    permissions-management:
+    - ''
+    # Actions that do not support resource constraints
+    wildcard-only:
+      single-actions: # standalone actions
+      - ''
+      # Service-wide, per access level - like 's3' or 'ec2'
+      service-read:
+      - ''
+      service-write:
+      - ''
+      service-list:
+      - ''
+      service-tagging:
+      - ''
+      service-permissions-management:
+      - ''
+
+* Then just fill it out:
+
+.. code-block:: yaml
+
+    mode: crud
+    name: myRole
+    description: 'Justification for privileges'
+    role_arn: 'arn:aws:iam::123456789102:role/myRole'
+    read:
+    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
+    write:
+    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
+    list:
+    - 'arn:aws:ssm:us-east-1:123456789012:parameter/myparameter'
+    tagging:
+    - 'arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret'
+    permissions-management:
+    - 'arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret'
+
+
+* Run the command:
+
+.. code-block:: bash
+
+   policy_sentry write-policy --input-file crud.yml
+
+
+* It will generate an IAM Policy containing an IAM policy with the actions restricted to the ARNs specified above.
+* The resulting policy (without the ``--minimize command``\ ) will look like this:
+
+.. code-block:: json
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "SsmReadParameter",
+                "Effect": "Allow",
+                "Action": [
+                    "ssm:getparameter",
+                    "ssm:getparameterhistory",
+                    "ssm:getparameters",
+                    "ssm:getparametersbypath",
+                    "ssm:listtagsforresource"
+                ],
+                "Resource": [
+                    "arn:aws:ssm:us-east-1:123456789012:parameter/myparameter"
+                ]
+            },
+            {
+                "Sid": "SsmWriteParameter",
+                "Effect": "Allow",
+                "Action": [
+                    "ssm:deleteparameter",
+                    "ssm:deleteparameters",
+                    "ssm:putparameter",
+                    "ssm:labelparameterversion"
+                ],
+                "Resource": [
+                    "arn:aws:ssm:us-east-1:123456789012:parameter/myparameter"
+                ]
+            },
+            {
+                "Sid": "SecretsmanagerPermissionsmanagementSecret",
+                "Effect": "Allow",
+                "Action": [
+                    "secretsmanager:deleteresourcepolicy",
+                    "secretsmanager:putresourcepolicy"
+                ],
+                "Resource": [
+                    "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret"
+                ]
+            },
+            {
+                "Sid": "SecretsmanagerTaggingSecret",
+                "Effect": "Allow",
+                "Action": [
+                    "secretsmanager:tagresource",
+                    "secretsmanager:untagresource"
+                ],
+                "Resource": [
+                    "arn:aws:secretsmanager:us-east-1:123456789012:secret:mysecret"
+                ]
+            }
+        ]
+    }
+
+
+Wildcard-only section
+---------------------
+
+You'll notice that as of release 0.7.1, there is a new section for `wildcard-only`:
+
+.. code-block:: yaml
+
+    mode: crud
+    name: myRole
+    # Specify resource ARNs
+    read:
+    - ''
+    # Actions that do not support resource constraints
+    wildcard-only:
+      single-actions: # standalone actions
+      - ''
+      # Service-wide, per access level - like 's3' or 'ec2'
+      service-read:
+      - ''
+      service-write:
+      - ''
+      service-list:
+      - ''
+      service-tagging:
+      - ''
+      service-permissions-management:
+      - ''
+
+The `wildcard-only` section is meant to hold IAM actions that do not support resource constraints. Most IAM actions do support resource constraints - for instance, `s3:GetObject` can be restricted according to a specific object or path within an S3 bucket ARN , like `arn:aws:s3:::mybucket/path/*`. However, some IAM actions do **not** support resource constraints.
+
+Example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For example, run a query against the IAM database to determine "which S3 actions at the LIST access level do not support resource constraints":
+
+.. code-block:: bash
+
+    policy_sentry query action-table --service s3 --access-level list --wildcard-only
+
+The output will be:
+
+.. code-block:: text
+    s3 LIST actions that must use wildcards in the resources block:
+    [
+        "s3:ListAllMyBuckets"
+    ]
+
+Similarly, S3 has a few actions that at the "Read" access level that do not support resource constraints. Run this query against the IAM database to discover those actions:
+
+
+.. code-block:: bash
+
+    policy_sentry query action-table --service s3 --access-level read --wildcard-only
+
+The output will be:
+
+.. code-block:: text
+
+    s3 READ actions that must use wildcards in the resources block:
+    [
+        "s3:GetAccessPoint",
+        "s3:GetAccountPublicAccessBlock",
+        "s3:ListAccessPoints"
+    ]
+
+
+Basic support for Wildcard-only Actions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As you can see from the previous example, there are definitely valid use cases for providing access to IAM Actions that do not support resource constraints (i.e., where the Action must be set to `Resource=*`).
+
+**Single IAM Actions**
+
+Previous to version 0.7.1, the user still had to provide specific IAM actions in that section. That is still supported, using the `single-actions` array under the `wildcard-only` map, as shown in the example `crud.yml` below.
+
+.. code-block:: yaml
+
+    mode: crud
+    name: myRole
+    wildcard-only:
+      single-actions:
+      - 's3:ListAllMyBuckets'
+
+The resulting policy would look like this:
+
+.. code-block:: json
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "MultMultNone",
+                "Effect": "Allow",
+                "Action": [
+                    "s3:ListAllMyBuckets"
+                ],
+                "Resource": [
+                    "*"
+                ]
+            }
+        ]
+    }
+
+And what's really cool about that - if the user tries to bypass it by supplying an action that supports resource constraints (like `secretsmanager:DeleteSecret`), Policy Sentry will ignore the user's request. Consider a file titled `crud.yml` with the contents below:
+
+.. code-block:: yaml
+
+    mode: crud
+    name: myRole
+    wildcard-only:
+      single-actions:
+      - 's3:ListAllMyBuckets'
+      - 'secretsmanager:DeleteSecret'  # Policy Sentry will ignore this!
+
+Now run the command:
+
+.. code-block:: bash
+
+    policy_sentry write-policy crud.yml
+
+Notice how the output does not include `secretsmanager:DeleteSecret`:
+
+.. code-block:: json
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "MultMultNone",
+                "Effect": "Allow",
+                "Action": [
+                    "s3:ListAllMyBuckets"
+                ],
+                "Resource": [
+                    "*"
+                ]
+            }
+        ]
+    }
+
+
+
+CRUD-based support for Wildcard-only Actions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+That previous example is very cool - but it's not terribly fast for users to have to run the CLI queries. We decided that it should be even easier than this. If you're using the `Terraform module <https://github.com/kmcquade/terraform-aws-policy-sentry>`__, then *you should never, ever have to query the IAM database*.
+
+Now bear witness to the latest feature addition to Policy Sentry: wildcard-only, CRUD-based, service-specific actions.
+
+.. code-block:: yaml
+
+    mode: crud
+    wildcard-only:
+        service-read:
+        - ecr           # This will add ecr:GetAuthorizationToken to the policy
+        - s3            # This adds s3:GetAccessPoint, s3:GetAccountPublicAccessBlock, s3:ListAccessPoints
+
+
+As shown above, the input only required the user to supply `s3` and `ecr` under the `service-read` array in the `wildcard-only` map.
+
+Now run the command:
+
+.. code-block:: bash
+
+    policy_sentry write-policy crud.yml
+
+Notice how the output includes *wildcard-only* actions at the *read* access level for the `ecr` and `s3` services:
+
+.. code-block:: json
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "MultMultNone",
+                "Effect": "Allow",
+                "Action": [
+                    "ecr:GetAuthorizationToken",
+                    "s3:GetAccessPoint",
+                    "s3:GetAccountPublicAccessBlock",
+                    "s3:ListAccessPoints"
+                ],
+                "Resource": [
+                    "*"
+                ]
+            }
+        ]
+    }
+
+
+Combining approaches
+~~~~~~~~~~~~~~~~~~~~~
+
+Here's a slightly more complex policy. See the input file `crud.yml` below:
+
+.. code-block:: yaml
+
+    mode: crud
+    read:
+    - arn:aws:s3:::example-org-s3-access-logs
+    wildcard-only:
+        service-read:
+        - ecr           # This will add ecr:GetAuthorizationToken to the policy
+        - s3            # This adds s3:GetAccessPoint, s3:GetAccountPublicAccessBlock, s3:ListAccessPoints
+
+After running the command:
+
+.. code-block:: bash
+
+    policy_sentry write-policy crud.yml
+
+.. code-block:: json
+
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "MultMultNone",
+                "Effect": "Allow",
+                "Action": [
+                    "ecr:GetAuthorizationToken",
+                    "s3:GetAccessPoint",
+                    "s3:GetAccountPublicAccessBlock",
+                    "s3:ListAccessPoints"
+                ],
+                "Resource": [
+                    "*"
+                ]
+            },
+            {
+                "Sid": "S3PermissionsmanagementBucket",
+                "Effect": "Allow",
+                "Action": [
+                    "s3:DeleteBucketPolicy",
+                    "s3:PutBucketAcl",
+                    "s3:PutBucketPolicy",
+                    "s3:PutBucketPublicAccessBlock"
+                ],
+                "Resource": [
+                    "arn:aws:s3:::example-org-s3-access-logs"
+                ]
+            }
+        ]
+    }
+
+And yes, it's all available in the Terraform module :)
diff --git a/docs/user-guide/writing-policies/folder-mode.rst b/docs/user-guide/writing-policies/folder-mode.rst
new file mode 100644
index 00000000..ed4273c4
--- /dev/null
+++ b/docs/user-guide/writing-policies/folder-mode.rst
@@ -0,0 +1,20 @@
+
+Folder Mode
+------------
+
+* **TLDR**: Write Multiple Policies from CRUD mode templates
+
+This command provides the same function as `write-policy`'s CRUD mode, but it can execute all the CRUD mode files in a folder. This is particularly useful in the Terraform use case, where the Terraform module can export a number of Policy Sentry template files into a folder, which can then be consumed using this command.
+
+See the Terraform demo for more details.
+
+.. code-block:: text
+
+   Usage: policy_sentry write-policy-dir [OPTIONS]
+
+   Options:
+     --input-dir TEXT    Relative path to Input directory that contains policy_sentry .yml files (CRUD mode only)  [required]
+     --output-dir TEXT   Relative path to directory to store AWS JSON policies [required]
+     --minimize INTEGER  Minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character length you want - for example, 4
+     --log-level         Set the logging level. Choices are CRITICAL, ERROR, WARNING, INFO, or DEBUG. Defaults to INFO
+     --help              Show this message and exit.
diff --git a/docs/user-guide/writing-policies/index.rst b/docs/user-guide/writing-policies/index.rst
new file mode 100644
index 00000000..f7c8c827
--- /dev/null
+++ b/docs/user-guide/writing-policies/index.rst
@@ -0,0 +1,12 @@
+Writing IAM Policies
+#####################
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Writing IAM Policies
+
+   crud-mode
+   actions-mode
+   folder-mode
+
diff --git a/examples/input-dir/policy-puma.yml b/examples/input-dir/policy-puma.yml
index 73043959..cd065fd5 100644
--- a/examples/input-dir/policy-puma.yml
+++ b/examples/input-dir/policy-puma.yml
@@ -1,7 +1,5 @@
 mode: crud
 name: 'RoleNameWithCRUD'
-description: 'Why I need these privs'
-role_arn: 'arn:aws:iam::123456789012:role/RiskyEC2'
 read:
 - arn:aws:s3:::example-org-sbx-vmimport
 - arn:aws:s3:::example-kinnaird
diff --git a/examples/input-dir/policy-zebu.yml b/examples/input-dir/policy-zebu.yml
index 73043959..cd065fd5 100644
--- a/examples/input-dir/policy-zebu.yml
+++ b/examples/input-dir/policy-zebu.yml
@@ -1,7 +1,5 @@
 mode: crud
 name: 'RoleNameWithCRUD'
-description: 'Why I need these privs'
-role_arn: 'arn:aws:iam::123456789012:role/RiskyEC2'
 read:
 - arn:aws:s3:::example-org-sbx-vmimport
 - arn:aws:s3:::example-kinnaird
diff --git a/examples/yml/actions.yml b/examples/yml/actions.yml
index 195fb34b..29288bea 100644
--- a/examples/yml/actions.yml
+++ b/examples/yml/actions.yml
@@ -1,8 +1,6 @@
 # Generate my policy when I know the Actions
 mode: actions
 name: 'RoleNameWithActions'
-description: 'Why I need these privs'
-role_arn: 'arn:aws:iam::123456789102:role/RiskyEC2'
 actions:
 - kms:CreateGrant
 - kms:CreateCustomKeyStore
diff --git a/examples/yml/crud-with-wildcard-service-level.yml b/examples/yml/crud-with-wildcard-service-level.yml
new file mode 100644
index 00000000..c3f5ca79
--- /dev/null
+++ b/examples/yml/crud-with-wildcard-service-level.yml
@@ -0,0 +1,12 @@
+mode: crud
+name: 'RoleNameWithCRUD'
+permissions-management:
+- arn:aws:s3:::example-org-s3-access-logs
+wildcard-only:
+    single-actions:
+    - ram:enablesharingwithawsorganization
+    - ram:getresourcepolicies
+    - secretsmanager:createsecret
+    service-read:
+    - ecr # GetAuthorizationToken
+    - s3 # GetAccessPoint, GetAccountPublicAccessBlock, ListAccessPoints
diff --git a/examples/yml/crud-with-wildcard.yml b/examples/yml/crud-with-wildcard.yml
index e9753b5a..c8d5ee24 100644
--- a/examples/yml/crud-with-wildcard.yml
+++ b/examples/yml/crud-with-wildcard.yml
@@ -1,20 +1,10 @@
 mode: crud
 name: 'RoleNameWithCRUD'
-description: 'Why I need these privs'
-role_arn: 'arn:aws:iam::123456789012:role/RiskyEC2'
-#  read:
-#    - arn:aws:s3:::example-org-sbx-vmimport/stuff
-#  write:
-#    - arn:aws:kms:us-east-1:123456789012:key/123456
-#  list:
-#    - arn:aws:s3:::example-org-flow-logs
-#    - arn:aws:s3:::example-org-sbx-vmimport/stuff
-#  tagging:
-#    - arn:aws:ssm:us-east-1:123456789012:parameter/test
 permissions-management:
 - arn:aws:s3:::example-org-s3-access-logs
-wildcard:
-- ram:enablesharingwithawsorganization
-- ram:getresourcepolicies
-- secretsmanager:createsecret
-- secretsmanager:putsecretvalue # attempting bypass
+wildcard-only:
+    single-actions:
+    - ram:enablesharingwithawsorganization
+    - ram:getresourcepolicies
+    - secretsmanager:createsecret
+    - secretsmanager:putsecretvalue # attempting bypass
diff --git a/examples/yml/crud.yml b/examples/yml/crud.yml
index 73043959..cd065fd5 100644
--- a/examples/yml/crud.yml
+++ b/examples/yml/crud.yml
@@ -1,7 +1,5 @@
 mode: crud
 name: 'RoleNameWithCRUD'
-description: 'Why I need these privs'
-role_arn: 'arn:aws:iam::123456789012:role/RiskyEC2'
 read:
 - arn:aws:s3:::example-org-sbx-vmimport
 - arn:aws:s3:::example-kinnaird
diff --git a/examples/yml/tagging.yml b/examples/yml/tagging.yml
index 52235bf8..598c5f72 100644
--- a/examples/yml/tagging.yml
+++ b/examples/yml/tagging.yml
@@ -1,7 +1,5 @@
 mode: actions
 name: myRole
-description: '' # For human auditability
-role_arn: '' # For human auditability
 actions:
 - a4b:tagresource
 - a4b:untagresource
diff --git a/policy_sentry/writing/sid_group.py b/policy_sentry/writing/sid_group.py
index e13ceadf..deae1dce 100644
--- a/policy_sentry/writing/sid_group.py
+++ b/policy_sentry/writing/sid_group.py
@@ -9,6 +9,7 @@ from policy_sentry.querying.actions import (
     get_actions_with_arn_type_and_access_level,
     get_dependent_actions,
     get_actions_that_support_wildcard_arns_only,
+    get_actions_at_access_level_that_support_wildcard_arns_only,
 )
 from policy_sentry.querying.arns import get_resource_type_name_with_raw_arn
 from policy_sentry.util.arns import does_arn_match, get_service_from_arn
@@ -281,66 +282,137 @@ class SidGroup:
         :param cfg: The loaded YAML as a dict. Must follow Policy Sentry dictated format.
         :param minimize: Minimize the resulting statement with *safe* usage of wildcards to reduce policy length. Set this to the character length you want - for example, 0, or 4. Defaults to none.
         """
-        try:
-            if "mode" in cfg.keys():
-                if cfg["mode"] == "crud":
-                    check_crud_schema(cfg)
-                    if "wildcard" in cfg.keys():
-                        provided_wildcard_actions = cfg["wildcard"]
-                        if isinstance(provided_wildcard_actions, list):
-                            verified_wildcard_actions = remove_actions_that_are_not_wildcard_arn_only(
-                                db_session, provided_wildcard_actions
-                            )
-                            if len(verified_wildcard_actions) > 0:
-                                self.add_by_list_of_actions(
-                                    db_session, verified_wildcard_actions
+        # try:
+        if "mode" in cfg.keys():
+            if cfg["mode"] == "crud":
+                check_crud_schema(cfg)
+                if "wildcard-only" in cfg.keys():
+                    if "single-actions" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["single-actions"]:
+                            if cfg["wildcard-only"]["single-actions"][0] != "":
+                                provided_wildcard_actions = cfg["wildcard-only"][
+                                    "single-actions"
+                                ]
+                                self.add_wildcard_only_actions(
+                                    db_session, provided_wildcard_actions
                                 )
-                    if "read" in cfg.keys():
-                        if cfg["read"] is not None and cfg["read"][0] != "":
-                            self.add_by_arn_and_access_level(
-                                db_session, cfg["read"], "Read"
-                            )
-                    if "write" in cfg.keys():
-                        if cfg["write"] is not None and cfg["write"][0] != "":
-                            self.add_by_arn_and_access_level(
-                                db_session, cfg["write"], "Write"
-                            )
-                    if "list" in cfg.keys():
-                        if cfg["list"] is not None and cfg["list"][0] != "":
-                            self.add_by_arn_and_access_level(
-                                db_session, cfg["list"], "List"
-                            )
-                    if "permissions-management" in cfg.keys():
-                        if (
-                            cfg["permissions-management"] is not None
-                            and cfg["permissions-management"][0] != ""
-                        ):
-                            self.add_by_arn_and_access_level(
-                                db_session,
-                                cfg["permissions-management"],
-                                "Permissions management",
-                            )
-                    if "tagging" in cfg.keys():
-                        if cfg["tagging"] is not None and cfg["tagging"][0] != "":
-                            self.add_by_arn_and_access_level(
-                                db_session, cfg["tagging"], "Tagging"
-                            )
-
-                if cfg["mode"] == "actions":
-                    check_actions_schema(cfg)
-                    # for policy in cfg[template]:
-                    if "actions" in cfg.keys():
-                        if cfg["actions"] is not None:
-                            self.add_by_list_of_actions(db_session, cfg["actions"])
-
-        except IndexError:
-            raise Exception(
-                "IndexError: list index out of range. This is likely due to an ARN in your list "
-                "equaling ''. Please evaluate your YML file and try again."
-            )
+                    if "service-read" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["service-read"]:
+                            if cfg["wildcard-only"]["service-read"][0] != "":
+                                service_read = cfg["wildcard-only"]["service-read"]
+                                self.add_wildcard_only_actions_matching_services_and_access_level(
+                                    db_session, service_read, "Read"
+                                )
+                    if "service-write" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["service-write"]:
+                            if cfg["wildcard-only"]["service-write"][0] != "":
+                                service_write = cfg["wildcard-only"]["service-write"]
+                                self.add_wildcard_only_actions_matching_services_and_access_level(
+                                    db_session, service_write, "Write"
+                                )
+                    if "service-list" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["service-list"]:
+                            if cfg["wildcard-only"]["service-list"][0] != "":
+                                service_list = cfg["wildcard-only"]["service-list"]
+                                self.add_wildcard_only_actions_matching_services_and_access_level(
+                                    db_session, service_list, "List"
+                                )
+                    if "service-tagging" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["service-tagging"]:
+                            if cfg["wildcard-only"]["service-tagging"][0] != "":
+                                service_tagging = cfg["wildcard-only"][
+                                    "service-tagging"
+                                ]
+                                self.add_wildcard_only_actions_matching_services_and_access_level(
+                                    db_session, service_tagging, "Tagging"
+                                )
+                    if "service-permissions-management" in cfg["wildcard-only"]:
+                        if cfg["wildcard-only"]["service-permissions-management"]:
+                            if (
+                                cfg["wildcard-only"]["service-permissions-management"][
+                                    0
+                                ]
+                                != ""
+                            ):
+                                service_permissions_management = cfg["wildcard-only"][
+                                    "service-permissions-management"
+                                ]
+                                self.add_wildcard_only_actions_matching_services_and_access_level(
+                                    db_session,
+                                    service_permissions_management,
+                                    "Permissions management",
+                                )
+                if "read" in cfg.keys():
+                    if cfg["read"] is not None and cfg["read"][0] != "":
+                        self.add_by_arn_and_access_level(
+                            db_session, cfg["read"], "Read"
+                        )
+                if "write" in cfg.keys():
+                    if cfg["write"] is not None and cfg["write"][0] != "":
+                        self.add_by_arn_and_access_level(
+                            db_session, cfg["write"], "Write"
+                        )
+                if "list" in cfg.keys():
+                    if cfg["list"] is not None and cfg["list"][0] != "":
+                        self.add_by_arn_and_access_level(
+                            db_session, cfg["list"], "List"
+                        )
+                if "permissions-management" in cfg.keys():
+                    if (
+                        cfg["permissions-management"] is not None
+                        and cfg["permissions-management"][0] != ""
+                    ):
+                        self.add_by_arn_and_access_level(
+                            db_session,
+                            cfg["permissions-management"],
+                            "Permissions management",
+                        )
+                if "tagging" in cfg.keys():
+                    if cfg["tagging"] is not None and cfg["tagging"][0] != "":
+                        self.add_by_arn_and_access_level(
+                            db_session, cfg["tagging"], "Tagging"
+                        )
+
+            if cfg["mode"] == "actions":
+                check_actions_schema(cfg)
+                if "actions" in cfg.keys():
+                    if cfg["actions"] is not None and cfg["actions"][0] != "":
+                        self.add_by_list_of_actions(db_session, cfg["actions"])
+
         rendered_policy = self.get_rendered_policy(db_session, minimize)
         return rendered_policy
 
+    def add_wildcard_only_actions(self, db_session, provided_wildcard_actions):
+        """
+        Given a list of IAM actions, add individual IAM Actions that do not support resource constraints to the MultMultNone SID
+
+        :param db_session: SQLAlchemy database session
+        :param provided_wildcard_actions: list actions provided by the user.
+        """
+        if isinstance(provided_wildcard_actions, list):
+            verified_wildcard_actions = remove_actions_that_are_not_wildcard_arn_only(
+                db_session, provided_wildcard_actions
+            )
+            if len(verified_wildcard_actions) > 0:
+                self.add_by_list_of_actions(db_session, verified_wildcard_actions)
+                # logger.debug("Added the following wildcard-only actions to the policy: %s", verified_wildcard_actions)
+
+    def add_wildcard_only_actions_matching_services_and_access_level(
+        self, db_session, services, access_level
+    ):
+        """
+        :param db_session: SQLAlchemy database session
+        :param services: A list of AWS services
+    :param access_level: An access level as it is written in the database, such as 'Read', 'Write', 'List', 'Permisssions management', or 'Tagging'
+        """
+        wildcard_only_actions_to_add = []
+        for service in services:
+            actions = get_actions_at_access_level_that_support_wildcard_arns_only(
+                db_session, service, access_level
+            )
+            wildcard_only_actions_to_add.extend(actions)
+        self.add_wildcard_only_actions(db_session, wildcard_only_actions_to_add)
+
     def remove_actions_not_matching_these(self, actions_to_keep):
         """
         :param actions_to_keep: A list of actions to leave in the policy. All actions not in this list are removed.
diff --git a/policy_sentry/writing/template.py b/policy_sentry/writing/template.py
index 5e95cb53..c9aa987a 100644
--- a/policy_sentry/writing/template.py
+++ b/policy_sentry/writing/template.py
@@ -3,22 +3,15 @@ These can be used for generating policies
 """
 from jinja2 import Template
 
-ACTIONS_TEMPLATE = """# Generate my policy when I know the Actions
-mode: actions
+ACTIONS_TEMPLATE = """mode: actions
 name: {{ name }}
-description: '' # For human auditability
-role_arn: '' # For human auditability
 actions:
 - ''
 """
 
-CRUD_TEMPLATE = """# Generate my policy when I know the access levels and ARNs
-mode: crud
+CRUD_TEMPLATE = """mode: crud
 name: {{ name }}
-description: '' # For human auditability
-role_arn: '' # For human auditability
-# Insert ARNs under each access level below
-# If you do not need to use certain access levels, delete them.
+# Specify resource ARNs
 read:
 - ''
 write:
@@ -29,30 +22,44 @@ tagging:
 - ''
 permissions-management:
 - ''
-# If the policy needs to use IAM actions that cannot be restricted to ARNs,
-# like ssm:DescribeParameters, specify those actions here.
-wildcard:
-- ''
+# Actions that do not support resource constraints
+wildcard-only:
+  single-actions: # standalone actions
+  - ''
+  # Service-wide - like 's3' or 'ec2'
+  service-read:
+  - ''
+  service-write:
+  - ''
+  service-list:
+  - ''
+  service-tagging:
+  - ''
+  service-permissions-management:
+  - ''
 """
 
 CRUD_TEMPLATE_DICT = {
     "mode": "crud",
     "name": "",
-    "description": "",
-    "role_arn": "",
     "read": [],
     "write": [],
     "list": [],
     "tagging": [],
     "permissions-management": [],
-    "wildcard": [],
+    "wildcard-only": {
+        "single-actions": [],
+        "service-read": [],
+        "service-write": [],
+        "service-list": [],
+        "service-tagging": [],
+        "service-permissions-management": [],
+    },
 }
 
 ACTIONS_TEMPLATE_DICT = {
     "mode": "actions",
     "name": "",
-    "description": "",
-    "role_arn": "",
     "actions": [],
 }
 
diff --git a/policy_sentry/writing/validate.py b/policy_sentry/writing/validate.py
index 98705358..2830adaa 100644
--- a/policy_sentry/writing/validate.py
+++ b/policy_sentry/writing/validate.py
@@ -32,25 +32,24 @@ CRUD_SCHEMA = Schema(
     {
         "mode": "crud",
         Optional("name"): And(Use(str)),
-        Optional("description"): And(Use(str)),
-        Optional("role_arn"): And(Use(str)),
         Optional("read"): [str],
         Optional("write"): [str],
         Optional("list"): [str],
         Optional("permissions-management"): [str],
         Optional("tagging"): [str],
-        Optional("wildcard"): [str],
+        Optional("wildcard-only"): {
+            Optional("single-actions"): [str],
+            Optional("service-read"): [str],
+            Optional("service-write"): [str],
+            Optional("service-list"): [str],
+            Optional("service-tagging"): [str],
+            Optional("service-permissions-management"): [str],
+        },
     }
 )
 
 ACTIONS_SCHEMA = Schema(
-    {
-        "mode": "actions",
-        Optional("name"): And(Use(str)),
-        Optional("description"): And(Use(str)),
-        Optional("role_arn"): And(Use(str)),
-        "actions": And([str]),
-    }
+    {"mode": "actions", Optional("name"): And(Use(str)), "actions": And([str]),}
 )
 
 
diff --git a/tasks.py b/tasks.py
index 6fbe2dea..4d4ccc3a 100755
--- a/tasks.py
+++ b/tasks.py
@@ -259,7 +259,7 @@ integration.add_task(query_with_yaml, 'query-yaml')
 unit.add_task(run_nosetests, 'nose')
 unit.add_task(run_pytest, 'pytest')
 
-docs.add_task(remove_html_files, 'remove-html-files')
+docs.add_task(remove_html_files, 'clean-html')
 docs.add_task(make_html, 'make-html')
 docs.add_task(open_html_docs, 'open-html')
 


Test patch:
diff --git a/test/analysis/test_analyze.py b/test/analysis/test_analyze.py
index b5fb5474..f58ccf18 100644
--- a/test/analysis/test_analyze.py
+++ b/test/analysis/test_analyze.py
@@ -12,7 +12,24 @@ db_session = connect_db(DATABASE_FILE_PATH)
 
 
 class AnalysisExpandWildcardActionsTestCase(unittest.TestCase):
-    # TODO: Write a wEiRd nOtCaMeLCaSe version of this
+    def test_a_determine_actions_to_expand_not_upper_camelcase(self):
+        """test_determine_actions_to_expand_not_upper_camelcase: The nOtCaMeLcAsE version of the same test"""
+        action_list = ["ecr:pUt*"]
+        self.maxDiff = None
+        desired_result = [
+            "ecr:PutImage",
+            "ecr:PutImageScanningConfiguration",
+            "ecr:PutImageTagMutability",
+            "ecr:PutLifecyclePolicy",
+        ]
+        result = determine_actions_to_expand(db_session, action_list)
+        print(result)
+        self.maxDiff = None
+        self.assertListEqual(
+            sorted(determine_actions_to_expand(db_session, action_list)),
+            sorted(desired_result),
+        )
+
     def test_determine_actions_to_expand(self):
         """
         test_determine_actions_to_expand: provide expanded list of actions, like ecr:*
diff --git a/test/command/test_write_policy.py b/test/command/test_write_policy.py
index 00044d2f..4b2453c6 100644
--- a/test/command/test_write_policy.py
+++ b/test/command/test_write_policy.py
@@ -15,21 +15,21 @@ class WritePolicyPreventWildcardEscalation(unittest.TestCase):
         cfg = {
             "mode": "crud",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "permissions-management": ["arn:aws:s3:::example-org-s3-access-logs"],
-            "wildcard": [
-                # The first three are legitimately wildcard only.
-                # Verify with `policy_sentry query action-table --service secretsmanager --wildcard-only`
-                "ram:EnableSharingWithAwsOrganization",
-                "ram:GetResourcePolicies",
-                "secretsmanager:CreateSecret",
-                # This last one can be "secret" ARN type OR wildcard. We want to prevent people from
-                # bypassing this mechanism, while allowing them to explicitly
-                # request specific privs that require wildcard mode. This next value -
-                # secretsmanager:putsecretvalue - is an example of someone trying to beat the tool.
-                "secretsmanager:PutSecretValue",
-            ],
+            "wildcard-only": {
+                "single-actions": [
+                    # The first three are legitimately wildcard only.
+                    # Verify with `policy_sentry query action-table --service secretsmanager --wildcard-only`
+                    "ram:EnableSharingWithAwsOrganization",
+                    "ram:GetResourcePolicies",
+                    "secretsmanager:CreateSecret",
+                    # This last one can be "secret" ARN type OR wildcard. We want to prevent people from
+                    # bypassing this mechanism, while allowing them to explicitly
+                    # request specific privs that require wildcard mode. This next value -
+                    # secretsmanager:putsecretvalue - is an example of someone trying to beat the tool.
+                    "secretsmanager:PutSecretValue",
+                ],
+            }
         }
         db_session = connect_db("bundled")
         output = write_policy_with_template(db_session, cfg)
@@ -78,4 +78,48 @@ class WritePolicyPreventWildcardEscalation(unittest.TestCase):
 #         policy = write_policy_with_template(db_session, cfg)
 #         print(policy)
 
+class WildcardOnlyServiceLevelTestCase(unittest.TestCase):
+    def test_add_wildcard_only_actions_matching_services_and_access_level(self):
+        """test_add_wildcard_only_actions_matching_services_and_access_level: We'd never write a policy like this
+        IRL but doing this as a quality check against how it handles the database """
+        policy_file_path = abspath(
+            join(
+                dirname(__file__), pardir + "/" + pardir + "/examples/yml/crud-with-wildcard-service-level.yml",
+            )
+        )
+        cfg = read_yaml_file(policy_file_path)
 
+        output = write_policy_with_template(db_session, cfg)
+        print(json.dumps(output, indent=4))
+        desired_output = {
+            "Version": "2012-10-17",
+            "Statement": [
+                {
+                    "Sid": "MultMultNone",
+                    "Effect": "Allow",
+                    "Action": [
+                        "ecr:GetAuthorizationToken",
+                        "s3:GetAccessPoint",
+                        "s3:GetAccountPublicAccessBlock",
+                        "s3:ListAccessPoints"
+                    ],
+                    "Resource": [
+                        "*"
+                    ]
+                },
+                {
+                    "Sid": "S3PermissionsmanagementBucket",
+                    "Effect": "Allow",
+                    "Action": [
+                        "s3:DeleteBucketPolicy",
+                        "s3:PutBucketAcl",
+                        "s3:PutBucketPolicy",
+                        "s3:PutBucketPublicAccessBlock"
+                    ],
+                    "Resource": [
+                        "arn:aws:s3:::example-org-s3-access-logs"
+                    ]
+                }
+            ]
+        }
+        self.assertDictEqual(output, desired_output)
diff --git a/test/querying/test_query_actions.py b/test/querying/test_query_actions.py
index d8f089cd..6c536903 100644
--- a/test/querying/test_query_actions.py
+++ b/test/querying/test_query_actions.py
@@ -298,5 +298,24 @@ class QueryActionsTestCase(unittest.TestCase):
         self.maxDiff = None
         self.assertListEqual(desired_output, output)
 
-# TODO: Test it when we give actions with wEiRd cases, and it should feed us back the cleaned up version.
-# # TODO: Because above, it actually would fail when they were lowercase, and it would pass when they were UpperCamelCase
+    def test_weird_lowercase_uppercase(self):
+        """test_weird_lowercase_uppercase: Same as test_remove_actions_that_are_not_wildcard_arn_only, but with wEiRd cases"""
+        provided_actions_list = [
+            # 3 wildcard only actions
+            "secretsmanager:cReAtEsEcReT",
+            "secretsmanager:gEtRaNdOmPasSwOrD",
+            "secretsmanager:LIstsEcretS",
+            # This one is wildcard OR "secret"
+            "secretsmanager:pUtSeCrEtVaLuE",
+        ]
+        desired_output = [
+            # 3 wildcard only actions
+            "secretsmanager:CreateSecret",
+            "secretsmanager:GetRandomPassword",
+            "secretsmanager:ListSecrets",
+        ]
+        output = remove_actions_that_are_not_wildcard_arn_only(
+            db_session, provided_actions_list
+        )
+        self.maxDiff = None
+        self.assertListEqual(desired_output, output)
diff --git a/test/writing/test_sid_group.py b/test/writing/test_sid_group.py
index 4dfeb873..83563ee1 100644
--- a/test/writing/test_sid_group.py
+++ b/test/writing/test_sid_group.py
@@ -13,8 +13,6 @@ class SidGroupActionsTestCase(unittest.TestCase):
         cfg = {
             "mode": "actions",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "actions": [
                 "kms:CreateGrant",
                 "kms:CreateCustomKeyStore",
@@ -282,8 +280,6 @@ class SidGroupCrudTestCase(unittest.TestCase):
         cfg = {
             "mode": "crud",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "permissions-management": ["arn:aws:s3:::example-org-s3-access-logs"],
             "list": [
                 "arn:aws:secretsmanager:us-east-1:123456789012:secret:anothersecret"
@@ -427,21 +423,21 @@ class SidGroupCrudTestCase(unittest.TestCase):
         cfg = {
             "mode": "crud",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "permissions-management": ["arn:aws:s3:::example-org-s3-access-logs"],
-            "wildcard": [
-                # The first three are legitimately wildcard only.
-                # Verify with `policy_sentry query action-table --service secretsmanager --wildcard-only`
-                "ram:enablesharingwithawsorganization",
-                "ram:getresourcepolicies",
-                "secretsmanager:createsecret",
-                # This last one can be "secret" ARN type OR wildcard. We want to prevent people from
-                # bypassing this mechanism, while allowing them to explicitly
-                # request specific privs that require wildcard mode. This next value -
-                # secretsmanager:putsecretvalue - is an example of someone trying to beat the tool.
-                "secretsmanager:putsecretvalue",
-            ],
+            "wildcard-only": {
+                "single-actions": [
+                    # The first three are legitimately wildcard only.
+                    # Verify with `policy_sentry query action-table --service secretsmanager --wildcard-only`
+                    "ram:enablesharingwithawsorganization",
+                    "ram:getresourcepolicies",
+                    "secretsmanager:createsecret",
+                    # This last one can be "secret" ARN type OR wildcard. We want to prevent people from
+                    # bypassing this mechanism, while allowing them to explicitly
+                    # request specific privs that require wildcard mode. This next value -
+                    # secretsmanager:putsecretvalue - is an example of someone trying to beat the tool.
+                    "secretsmanager:putsecretvalue",
+                ],
+            }
         }
         sid_group = SidGroup()
         output = sid_group.process_template(db_session, cfg)
diff --git a/test/writing/test_template.py b/test/writing/test_template.py
index 87a167db..379d8fef 100644
--- a/test/writing/test_template.py
+++ b/test/writing/test_template.py
@@ -4,24 +4,17 @@ from policy_sentry.writing.template import create_actions_template, create_crud_
 
 class TemplateTestCase(unittest.TestCase):
     def test_actions_template(self):
-        desired_msg = """# Generate my policy when I know the Actions
-mode: actions
+        desired_msg = """mode: actions
 name: myrole
-description: '' # For human auditability
-role_arn: '' # For human auditability
 actions:
 - ''"""
         actions_template = create_actions_template("myrole")
         self.assertEqual(desired_msg, actions_template)
 
     def test_crud_template(self):
-        desired_msg = """# Generate my policy when I know the access levels and ARNs
-mode: crud
+        desired_msg = """mode: crud
 name: myrole
-description: '' # For human auditability
-role_arn: '' # For human auditability
-# Insert ARNs under each access level below
-# If you do not need to use certain access levels, delete them.
+# Specify resource ARNs
 read:
 - ''
 write:
@@ -32,9 +25,20 @@ tagging:
 - ''
 permissions-management:
 - ''
-# If the policy needs to use IAM actions that cannot be restricted to ARNs,
-# like ssm:DescribeParameters, specify those actions here.
-wildcard:
-- ''"""
+# Actions that do not support resource constraints
+wildcard-only:
+  single-actions: # standalone actions
+  - ''
+  # Service-wide - like 's3' or 'ec2'
+  service-read:
+  - ''
+  service-write:
+  - ''
+  service-list:
+  - ''
+  service-tagging:
+  - ''
+  service-permissions-management:
+  - ''"""
         crud_template = create_crud_template("myrole")
         self.assertEqual(desired_msg, crud_template)
diff --git a/test/writing/test_validate.py b/test/writing/test_validate.py
index 9e724f93..c58d0ac8 100644
--- a/test/writing/test_validate.py
+++ b/test/writing/test_validate.py
@@ -4,8 +4,6 @@ from policy_sentry.writing.validate import check_actions_schema, check_crud_sche
 valid_cfg_for_crud = {
     "mode": "crud",
     "name": "RoleNameWithCRUD",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789012:role/MyRole",
     "read": ["arn:aws:s3:::example-org-sbx-vmimport",],
     "write": ["arn:aws:s3:::example-org-s3-access-logs",],
     "list": [
@@ -14,14 +12,14 @@ valid_cfg_for_crud = {
     ],
     "tagging": ["arn:aws:ssm:us-east-1:123456789012:parameter/test"],
     "permissions-management": ["arn:aws:s3:::example-org-s3-access-logs"],
-    "wildcard": ["arn:aws:s3:::example-org-s3-access-logs"],
+    "wildcard-only": {
+        "service-read": ["s3"]
+    },
 }
 
 valid_cfg_for_actions = {
     "mode": "actions",
     "name": "RoleNameWithActions",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789102:role/MyRole",
     "actions": [
         "kms:CreateGrant",
         "kms:CreateCustomKeyStore",
@@ -33,16 +31,12 @@ valid_cfg_for_actions = {
 valid_crud_with_one_item_only = {
     "mode": "crud",
     "name": "RoleNameWithCRUD",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789012:role/MyRole",
     "read": ["arn:aws:s3:::example-org-sbx-vmimport",],
 }
 
 invalid_crud_with_mispelled_category = {
     "mode": "crud",
     "name": "RoleNameWithCRUD",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789012:role/MyRole",
     "reed": ["arn:aws:s3:::example-org-sbx-vmimport",],
     "right": ["arn:aws:s3:::example-org-sbx-vmimport",],
     "wrist": ["arn:aws:s3:::example-org-sbx-vmimport",],
diff --git a/test/writing/test_write_policy_library_usage.py b/test/writing/test_write_policy_library_usage.py
index cb85029f..021fd33b 100644
--- a/test/writing/test_write_policy_library_usage.py
+++ b/test/writing/test_write_policy_library_usage.py
@@ -142,10 +142,10 @@ class WritePolicyWithLibraryOnly(unittest.TestCase):
         crud_template["permissions-management"].append(
             "arn:aws:kms:us-east-1:123456789012:key/123456"
         )
-        crud_template["wildcard"].extend(wildcard_actions_to_add)
         crud_template["tagging"].append(
             "arn:aws:ssm:us-east-1:123456789012:parameter/test"
         )
+        crud_template["wildcard-only"]["single-actions"].extend(wildcard_actions_to_add)
         # Modify it
         sid_group = SidGroup()
         minimize = None
diff --git a/test/writing/test_yaml.py b/test/writing/test_yaml.py
index 08fb7d03..10c6e107 100644
--- a/test/writing/test_yaml.py
+++ b/test/writing/test_yaml.py
@@ -10,8 +10,6 @@ db_session = connect_db(DATABASE_FILE_PATH)
 valid_cfg_for_crud = {
     "mode": "crud",
     "name": "RoleNameWithCRUD",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
     "read": [
         "arn:aws:s3:::example-org-sbx-vmimport",
         "arn:aws:s3:::example-kinnaird",
@@ -38,8 +36,6 @@ valid_cfg_for_crud = {
 valid_cfg_for_actions = {
     "mode": "actions",
     "name": "RoleNameWithActions",
-    "description": "Why I need these privs",
-    "role_arn": "arn:aws:iam::123456789102:role/RiskyEC2",
     "actions": [
         "kms:CreateGrant",
         "kms:CreateCustomKeyStore",
@@ -57,8 +53,6 @@ class YamlValidationOverallTestCase(unittest.TestCase):
         """
         cfg_with_missing_name = {
             "mode": "actions",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789102:role/RiskyEC2",
             "actions": [
                 "kms:CreateGrant",
                 "kms:CreateCustomKeyStore",
@@ -69,25 +63,6 @@ class YamlValidationOverallTestCase(unittest.TestCase):
         #  This should NOT raise an exception so leaving it as-is.
         policy = write_policy_with_template(db_session, cfg_with_missing_name)
 
-    def test_allow_missing_description(self):
-        """
-        test_actions_missing_description: write-policy when the YAML file is missing a description
-        :return:
-        """
-        cfg_with_missing_description = {
-            "mode": "actions",
-            "name": "RoleNameWithActions",
-            "role_arn": "arn:aws:iam::123456789102:role/RiskyEC2",
-            "actions": [
-                "kms:CreateGrant",
-                "kms:CreateCustomKeyStore",
-                "ec2:AuthorizeSecurityGroupEgress",
-                "ec2:AuthorizeSecurityGroupIngress",
-            ],
-        }
-        #  This should NOT raise an exception so leaving it as-is.
-        policy = write_policy_with_template(db_session, cfg_with_missing_description)
-
     def test_allow_missing_arn(self):
         """
         test_actions_missing_arn: write-policy actions command when YAML file block is missing an ARN
@@ -96,7 +71,6 @@ class YamlValidationOverallTestCase(unittest.TestCase):
         cfg_with_missing_actions = {
             "mode": "actions",
             "name": "RoleNameWithActions",
-            "description": "Why I need these privs",
             "actions": [
                 "kms:CreateGrant",
                 "kms:CreateCustomKeyStore",
@@ -117,8 +91,6 @@ class YamlValidationCrudTestCase(unittest.TestCase):
         crud_file_input = {
             "mode": "crud",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "read": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
             "write": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
             "list": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
@@ -136,8 +108,6 @@ class YamlValidationCrudTestCase(unittest.TestCase):
         crud_file_input = {
             "mode": "crud",
             "name": "RoleNameWithCRUD",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789012:role/RiskyEC2",
             "read": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
             "write": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
             "list": ["arn:aws:ssm:us-east-1:123456789012:parameter/test",],
@@ -190,8 +160,6 @@ class YamlValidationActionsTestCase(unittest.TestCase):
         cfg_with_missing_actions = {
             "mode": "actions",
             "name": "RoleNameWithActions",
-            "description": "Why I need these privs",
-            "role_arn": "arn:aws:iam::123456789102:role/RiskyEC2",
         }
         with self.assertRaises(Exception):
             policy = write_policy_with_template(db_session, cfg_with_missing_actions)