Giter Club home page Giter Club logo

kube-aws-iam-controller's Introduction

AWS IAM Controller for Kubernetes

Build Status Coverage Status

This is a Kubernetes controller for distributing AWS IAM role credentials to pods via secrets.

It aims to solve the same problem as other existing tools like jtblin/kube2iam and uswitch/kiam, namely distribute different AWS IAM roles to different pods within the same cluster. However, it solves the problem in a different way to work around an inherit problem with the architecture or kube2iam and similar solutions.

EC2 metadata service solution (kube2iam, kiam)

Kube2iam works by running an EC2 metadata service proxy on each node in order to intercept role requests made by pods using one of the AWS SDKs. Instead of forwarding the node IAM role to the pod, the proxy will make an assume role call to STS and get the role requested by the pod (via an annotation). If the assume role request is fast, everything is fine, and the pod will get the correct role. However, if the assume role request is too slow (>1s) then the AWS SDKs will timeout and try to get credentials via the next option in the chain (e.g. a file) resulting in the pod not getting the expected role or no role at all.

This is often not a problem in clusters with a stable workload, but if you have clusters with a very dynamic workload there will be a lot of cases where a pod starts before kube2iam is ready to provide the expected role. One case is when scaling up a cluster and a new pod lands on a fresh node before kube2iam, another case is when a new pod is created and starts before kube2iam got the event that the pod was created. During update of the kube2iam daemonset there will also be a short timeframe where the metadata url will be unavailable for the pods which could lead to a refresh of credentials failing.

Kubernetes secrets solution

Instead of running as a proxy on each node, this controller runs as a single instance and distributes AWS IAM credentials via secrets. This solves the race condition problem by relying on a property of Kubernetes which ensures that a secret, mounted by a pod, must exist before the pod is started. This means that the controller can even be away for a few minutes without affecting pods running in the cluster as they will still be able to mount and read the secrets. Furthermore having a single controller means there is only one caller for to the AWS API resulting in fewer calls which can prevent ratelimiting in big clusters and you don't need to give all nodes the power to assume other roles if it's not needed.

One minor trade-off with this solution is that each pod requiring AWS IAM credentials must define a secret mount rather than a single annotation.

NB This approach currently only works for some of the AWS SDKs. I'm reaching out to AWS to figure out if this is something that could be supported.

See the configuration guide for supported SDKs.

How it works

The controller continuously looks for custom AWSIAMRole resources which specify an AWS IAM role by name or by the full ARN. For each resource it finds, it will generate/update corresponding secrets containing credentials for the IAM role specified. The secrets can be mounted by pods as a file enabling the AWS SDKs to use the credentials.

If an AWSIAMRole resource is deleted, the corresponding secret would be automatically cleaned up as well.

Specifying AWS IAM role on pods

See the configuration guide for supported SDKs.

In order to specify that a certain AWS IAM Role should be available for applications in a namespace you need to define an AWSIAMRole resource which references the IAM role you want:

apiVersion: zalando.org/v1
kind: AWSIAMRole
metadata:
  name: my-app-iam-role
spec:
  # The roleReference allows specifying an AWS IAM role name or arn
  # Possible values:
  #   "aws-iam-role-name"
  #   "arn:aws:iam::<account-id>:role/aws-iam-role-name"
  roleReference: <my-iam-role-name-or-arn>

The controller will detect the resource and create a corresponding secret with the same name containing the role credentials. To use the credentials in a pod you simply mount the secret (called my-app-iam-role in this example), making the credentials available as a file for your application to read and use. Additionally you must also define an environment variable AWS_SHARED_CREDENTIALS_FILE=/path/to/mounted/secret for each container. The environment variable is used by AWS SDKs and the AWS CLI to automatically find and use the credentials file.

See a full example in example-app.yaml.

Note: This way of specifying the role on pod specs are subject to change. It is currently moving a lot of effort on to the users defining the pod specs. A future idea is to make the controller act as an admission controller which can inject the required configuration automatically.

Setting up AWS IAM roles

The controller does not take care of AWS IAM role provisioning and assumes that the user provisions AWS IAM roles manually, for instance via CloudFormation or Terraform.

Here is an example of an AWS IAM role defined via CloudFormation:

Parameters:
  AssumeRoleARN: 
    Description: "Role ARN of the role used by kube-aws-iam-controller"
    Type: String
Metadata:
  StackName: "aws-iam-example"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example IAM Role"
Resources:
  IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "aws-iam-example"
      Path: /
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            AWS: !Ref "AssumeRoleARN"
        Version: '2012-10-17'
      Policies:
      - PolicyName:  "policy"
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "ec2:Describe*"
            Resource: "*"

The role could be created via:

# $ASSUME_ROLE_ARN is the ARN of the role used by the kube-aws-iam-controller deployment
$ aws cloudformation create-stack --stack-name aws-iam-example \
  --parameters "ParameterKey=AssumeRoleARN,ParameterValue=$ASSUME_ROLE_ARN" \
  --template-body=file://iam-role.yaml --capabilities CAPABILITY_NAMED_IAM

The important part is the AssumeRolePolicyDocument:

AssumeRolePolicyDocument:
  Statement:
  - Action: sts:AssumeRole
    Effect: Allow
    Principal:
      AWS: !Ref "AssumeRoleARN"
  Version: '2012-10-17'

This allows the kube-aws-iam-controller to assume the role and provide credentials on behalf of the application requesting credentials via an AWSIAMRole resource in the cluster.

The AssumeRoleARN is the ARN of the role which the kube-aws-iam-controller is running with. Usually this would be the instance role of the EC2 instance were the controller is running.

Using custom Assume role

Sometimes it's desirable to let the controller assume roles with a specific role dedicated for that task i.e. a role different from the instance role. The controller allows specifying such a role via the --assume-role=<controller-role> flag providing the following setup:

                                                                           +-------------+
                                                                           |             |
                                                                      +--> | <app-role1> |
+-----------------+                +-------------------+              |    |             |
|                 |                |                   |              |    +-------------+
| <instance-role> | -- assumes --> | <controller-role> | -- assumes --+
|                 |                |                   |              |    +-------------+
+-----------------+                +-------------------+              |    |             |
                                                                      +--> | <app-role2> |
                                                                           |             |
                                                                           +-------------+

In this case the <instance-role> will only be used for the initial assuming of the <controller-role> and all <app-role>s are assumed by the <controller-role>. This makes it possible to have many different <instance-role>s while the <app-role>s only have to trust the single static <controller-role>. If you don't specify --assume-role then the <instance-role> would have to assume the <app-role>s.

Here is an example of the AWS IAM roles defined for this set-up to work:

Metadata:
  StackName: "aws-iam-assume-role-example"
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example AWS IAM Assume Role"
Resources:
  InstanceIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "instance-role"
      Path: /
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
        Version: '2012-10-17'
      Policies:
      - PolicyName:  "policy"
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "sts:AssumeRole"
            Resource: "*"

  KubeAWSIAMControllerIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "kube-aws-iam-controller"
      Path: /
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${InstanceIAMRole}'
        Version: '2012-10-17'
      Policies:
      - PolicyName:  "policy"
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "sts:AssumeRole"
            Resource: "*"

  APPIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "app-role"
      Path: /
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${KubeAWSIAMControllerIAMRole}'
        Version: '2012-10-17'
      Policies:
      - PolicyName:  "policy"
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "ec2:Describe*"
            Resource: "*"

Setup

The kube-aws-iam-controller can be run as a deployment in the cluster. See deployment.yaml.

Deploy it by running:

$ kubectl apply -f docs/deployment.yaml

To ensure that pods requiring AWS IAM roles doesn't go to the EC2 metadata service of the node instead of using the credentials file provided by the secret you must block the metadata service from the pod network on each node. E.g. with an iptables rule:

$ /usr/sbin/iptables \
      --append PREROUTING \
      --protocol tcp \
      --destination 169.254.169.254 \
      --dport 80 \
      --in-interface cni0 \
      --match tcp \
      --jump DROP

Where cni0 is the interface of the pod network on the node.

Note: The controller will read all pods on startup and therefore the memory limit for the pod must be set relative to the number of pods in the cluster (i.e. vertical scaling).

Bootstrap in non-AWS environment

If you need access to AWS from another environment e.g. GKE then the controller can be deployed with seed credentials and refresh its own credentials used for the assume role calls similar to how it refreshes all other credentials.

