Skip to content

Instantly share code, notes, and snippets.

@jstewmon
Last active April 15, 2024 18:03
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jstewmon/ee5d4b7ec0d8d60cbc303cb515272f8a to your computer and use it in GitHub Desktop.
Save jstewmon/ee5d4b7ec0d8d60cbc303cb515272f8a to your computer and use it in GitHub Desktop.
AWS IAM Policy JSON Schema
{
"type": "object",
"required": ["Statement"],
"additionalProperties": false,
"properties": {
"Version": {
"type": "string",
"enum": ["2008-10-17", "2012-10-17"]
},
"Id": {
"type": "string"
},
"Statement": {
"oneOf": [
{
"$ref": "#/definitions/Statement"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/Statement"
}
}
]
}
},
"definitions": {
"aws-arn": {
"type": "string",
"pattern": "^arn:aws:[^:]+:[^:]*:(?:\\d{12}|\\*)?:.+$"
},
"aws-principal-arn": {
"type": "string",
"pattern": "^arn:aws:iam::\\d{12}:(?:root|user|group|role)"
},
"string-array": {
"type": "array",
"items": {
"type": "string"
}
},
"string-or-string-array": {
"anyOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/string-array"
}
]
},
"wildcard": {
"const": "*"
},
"condition-set-value": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/string-array"
}
},
"condition-value": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#/definitions/string-or-string-array" },
{ "type": "boolean" },
{ "type": "number" }
]
}
},
"Statement": {
"allOf": [
{
"oneOf": [{ "required": ["Action"] }, { "required": ["NotAction"] }]
},
{
"oneOf": [
{ "required": ["Resource"] },
{ "required": ["NotResource"] }
]
},
{
"type": "object",
"required": ["Effect"],
"additionalProperties": false,
"properties": {
"Sid": {
"type": "string"
},
"Effect": {
"type": "string",
"enum": ["Allow", "Deny"]
},
"Action": {
"$ref": "#/definitions/Action"
},
"NotAction": {
"$ref": "#/definitions/Action"
},
"Principal": {
"$ref": "#/definitions/Principal"
},
"NotPrincipal": {
"$ref": "#/definitions/Principal"
},
"Resource": {
"$ref": "#/definitions/Resource"
},
"NotResource": {
"$ref": "#/definitions/Resource"
},
"Condition": {
"$ref": "#/definitions/Condition"
}
}
}
]
},
"Action": {
"anyOf": [
{
"$ref": "#/definitions/wildcard"
},
{
"$ref": "#/definitions/string-or-string-array"
}
]
},
"Principal": {
"anyOf": [
{
"$ref": "#/definitions/wildcard"
},
{
"type": "object",
"properties": {
"AWS": {
"anyOf": [
{
"$ref": "#/definitions/wildcard"
},
{
"$ref": "#/definitions/aws-principal-arn"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/aws-principal-arn"
}
}
]
},
"Federated": {
"$ref": "#/definitions/string-or-string-array"
},
"CanonicalUser": {
"$ref": "#/definitions/string-or-string-array"
}
}
}
]
},
"Resource": {
"anyOf": [
{
"$ref": "#/definitions/wildcard"
},
{
"$ref": "#/definitions/aws-arn"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/aws-arn"
}
}
]
},
"Condition": {
"type": "object",
"properties": {
"Null": {
"type": "object",
"additionalProperties": {
"enum": ["true", "false", true, false]
}
},
"StringEquals": {
"$ref": "#/definitions/condition-value"
},
"StringNotEquals": {
"$ref": "#/definitions/condition-value"
},
"StringEqualsIgnoreCase": {
"$ref": "#/definitions/condition-value"
},
"StringNotEqualsIgnoreCase": {
"$ref": "#/definitions/condition-value"
},
"StringLike": {
"$ref": "#/definitions/condition-value"
},
"StringNotLike": {
"$ref": "#/definitions/condition-value"
},
"NumericEquals": {
"$ref": "#/definitions/condition-value"
},
"NumericNotEquals": {
"$ref": "#/definitions/condition-value"
},
"NumericLessThan": {
"$ref": "#/definitions/condition-value"
},
"NumericLessThanEquals": {
"$ref": "#/definitions/condition-value"
},
"NumericGreaterThan": {
"$ref": "#/definitions/condition-value"
},
"NumericGreaterThanEquals": {
"$ref": "#/definitions/condition-value"
},
"DateEquals": {
"$ref": "#/definitions/condition-value"
},
"DateNotEquals": {
"$ref": "#/definitions/condition-value"
},
"DateLessThan": {
"$ref": "#/definitions/condition-value"
},
"DateLessThanEquals": {
"$ref": "#/definitions/condition-value"
},
"DateGreaterThan": {
"$ref": "#/definitions/condition-value"
},
"DateGreaterThanEquals": {
"$ref": "#/definitions/condition-value"
},
"Bool": {
"$ref": "#/definitions/condition-value"
},
"BinaryEquals": {
"$ref": "#/definitions/condition-value"
},
"IpAddress": {
"$ref": "#/definitions/condition-value"
},
"NotIpAddress": {
"$ref": "#/definitions/condition-value"
},
"ArnEquals": {
"$ref": "#/definitions/condition-value"
},
"ArnNotEquals": {
"$ref": "#/definitions/condition-value"
},
"ArnLike": {
"$ref": "#/definitions/condition-value"
},
"ArnNotLike": {
"$ref": "#/definitions/condition-value"
},
"StringEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"StringNotEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"StringEqualsIgnoreCaseIfExists": {
"$ref": "#/definitions/condition-value"
},
"StringNotEqualsIgnoreCaseIfExists": {
"$ref": "#/definitions/condition-value"
},
"StringLikeIfExists": {
"$ref": "#/definitions/condition-value"
},
"StringNotLikeIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericNotEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericLessThanIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericLessThanEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericGreaterThanIfExists": {
"$ref": "#/definitions/condition-value"
},
"NumericGreaterThanEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateNotEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateLessThanIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateLessThanEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateGreaterThanIfExists": {
"$ref": "#/definitions/condition-value"
},
"DateGreaterThanEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"BoolIfExists": {
"$ref": "#/definitions/condition-value"
},
"BinaryEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"IpAddressIfExists": {
"$ref": "#/definitions/condition-value"
},
"NotIpAddressIfExists": {
"$ref": "#/definitions/condition-value"
},
"ArnEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"ArnNotEqualsIfExists": {
"$ref": "#/definitions/condition-value"
},
"ArnLikeIfExists": {
"$ref": "#/definitions/condition-value"
},
"ArnNotLikeIfExists": {
"$ref": "#/definitions/condition-value"
},
"ForAllValues:StringEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:StringNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:StringEqualsIgnoreCase": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:StringNotEqualsIgnoreCase": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:StringLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:StringNotLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericLessThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericLessThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericGreaterThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NumericGreaterThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateLessThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateLessThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateGreaterThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:DateGreaterThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:Bool": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:BinaryEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:IpAddress": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:NotIpAddress": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:ArnEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:ArnNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:ArnLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAllValues:ArnNotLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringEqualsIgnoreCase": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringNotEqualsIgnoreCase": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:StringNotLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericLessThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericLessThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericGreaterThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NumericGreaterThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateLessThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateLessThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateGreaterThan": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:DateGreaterThanEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:Bool": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:BinaryEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:IpAddress": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:NotIpAddress": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:ArnEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:ArnNotEquals": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:ArnLike": {
"$ref": "#/definitions/condition-set-value"
},
"ForAnyValues:ArnNotLike": {
"$ref": "#/definitions/condition-set-value"
}
}
}
}
}
@robbrad
Copy link

