Skip to content

Lambda Star Permissions

Level: Warning

Initial version: 0.1.3

cfn-lint: WS1003

tflint: aws_iam_role_lambda_no_star

With Lambda functions, you should follow least-privileged access and only allow the access needed to perform a given operation. Attaching a role with more permissions than necessary can open up your systems for abuse.

Limitations on policies

This rule only works with inline IAM policy documents: policies defined as part of an IAM role resource and, for AWS::Serverless::Function resources without an explicit Role, inline policy documents in the function's Policies property. It will not check managed policies, policies defined as separate resources, or SAM policy templates such as DynamoDBWritePolicy, which are not expanded into effective permissions.

IAM Statement elements written either as a single object or as a list are both supported. The rule checks wildcard actions (for example * or s3:*); a wildcard in a Resource ARN, such as arn:aws:s3:::my-bucket/*, does not by itself trigger this rule.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Resources:
  MyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      # The rule will check this policy
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: dynamodb:Query
                Resource: "*"

  # It will not check this policy
  MyPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: dynamodb:*
                Resource: "*"
      Roles:
        - !GetAtt MyRole.Arn
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
resource "aws_iam_role" "this" {
  name = "my-function-role"
  assume_role_policy = data.aws_iam_policy_document.assume.json

  # The rule will check this policy
  inline_policy {
    name = "FunctionPolicy"
    policy = data.aws_iam_policy_document.valid.json
  }
}

# It will not check this policy
resource "aws_iam_policy" "this" {
  policy = data.aws_iam_policy_document.invalid.json
}

resource "aws_iam_policy_attachment" "this" {
  roles = [aws_iam_role.this.name]
  policy_arn = aws_iam_policy.this.arn
}

data "aws_iam_policy_document" "assume" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type       = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

data "aws_iam_policy_document" "valid" {
  statement {
    actions = ["dynamodb:Query"]
    resources = ["arn:aws:dynamodb:eu-west-1:111122223333:table/my-table"]
  }
}

data "aws_iam_policy_document" "invalid" {
  statement {
    actions = ["dynamodb:*"]
    resources = ["*"]
  }
}

Why is this a warning?

If your Lambda function needs a broad range of permissions, you do not know ahead of time which permissions you will need, and you have evaluated the risks of using broad permissions for this function, you might ignore this rule.

AWS SAM templates

For AWS::Serverless::Function resources without an explicit Role, this rule checks inline IAM policy documents in the function's effective Policies property. Named managed policies and SAM policy templates such as DynamoDBWritePolicy are not expanded into effective permissions.

Implementations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { AttributeType, Table } from '@aws-cdk/aws-dynamodb';
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';

export class MyStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const myTable = new Table(
      scope, 'MyTable',
      {
        partitionKey: {
          name: 'id',
          type: AttributeType.STRING,
        }
      },
    );

    const myFunction = new Function(
      scope, 'MyFunction',
      {
        code: Code.fromAsset('src/hello/'),
        handler: 'main.handler',
        runtime: Runtime.PYTHON_3_8,
      }
    );

    // Grant read access to the DynamoDB table
    table.grantReadData(myFunction);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "Resources": {
    "MyFunction": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "CodeUri": ".",
        "Runtime": "python3.8",
        "Handler": "main.handler",

        "Policies": [{
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            // Tightly scoped permissions to just 's3:GetObject'
            // instead of 's3:*' or '*'
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-bucket/*"
          }]
        }]
      }
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Runtime: python3.12
      Handler: main.handler

      Policies:
        - Version: "2012-10-17"
          Statement:
            - Effect: Allow
              # Tightly scoped permissions to just 's3:GetObject'
              # instead of 's3:*' or '*'
              Action: s3:GetObject
              Resource: "arn:aws:s3:::my-bucket/*"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
provider:
  name: aws
  iam:
    role:
      name: my-function-role
      statements:
        - Effect: Allow
          # Tightly scoped permissions to just 's3:GetObject'
          # instead of 's3:*' or '*'
          Action: s3:GetObject
          Resource: "arn:aws:s3:::my-bucket/*"

functions:
  hello:
    handler: handler.hello
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
resource "aws_iam_role" "this" {
  name = "my-function-role"
  assume_role_policy = data.aws_iam_policy_document.assume.json

  inline_policy {
    name = "FunctionPolicy"
    policy = data.aws_iam_policy_document.this.json
  }
}

data "aws_iam_policy_document" "assume" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type       = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

data "aws_iam_policy_document" "this" {
  statement {
    # Tightly scoped permissions to just 'dynamodb:Query'
    # instead of 'dynamodb:*' or '*'
    actions = ["dynamodb:Query"]
    resources = ["arn:aws:dynamodb:eu-west-1:111122223333:table/my-table"]
  }
}

resource "aws_lambda_function" "this" {
  function_name = "my-function"
  handler       = "main.handler"
  runtime       = "python3.8"
  filename      = "function.zip"
  role          = aws_iam_role.this.arn
}

See also