To create the initial seed credentials you must configure an AWS IAM role used for the assume role calls. In this example the IAM role is created via cloudformation, but you can do it however you like. The important part is that the role has permissions to do sts calls as it will be assuming other roles. And you should also allow the role to be assumed by your own user for creating the initial seed credentials:

$ cat role.yaml
Metadata:
  StackName: kube-aws-iam-controller-role
Resources:
  KubeAWSIAMControllerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: kube-aws-iam-controller-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action: ['sts:AssumeRole']
          Effect: Allow
          Principal:
            AWS: "<arn-of-your-user>"
        Version: '2012-10-17'
      Path: /
      Policies:
      - PolicyName: assumer-role
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Action:
            - sts:*
            Resource: "*"
            Effect: Allow
# create the role via cloudformation
$ aws cloudformation create-stack --stack-name kube-aws-iam-controller-role --template-body=file://role.yaml --capabilities CAPABILITY_NAMED_IAM

And then you can use the script ./scripts/set_secret.sh to generate initial credentials and create a secret. The script requires the ARN of the created IAM role as an argument.

$ ./scripts/set_secret.sh $IAM_ROLE_ARN

Once the secret is created you can deploy the controller using the example manifest in deployment_with_role.yaml.

The controller will use the secret you created with temporary credentials and continue to refresh the credentials automatically.

Building

This project uses Go modules as introduced in Go 1.11 therefore you need Go >=1.11 installed in order to build. If using Go 1.11 you also need to activate Module support.

Assuming Go has been setup with module support it can be built simply by running:

export GO111MODULE=on # needed if the project is checked out in your $GOPATH.
$ make

kube-aws-iam-controller's People

Contributors

adetunjiakintundeakinde avatar arjunrn avatar burdzwastaken avatar demoncoder95 avatar dependabot[bot] avatar elemental-lf avatar fpietsch avatar gargravarr avatar headcr4sh avatar jjst avatar katyanna avatar linki avatar mikkeloscar avatar myaser avatar szuecs avatar tariq1890 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kube-aws-iam-controller's Issues

Are credentials persistent?

I need a mechanism where credentials obtained by Pod are persistent. Are they persistent based on IAM role or they are valid for certain amount of time and must be obtained again?

the "awsiamroles" crd is broken

I've tried to apply the docs/aws_iam_role_crd.yaml file and got the following error:

$ kubectl apply -f aws_iam_role_crd.yaml

The CustomResourceDefinition "awsiamroles.zalando.org" is invalid: 
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[AssumeRolePolicyDocument].properties[Statement].items.properties[Principal].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[AssumeRolePolicyDocument].properties[Statement].items.type: Required value: must not be empty for specified array items
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[AssumeRolePolicyDocument].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[Policies].properties[PolicyDocument].properties[Statement].items.type: Required value: must not be empty for specified array items
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[Policies].properties[PolicyDocument].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].properties[Policies].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[spec].properties[roleDefinition].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[spec].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.properties[status].type: Required value: must not be empty for specified object fields
* spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root

Am I missing something here?

Monitoring IAM Controller

Any plans of adding monitoring and metrics related to functioning of iam controller? Any ideas around how it can be implemented?

Secret content places specific requirements on pods

The Kubernetes Secrets created by the controller include two files: credentials.json, which is refreshed regularly, and credentials.process, which remains fixed over time. The content of the latter file is as follows:

[default]
credential_process = cat /meta/aws-iam/credentials.json

This content forces pods that wish to refresh their credentials regularly to accommodate the following:

  • cat must be present in the container image.
    The documentation for the Python and Go SDKs mentions this requirement today.
  • sh must be present in the container image.
    Using the aws.credentials.processcreds.ProcessProvider type invokes sh -c as the prefix to the specified command.
  • The pod must mount the Secret at /meta/aws-iam.
    The examples in the documentation show this, but don't mention that it's mandatory when using the credentials.process file.

The last one surprised me, but as I started thinking of how to work around it, I discovered that the AWS SDKs make no promises about the current working directory of the command invoked by way of the credential_process directive. Presumably it inherits the working directory of the parent process.