robbrad commented Aug 4, 2021

Super Schema!

Out of interest would numeric be allowed here https://gist.github.com/jstewmon/ee5d4b7ec0d8d60cbc303cb515272f8a#file-aws-iam-poilcy-schema-json-L198

We had 0 as a value but the schema wants it to be a string "0"

@jstewmon
Copy link
Author

jstewmon commented Aug 4, 2021

Hmm... according to the grammar, number is allowed as a condition value. Did you check whether the AWS service accepts your policy with a JSON number as the condition value?

@robbrad
Copy link

robbrad commented Aug 4, 2021

Yeh - nice link! - thats what I mean number is not in the Schema : you have

"$ref": "#/definitions/string-or-string-array" or enum for condition value

Good idea to try it - we are using the schema for SCP validation but it seems the SCP console is pretty "accpeting"- I will try it in the IAM policy to see whats allowed

@jmflynn81
Copy link

jmflynn81 commented Sep 7, 2021

Definitely an awesome start!

Just looking at the required section for the 'Statement' you have 'Action' and 'Resource' in there but they are mutually exclusive with their 'NotAction' and 'NotResource' counterparts. IE you could have a valid statement with a 'NotAction' and no 'Action' but the schema validation would fail. I'm trying to resolve these requirements at the moment with the following...

"allOf": [
    {
        "required": [
            "Effect"
        ]
    },
    {
        "oneOf": [
            {
                "required": [
                    "Action"
                ]
            },
            {    
                "required": [
                    "NotAction"                                               
                ]
            }    
        ]
    },
    {
        "oneOf": [
            {
                "required": [
                    "Resource"
                ]
            },
            {
                "required": [
                    "NotResource"
                ]
            }
        ]
    }
]

Also, looking at the grammer (and something which tripped me up), the statement can be both an 'array' and an 'object' (single statement).

@jstewmon
Copy link
Author

jstewmon commented Sep 9, 2021

Thanks for the feedback! I updated the gist accordingly.

n.b. I did some cursory testing, but I'm not using this schema myself, so no warranties, etc.

@LeePorte
Copy link

LeePorte commented Jun 9, 2022

I have come up against an issue in that if you use the Action of sts:AssumeRole the schema causes failure due there being no Resource present.

e.g. from https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_policy-examples.html

{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Effect": "Allow",
     "Principal": {
       "Service": [
         "elasticmapreduce.amazonaws.com",
         "datapipeline.amazonaws.com"
       ]
     },
     "Action": "sts:AssumeRole"
   }
 ]
}

I've posted a gist which fixes this over at https://gist.github.com/LeePorte/16825880f7eac126555b9aa3bda1cf22

It adds in the ability to have a Service as a principal which validates against the pattern of .+.amazonaws.com$ and has some if else logic as to weather to apply

"oneOf": [
          { "required": ["Resource"] },
          { "required": ["NotResource"] }
        ]

based on if the Action is sts:AssumeRole

@rrrix
Copy link

rrrix commented Sep 14, 2022

This is why I love open source :) :)

@Edgar-P-yan
Copy link

Hi! The schema is awesome. Wanted to mention this:

  1. Principal and NotPrincipal are also mutually exclusive, so i would add:
"oneOf": [
  { "required": ["Principal"] },
  { "required": ["NotPrincipal"] }
]
  1. Seems like the array of statements cant be empty, so i'd add "minLength": 1
"Statement": {
  "oneOf": [
    {
      "$ref": "#/definitions/Statement"
    },
    {
      "type": "array",
      "minLength": 1,
      "items": {
        "$ref": "#/definitions/Statement"
      }
    }
  ]
}

Thanks a lot for the schema, it saved me some time :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment