community icon indicating copy to clipboard operation
community copied to clipboard

Lambda Controller - appears to require extra IAM permissions when first run for a given account

Open rlister opened this issue 2 years ago • 6 comments

Describe the bug

Controller needs extra IAM permissions, but only when first installed.

I am using the documented IAM policy from https://raw.githubusercontent.com/aws-controllers-k8s/lambda-controller/main/config/iam/recommended-inline-policy.

For each AWS account and region where I have the lambda controller installed, the first Function I create reports AccessDeniedException: \n\tstatus code: 403 in the controller logs.

If I edit the policy to add Action *, the creation succeeds. I can subsequently remove this Action, and the controller continues to work as advertised.

Does the controller perhaps need to create some resource one time, if it has never run before?

Relevant debug-level logs:

2023-08-31T14:51:44.198Z    DEBUG    ackrt    > r.Sync    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "generation": 1}
2023-08-31T14:51:44.198Z    DEBUG    ackrt    >> r.resetConditions    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "generation": 1}
2023-08-31T14:51:44.198Z    DEBUG    ackrt    << r.resetConditions    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "generation": 1}
2023-08-31T14:51:44.198Z    DEBUG    ackrt    >> rm.ResolveReferences    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.219Z    DEBUG    ackrt    << rm.ResolveReferences    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.219Z    DEBUG    ackrt    >> rm.EnsureTags    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.219Z    DEBUG    ackrt    << rm.EnsureTags    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.219Z    DEBUG    ackrt    >> rm.ReadOne    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.219Z    DEBUG    ackrt    >>> rm.sdkFind    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.238Z    DEBUG    ackrt    <<< rm.sdkFind    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "resource not found"}
2023-08-31T14:51:44.238Z    DEBUG    ackrt    << rm.ReadOne    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "resource not found"}
2023-08-31T14:51:44.238Z    DEBUG    ackrt    >> r.createResource    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.238Z    DEBUG    ackrt    >>> rm.Create    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.238Z    DEBUG    ackrt    >>>> rm.sdkCreate    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    <<<< rm.sdkCreate    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "AccessDeniedException: \n\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80"}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    <<< rm.Create    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "AccessDeniedException: \n\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80"}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    << r.createResource    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "AccessDeniedException: \n\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80"}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    >> r.ensureConditions    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    >>> rm.IsSynced    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    <<< rm.IsSynced    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    << r.ensureConditions    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    < r.Sync    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "error": "AccessDeniedException: \n\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80"}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    > r.patchResourceStatus    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.484Z    DEBUG    ackrt    >> kc.Patch (status)    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.499Z    DEBUG    ackrt    patched resource status    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1, "json": "{\"metadata\":{\"resourceVersion\":\"848660735\"},\"spec\":{\"role\":null},\"status\":{\"conditions\":[{\"lastTransitionTime\":\"2023-08-31T14:51:44Z\",\"status\":\"True\",\"type\":\"ACK.ReferencesResolved\"},{\"message\":\"AccessDeniedException: \\n\\tstatus code: 403, request id: 29d65
0e4-5dc3-4971-8627-e6700b3f9b80\",\"status\":\"True\",\"type\":\"ACK.Recoverable\"},{\"lastTransitionTime\":\"2023-08-31T14:51:44Z\",\"message\":\"Unable to determine if desired resource state matches latest observed state\",\"reason\":\"AccessDeniedException: \\n\\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80\",\"status\":\"Unknown\",\"type\":\"ACK.ResourceSynced\"}]}}"}
2023-08-31T14:51:44.499Z    DEBUG    ackrt    << kc.Patch (status)    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.499Z    DEBUG    ackrt    < r.patchResourceStatus    {"account": "xxx", "role": "", "region": "us-east-1", "kind": "Function", "namespace": "default", "name": "test", "is_adopted": false, "generation": 1}
2023-08-31T14:51:44.499Z    ERROR    Reconciler error    {"controller": "function", "controllerGroup": "lambda.services.k8s.aws", "controllerKind": "Function", "Function": {"name":"test","namespace":"default"}, "namespace": "default", "name": "test", "reconcileID": "32a2612b-2db2-4d3f-89c0-cfa38c956a72", "error": "AccessDeniedException: \n\tstatus code: 403, request id: 29d650e4-5dc3-4971-8627-e6700b3f9b80"}
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler
    /go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:329
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem
    /go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:274
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2.2
    /go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:235
2023-08-31T14:51:44.510Z    DEBUG    exporter.field-export-reconciler    error did not need requeue    {"error": "the source resource is not synced yet"}

Steps to reproduce

  • have an account/region which has never run lambda controller
  • install lambda controller
  • create a Function, which will fail
  • edit controller IAM policy to add Action *
  • Function creation will succeed
  • edit controller IAM policy to remove Action *

Expected outcome

Initial install of controller should have sufficient permissions.

Environment

  • Kubernetes version: v1.27.4-eks-2d98532
  • Using EKS (yes/no), if so version? yes 1.27
  • AWS service targeted (S3, RDS, etc.) Lambda

rlister avatar Aug 31 '23 15:08 rlister

/cc @aws-controllers-k8s/lambda-maintainer

a-hilaly avatar Sep 06 '23 03:09 a-hilaly

Hey @rlister,

Thanks for bringing this into our attention. Can you give more details on the template you used to create the function?

We tried to reproduce it in a new region and it works fine with the IAM role you described. Maybe there's something special with the template you are trying. Thanks!

Vandita2020 avatar Sep 07 '23 00:09 Vandita2020

Example function template (applied using helm):

apiVersion: lambda.services.k8s.aws/v1alpha1
kind: Function

metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}

spec:
  name: {{ .Release.Name }}
  description: {{ now | unixEpoch | quote }}

  packageType: Image

  code:
    imageURI: {{ .Values.image }}

  timeout: 180

  environment:
    variables: {{ toJson .Values.env }}

  roleRef:
    from:
      name: {{ .Release.Name }}

spec.description is set from date to force an update on every helm update, due to issues getting function to update from new images.

Exec role is created with ACK IAM controller:

apiVersion: iam.services.k8s.aws/v1alpha1
kind: Role

metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}

spec:
  name: {{ .Release.Name }}
  description: {{ .Release.Name }}

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

  policies:
    - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

and spec.environment.variables is set from a values object like:

env:
  SENTRY_ENVIRONMENT: production

rlister avatar Oct 10 '23 20:10 rlister

Hey @rlister, thanks for sharing more details on the issue.

I looked into your issue and figured the reason behind access denied is because your ECR repository where your image is stored might not have all the permissions. If you see this documentation, it lists out the two ECR repository policies ecr:BatchGetImage and ecr:GetDownloadUrlForLayer needed in your ECR repo for Lambda to retrieve the image.

In case when Amazon ECR repository does not include these permissions, Lambda adds ecr:BatchGetImage and ecr:GetDownloadUrlForLayer to the container image repository permissions. However Lambda can add these permissions only if the principal calling Lambda has ecr:getRepositoryPolicy and ecr:setRepositoryPolicy permissions.

In your case the recommended-inline-policy do not have the permissions required for Lambda to add the permissions in ECR. When you change policy action to * it adds permissions to do everything including the permissions required by Lambda to set the ECR permissions. This way ECR policy is updated and the next time you create a function using same image, the ECR now has required permission and functions get created properly.

So please check if your ECR repository has the required permissions.

Vandita2020 avatar Feb 22 '24 23:02 Vandita2020

Closing this issue for now. Please let us know if you have any more questions regarding this. Thanks!

Vandita2020 avatar Apr 08 '24 18:04 Vandita2020