It may not seem like a significant imposition on pod authors, but it warrants documenting the constraint if we can't find a workaround. One approach is to use an init container to write the equivalent of the credentials.process into a mounted "emptyDir" volume, which would allow the pod author to point at a credentials.json file mounted elsewhere. Doing that probably requires another fifteen lines of configuration.

The first two impositions make it difficult to use the scratch base image, or something similarly slim like gcr.io/distroless/base, both popular with programs written in Go. Again, it may be possible to use an init container to copy cat and sh into a mounted volume, in order to allow use of an existing container image that lacks these tools.

I had thought that it would be worth mentioning in the documentation that applications that only need to read and use credentials very early in their initialization stage (such as reading an S3 object when starting) could safely read the credentials.json file, since it wouldn't matter if the credentials expired afterward. However, the credentials.json file is refreshed on a schedule that's not synchronized with any pod's lifecycle. It's possible that a pod starts running and mounts the credentials.json file just before those credentials are about to expire. The controller does offer the --refresh-limit command-line flag to adjust this tolerance period, but it would be disingenuous to claim that every pod is guaranteed to start with a credentials.json file that will be valid for that period.

This is partly a request for augmenting the documentation, and partly a request for ideas for how we could reduce these requirements on containers that consume the Secrets published by this controller. Don't get me started on AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.

Emit events to pods

The controller should emit events to pods when it's unable to setup the IAM credentials requested.

Taking control of seed credential

When running in a non-AWS environment, we have to seed a credential in the system. Ideally, it would be great for that credential to then be managed by the controller so it gets auto-rotated. These are the steps I took to make it work, but wanted to check with you all to see if this is the anticipated route (or talk about another route).

  1. I first created the seed credential named kube-aws-iam-controller

  2. I created an AWSIAMRole with the same name in the same namespace (in my case, kube-aws-iam-controller)

  3. When the controller sees the role, it tries to create credentials, but can't because:

    reason: 'CreateSecretFailed' Failed to create secret kube-system/kube-aws-iam-controller-iam-role with credentials: secrets \"kube-aws-iam-controller-iam-role\" already exists"
    
  4. Looking at the source, I found that you use a label named heritage with a value of kube-aws-iam-controller to know the secret is being managed by the controller. I added this label to the seed secret.

  5. The controller then sees the secret doesn't have an expire key, so auto-rotates the credential.

Since then, I seem to be in a stable state. But, I fully recognize labels aren't normally part of public API, so are subject to change. Is there another approach I should take or any recommendations on a route to "adopt an existing secret"?

Role which use of paths GetCredentialsFailed

I tried CRD method

apiVersion: amazonaws.com/v1
kind: AWSIAMRole
metadata:
  name: alb-ingress-controller-role
spec:
  # The roleReference allows specifying an AWS IAM role name or arn
  # Possible values:
  #   "aws-iam-role-name"
  #   "arn:aws:iam::<account-id>:role/aws-iam-role-name"
  roleReference: "arn:aws:iam::XXXX:role/k8s/kube-system/alb-ingress-controller_role"

Error occurred GetCredentialsFailed in kube-aws-iam-controller.

time="2019-05-09T11:55:57Z" level=info msg="Event(v1.ObjectReference{Kind:\"AWSIAMRole\", Namespace:\"kube-system\", Name:\"alb-ingress-controller-role\", UID:\"ef0dadf2-724c-11e9-85ed-0a61e5f811aa\", APIVersion:\"amazonaws.com/v1\", ResourceVersion:\"20473920\", FieldPath:\"\"}): type: 'Warning' reason: 'GetCredentialsFailed' Failed to get creedentials for role 'arn:aws:iam::XXXX:role/k8s/kube-system/alb-ingress-controller_role': invalid roleARN: arn:aws:iam::XXXX:role/k8s/kube-system/alb-ingress-controller_role"

When I tried on a Role that does not include path, no error occurred.

