4

I need some help related to creating AWS policies.

I need a policy linked to an EC2 instance to be able to give only a get-parameters-by-path to a specific parameter in AWS SSM parameter store, without being able to change anything like Delete, Create, etc and should only be able to get the values.

This policy specificity will be given through tags.

Here's my policy I'm trying to use:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": ["ssm:*"],
            "Resource": ["*"]
        },
        {
            "Effect": "Deny",
            "Action": [
               "ssm:PutParameter",
               "ssm:GetParameter",
               "ssm:GetParameters",
               "ssm:DeleteParameter",
               "ssm:GetParameterHistory",
               "ssm:DeleteParameters",
               "ssm:GetParametersByPath"
             ],
            "Resource": ["*"],
            "Condition": {
                "StringNotEquals": {
                    "ssm:resourceTag/env": "development-1"
                }
            }
        }
    ]
}

Using the AWS Policy Simulator it informs you that when trying to View, Create, Modify, Delete Parameters with "ssm:resourceTag/env": "development-2" a denial message is informed, while other projects with "ssm:resourceTag/env": "development-1" it is possible to modify, view, etc.

However, when tying the same policy to an EC2 instance, the policy blocks any of the actions added in Deny.

EC2 Informed Messages:

/development-1/project-1

aws --region us-east-2 ssm get-parameters-by-path --path /development-1/project-1/ --recursive --with-decryption --output text --query "Parameters[].[Value]"

An error occurred (AccessDeniedException) when calling the GetParametersByPath operation: User: arn:aws:sts::111111111:assumed-role/rule-ec2/i-11111111111 is not authorized to perform: ssm:GetParametersByPath on resource: arn:aws:ssm:us-east-2:11111111111:parameter/development-1/project-1/ with an explicit deny

/development-2/project-2

aws --region us-east-2 ssm get-parameters-by-path --path /development-2/project-2/ --recursive --with-decryption --output text --query "Parameters[].[Value]"
    
An error occurred (AccessDeniedException) when calling the GetParametersByPath operation: User: arn:aws:sts::11111111111:assumed-role/rule-ec2/i-11111111111 is not authorized to perform: ssm:GetParametersByPath on resource: arn:aws:ssm:us-east-2:11111111111:parameter/development-2/project-2/ with an explicit deny

Tags used:

key=value

/development-1/project-1:

env=development-1

/development-2/project-2:

env=development-2

What am I doing wrong?

ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
Charmander
  • 43
  • 1
  • 1
  • 5
  • Your statement that your policy should "give only a get-parameters-by-path to a specific Parameter Store [path]" doesn't match what you've implemented in the policy you've shown us. Wouldn't it be more appropriate to implement a policy that simply allowed ssm:GetParametersByPath on the relevant resource path? – jarmod Aug 26 '21 at 22:28

2 Answers2

7

You can't set a condition key based on the tag of the parameter for any of the ssm:GetParameter* IAM actions because the API doesn't (currently) support condition keys.

Instead you can restrict by the ARN of the parameter and in general the practice with SSM parameter store is to use a hierarchical path to the parameters to allow for you to restrict access via IAM and then optionally wildcarding where you are happy for things to have anything under that path.

So a common pattern might be to have some structure that looks something like this:

/production/foo-service/database/password
/production/foo-service/bar-service/api-key
/production/bar-service/database/password
/production/bar-service/foo-service/api-key
/development/foo-service/database/password
/development/foo-service/bar-service/api-key
/development/bar-service/database/password
/development/bar-service/foo-service/api-key

Then for your foo-service running in production you give it an IAM role with the following permissions:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": ["ssm:GetParameter*"],
            "Resource": ["arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/production/foo-service/*"]
        }
    ]
}

This will allow the foo-service in production to then access both of /production/foo-service/database/password and /production/foo-service/bar-service/api-key but won't allow access to any of the other parameters or the ability to modify or delete the parameters.

As mentioned in @Marcin's answer, you don't need to add a deny statement to your IAM policy here because the default for IAM is to deny unless things have explicitly been given an allow statement.

The exception to this would be if you are doing very complex things where you want to give mostly access to a wide range of things but then block a small subset of things. This blog post talks about how the default ReadOnlyAccess policy is too permissive for their organisation so they restrict access with a deny statement on things they don't want to give such open access to. They could also go the opposite way and never use that AWS managed policy and instead have to maintain a very broad set of actions across a lot of services themselves which might be considered safer but also potentially a lot of work.

ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
  • I have been able to set the following conditions on "ssm:GetParameter", "ssm:GetParameterHistory", "ssm:GetParameters", "ssm:GetParametersByPath": `"Condition": { "StringNotEquals": { "aws:ResourceTag/Secured": "True" } }` – Stephan Schrijver May 17 '23 at 09:31
6

You are using Deny to ssm:GetParametersByPath, so it will always be denied. Deny always takes a priority over any allow.

But in your case, since its instance profile, your policy doesn't have to be that complex. By default, everything is implicitly denied, so you only need explicit allow:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": ["ssm:GetParametersByPath"],
            "Resource": ["*"]
        }
    ]
}

For Resource you can add only the ssm parameters that you want to allow access to, not all of them.

Marcin
  • 215,873
  • 14
  • 235
  • 294