kube-aws-iam-controller deployment:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "6"
  creationTimestamp: 2019-05-09T11:08:42Z
  generation: 6
  labels:
    app: kube-aws-iam-controller
    chart: kube-aws-iam-controller-0.0.1
    heritage: Tiller
    release: kube-aws-iam-controller
  name: kube-aws-iam-controller
  namespace: operator
  resourceVersion: "20474580"
  selfLink: /apis/extensions/v1beta1/namespaces/operator/deployments/kube-aws-iam-controller
  uid: ce0bd319-724a-11e9-85ed-0a61e5f811aa
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: kube-aws-iam-controller
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: kube-aws-iam-controller
    spec:
      containers:
      - args:
        - --interval=10s
        - --refresh-limit=10m
        command:
        - /kube-aws-iam-controller
        image: mikkeloscar/kube-aws-iam-controller:v0.0.6
        imagePullPolicy: IfNotPresent
        name: kube-aws-iam-controller
        resources:
          limits:
            cpu: 100m
            memory: 64Mi
          requests:
            cpu: 25m
            memory: 64Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      nodeSelector:
        node-role.kubernetes.io/master: ""
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: kube-aws-iam-controller
      serviceAccountName: kube-aws-iam-controller
      terminationGracePeriodSeconds: 30
      tolerations:
      - effect: NoSchedule
        operator: Exists
      - effect: NoExecute
        operator: Exists
      - key: CriticalAddonsOnly
        operator: Exists
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: 2019-05-09T11:11:30Z
    lastUpdateTime: 2019-05-09T11:11:30Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2019-05-09T11:08:42Z
    lastUpdateTime: 2019-05-09T11:48:43Z
    message: ReplicaSet "kube-aws-iam-controller-7c8b58d7df" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 6
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1

In this document,

    // Use the role session name to uniquely identify a session when the same role
    // is assumed by different principals or for different reasons. In cross-account
    // scenarios, the role session name is visible to, and can be logged by the
    // account that owns the role. The role session name is also used in the ARN
    // of the assumed role principal.

I don't care much about it, butI tried to patch code and then no error with role include path.

cw-sakamoto@451af42
cw-sakamoto@43c035c

Allow controller to assume an intermediate IAM role at startup

The vaguely similar kiam controller allows use of what it calls (confusingly) a "Server role"—an IAM role that the controller assumes when it starts, and which other application-scoped IAM roles must mention in a trust relationship in order for the controller to be able to assume them on behalf of requesting pods.

By offering this feature, application IAM role authors don't have to trust the IAM role initially held by the controller pod, which is likely to be the IAM role of the EC2 instance on which the controller pod is running. These instance-level roles are often established per cluster, if not per machine, and are neither sufficient abstract nor stable for application authors to reference in their trust relationships.

Consider emulating this feature here, introducing an optional command-line flag that allows specifying this intermediate "pod delegate" IAM role. If specified, the controller would immediately attempt to assume this role when it starts.

Adding this would require augmenting the documentation to show a more complete example of the following:

  • The initial (machine-level) IAM role must allow assumption of the intermediate "pod delegate" role.
  • The intermediate "pod delegate" role must trust the initial (machine-level) role.
  • The intermediate "pod delegate" role must allow assumption of either all roles, or some narrower subset that encompasses what the cluster operators will allow.
  • An application author's IAM role must trust the intermediate "pod delegate" role.

With this in place, application authors never need to know about these initial (machine-level) roles.

Embedding role identifier in secret name precludes use of paths

At present, kube-aws-iam-controller allows pod authors to nominate an IAM role by way of appending that role's name to a sentinel prefix, forming a Kubernetes Secret name. kube-aws-iam-controller forms the role's complete ARN by concatenating either a designated or discovered base ARN with the suffix of the Secret name. Since Kubernetes Secret names can't contain forward slash characters, users can't designate IAM roles with paths that contain such slashes.

It's possible for a user to specify an IAM role ARN prefix—including a path—by using the --base-role-arn command-line flag. If all the roles nominated by pods happen to all live under that same path, the current concatenation scheme does allow forming role ARNs with paths. However, if pod authors will want to nominate IAM roles under different paths, they can't embed those paths in Secret names.

kiam doesn't have this problem because the annotation value it reads for nominating an IAM role ARN has no syntactic constraints. Secret names—and all Kubernetes object names—are too limited for this purpose.

Consider an IAM role with an ARN like arn:aws:iam::123456789012:role/division/department/team/ReadAssets. We can discover the base role ARN as arn:aws:iam::123456789012:role, and we can include the role name "ReadAssets" in a Secret name ("aws-iam-ReadAssets"), but concatenating the two parts gives us the wrong ARN: arn:aws:iam::123456789012:role/ReadAssets. Against this, we can't name our Secret "aws-iam-role/division/department/team/ReadAssets."

If we specified a --base-role-arn flag value of "arn:aws:iam::123456789012:role/division/department/team/" then we could name our Secret "aws-iam-ReadAssets" and get the right ARN for that role, but another pod author couldn't then nominate a role with an ARN under a different path like arn:aws:iam::123456789012:role/division/project/mint/CreateMoney.

The project documentation currently bears this promise or threat:

This way of specifying the role on pod specs are subject to change.

I read the rest of the paragraph as a concern for the pod authors' convenience, but there's also this gap in capability. I do like the current design, so perhaps we could augment it just enough by accepting an optional annotation on the pods to designate an ARN path. In the example above, the Secret name would be "aws-iam-ReadAssets," and the yet-to-be-named annotation value would be "division/department/team." IAM does have this odd identifier model, where the object names are global within the containing AWS account, yet the ARNs can include this path, and I don't think that you can omit a path from an ARN.

Roles also have unique IDs that a Secret name could accommodate syntactically. Unfortunately, the STS AssumeRoleInput type doesn't accept such a unique ID in lieu of a role ARN.

Have you considered this problem before? What do you think about the proposed optional annotation for specifying an ARN path?

Connect ServiceAccounts and AWS IAM roles

Currently the controller expects the AWS IAM roles to be created separately and available when pods are created. It would be nice to connect the AWS IAM role with a Kubernetes serviceAccounts and use the native service accounts as the only identity needed to be specified by a pod.

The way it would work is that users would specify a serviceAccount like this:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-application
  annotations:
    iam.amazonaws.com/role-policy: |
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": "route53:*",
            "Resource": "*",
            "Effect": "Allow"
          }
        ]
      }

The iam.amazonaws.com/role-policy would include an AWS IAM role policy for which the controller would automatically provision a AWS IAM role and provide it to the pod who specified the given serviceAccount in the pod spec.

This would not only keep the AWS identity and the Kubernetes identity connected it would also make things simpler for the user, e.g. they would not need to specify a trust relationship on the AWS IAM roles they are creating, as it would be done transparently.

In cases where an AWS IAM role already exists, it could just connect it to a serviceAccount like this:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-application
  annotations:
    iam.amazonaws.com/role: my-role-name

.NET SDK support?

We are a .NET Core shop and looking at migrating workloads from static json creds files on GCP? GKE to AWS/EKS. Working on possible solutions for credential handling for EKS. This project looks great but the lack of specific documentation and support for the AWS .NET SDK has me wondering if this can work. Any guidance or knowledge on the topic? Thanks!

ValidationError session-name

When I tried to check CRD merged in #13 , I got the following error.

time="2019-05-07T02:31:53Z" level=error msg="Failed to get credentials for role kube-fluentd-operator-role: ValidationError: 1 validation error detected: Value 'arn_aws_iam__XXXXXX_role.kube-fluentd-operator-role-session' at 'roleSessionName' failed to satisfy constraint: Member must have length less than or equal to 64\n\tstatus code: 400, request id: 4690bf63-7070-11e9-bc91-XXXXXX"

Deprecate pod discovery

Deprecate and later remove the feature of automatically discovering IAM roles by looking at pod with a secret mount named aws-iam-*

The feature is very limited and has been replaced by #13

Incomplete setup documentation

I have tried to seutp kube-aws-iam-controller as documented but have not been able to do so even after several attempts. At first instance I got error when applying the deployment that kube-aws-iam-controller service account not found. I explicitly created service-account and granted clusterwide role to get, list and watch secrets (which is undocumented). After this the deployment succeeds but I get the following error from the pod:

level=error msg="secrets is forbidden: User \"system:serviceaccount:kube-system:kube-aws-iam-controller\" cannot list resource \"secrets\" in API group \"\" at the cluster scope"

Controller is vulnerable to being a confused deputy (without an external ID)

In the current design, the controller creates a Kubernetes Secret object when it notices one or more pods that wish to mount a Secret with the right name. Any pods within that namespace can mount that same Secret object, regardless of governing RBAC permissions, and any pods in any namespace can assume whichever IAM roles trust the controller's initial role. That's a broad entitlement for role delegation.

In kiam, there's a way to limit which IAM roles pod authors can assume within a namespace, but even that doesn't go far enough: It allows the Kubernetes administrator some control over IAM role assumption, but it does not help the IAM roles' owners limit their trust scope. The IAM roles' owners must trust kiam or kube-aws-aim-controller for all the pods in an entire Kubernetes cluster; essentially, the role owners must trust an entire cluster. That's too broad of a scope with a multi-tenant cluster, where an IAM role owner might trust only a namespace, or only particular pods within the namespace.

By forcing the IAM role owners to trust the entire cluster, kube-aws-iam-controller becomes a confused deputy. Pod authors in one namespace may be able to learn the names of Secret objects in another namespace—as some multi-tenant clusters allow inter-namespace observation, if not mutation—and once they have that role name, they too can use it, even if the given IAM role owner only intended pods for one tenant to be able to assume it.

One way around this problem as prescribed by AWS is to use an External ID. When assuming the IAM role nominated by the Secret name suffix, the assuming STS client will include an external ID specific to the requesting party—in this case, a Kubernetes pod—that the IAM role owner can assess in his trust policy for the role being assumed.

Here, kube-aws-iam-controller could supply three pieces of information to the IAM role owner:

  • pod name
  • containing namespace
  • cluster ID (optional)

With these three pieces, an IAM role owner could limit the scope of his trust, down from many Kubernetes clusters—in the case where kube-aws-iam-controller's initial assumed role is shared among clusters—to a single cluster, further down to a single namespace within a cluster, and even further down to a prefix name of the pods within a namespace.

The external ID syntax would need to preclude false aliasing. We know that neither namespace names nor pod names can contain a forward slash, but we have no idea what constitutes a cluster ID, so we can construct the external ID as

namespace name / pod name [ / cluster ID ]

treating the suffix as optional, only included if kube-aws-iam-controller is configured with a nonempty cluster ID. It may be useful to forbid a cluster ID to include a forward slash character ('/').

With this external ID include in its calls to sts:AssumeRole, kube-aws-iam-controller would allow the IAM role owner to include conditions in its trust policy, using the string condition operators to match expected values:

Constraint Condition
Within namespace {"StringLike": {"sts:ExternalId": "trusted-ns/*"}}
Pods with generated name {"StringLike": {"sts:ExternalId": "trusted-ns/server-*"}}
Within cluster {"StringLike": {"sts:ExternalId": "*/*/cluster-1"}}
Within specific pod {"StringLike": {"sts:ExternalId": "trusted-ns/server-0/cluster-1"}}

Note that due to the limited capability of the StringLike operator, the "Within cluster" example above is vulnerable to false aliasing if a cluster ID can contain forward slashes, assuming that the StringLike operator uses greedy matching for wildcards. Likewise, it is difficult to match a pod name prefix of, say, "server-" when another set of pods could use the prefix "server-impostor-."

Some refinement is possible for the external ID format, but I hope this makes the idea clear enough: Without an external ID, kube-aws-iam-controller's trust scope is too broad for some environments, and AWS already has a way of narrowing such a scope.

The main problem I see here is that Kubernetes Secret names are scoped to the containing namespace, so kube-aws-iam-controller can't easily include a pod name in an external ID; any number of pods in the same namespace could mount the Secret, so long as the first pod's projected external ID satisfies the assumed IAM role's trust criteria. Trying to create a Secret that only some pods within a namespace could make use of is complicated, if not impossible. (Perhaps a validating admission control plugin could handle that, though an IAM role's trust criteria could change after the pod is created.) Given that, omitting the pod name and using just the namespace name and optional cluster ID (with the format reduced to namespace name / [ cluster ID ]) would still be an improvement.

I'd like to hear your thoughts on the proposal. In the meantime, thank you for your work on the project!

Support for all AWS partitions

Currently the controller hard codes the arnPrefix making it not possible to use it within environments such as AWS GovCloud where the ARN prefix differs: e.g. arn:aws-us-gov:iam::.

I am happy to create a patch but wondered how you would want this implemented as I see two directions:

  • User provided via a flag making it the operators duty to toggle the configuration in alternative partitions
  • Parsed from the user provided or autodiscovered base role ARN

Let me know if you have any thoughts or ideas regarding the implementation details.

Thanks for the great project!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.