kubernetes-sigs / external-dns

Configure external DNS servers (AWS Route53, Google CloudDNS and others) for Kubernetes Ingresses and Services
Apache License 2.0
7.65k stars 2.56k forks source link

AWS cross account access with OIDC provider in EKS & externalDNS not working #1608

Closed michazt closed 3 years ago

michazt commented 4 years ago

Hello to everyone,

I hope this is the right place to ask my question. If not please point me to the correct place or documentation.

We are in the process of setting up an AWS EKS cluster that uses the OIDC provider for IAM access control to AWS resources. So far this works fine for the ALB ingresss controler. Now we want to also add externalDNS to create DNS records in Route53 for zones hosted in another AWS account.

We have a MGMT AWS account (ID: 222233334444) that is hosting the Route53 DNS zones and a test stage AWS account (ID: 999988887777) where we run the EKS cluster. Now we want to setup externalDNS for a cross account access to add/change the DNS entries hosted in the MGMT account. See below for the config files and error message we get.

Please let me know if I missed something or you need more information.

Michael

====== config details ====== When I try to setup externalDNS I get the following error:

deployment.apps/external-dns created
serviceaccount/external-dns created
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created

kubectl logs -n kube-system external-dns-XYZ
time="2020-05-28T16:14:23Z" level=info msg="config: {Master: KubeConfig: RequestTimeout:30s IstioIngressGatewayServices:[] ContourLoadBalancerService:heptio-contour/contour SkipperRouteGroupVersion:zalando.org/v1 Sources:[service ingress] Namespace: AnnotationFilter: FQDNTemplate: CombineFQDNAndAnnotation:false IgnoreHostnameAnnotation:false Compatibility: PublishInternal:false PublishHostIP:false AlwaysPublishNotReadyAddresses:false ConnectorSourceServer:localhost:8080 Provider:aws GoogleProject: GoogleBatchChangeSize:1000 GoogleBatchChangeInterval:1s DomainFilter:[test.local] ExcludeDomains:[] ZoneIDFilter:[] AlibabaCloudConfigFile:/etc/kubernetes/alibaba-cloud.json AlibabaCloudZoneType: AWSZoneType:private AWSZoneTagFilter:[] AWSAssumeRole:\"arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest\" AWSBatchChangeSize:1000 AWSBatchChangeInterval:1s AWSEvaluateTargetHealth:true AWSAPIRetries:3 AWSPreferCNAME:false AzureConfigFile:/etc/kubernetes/azure.json AzureResourceGroup: AzureSubscriptionID: AzureUserAssignedIdentityClientID: CloudflareProxied:false CloudflareZonesPerPage:50 CoreDNSPrefix:/skydns/ RcodezeroTXTEncrypt:false AkamaiServiceConsumerDomain: AkamaiClientToken: AkamaiClientSecret: AkamaiAccessToken: InfobloxGridHost: InfobloxWapiPort:443 InfobloxWapiUsername:admin InfobloxWapiPassword: InfobloxWapiVersion:2.3.1 InfobloxSSLVerify:true InfobloxView: InfobloxMaxResults:0 DynCustomerName: DynUsername: DynPassword: DynMinTTLSeconds:0 OCIConfigFile:/etc/kubernetes/oci.yaml InMemoryZones:[] OVHEndpoint:ovh-eu PDNSServer:http://localhost:8081 PDNSAPIKey: PDNSTLSEnabled:false TLSCA: TLSClientCert: TLSClientCertKey: Policy:upsert-only Registry:txt TXTOwnerID:\"test account\" TXTPrefix: Interval:1m0s Once:false DryRun:false UpdateEvents:false LogFormat:text MetricsAddress::7979 LogLevel:debug TXTCacheInterval:0s ExoscaleEndpoint:https://api.exoscale.ch/dns ExoscaleAPIKey: ExoscaleAPISecret: CRDSourceAPIVersion:externaldns.k8s.io/v1alpha1 CRDSourceKind:DNSEndpoint ServiceTypeFilter:[] CFAPIEndpoint: CFUsername: CFPassword: RFC2136Host: RFC2136Port:0 RFC2136Zone: RFC2136Insecure:false RFC2136TSIGKeyName: RFC2136TSIGSecret: RFC2136TSIGSecretAlg: RFC2136TAXFR:false RFC2136MinTTL:0s NS1Endpoint: NS1IgnoreSSL:false TransIPAccountName: TransIPPrivateKeyFile:}"
time="2020-05-28T16:14:23Z" level=info msg="Instantiating new Kubernetes client"
time="2020-05-28T16:14:23Z" level=debug msg="kubeMaster: "
time="2020-05-28T16:14:23Z" level=debug msg="kubeConfig: "
time="2020-05-28T16:14:23Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2020-05-28T16:14:23Z" level=info msg="Created Kubernetes client https://172.20.0.1:443"
time="2020-05-28T16:14:25Z" level=info msg="Assuming role: \"arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest\""
time="2020-05-28T16:14:26Z" level=error msg="AccessDenied: User: arn:aws:sts::999988887777:assumed-role/externalDNSEKSrole/${ID} is not authorized to perform: sts:AssumeRole on resource: \"arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest\"\n\tstatus code: 403, request id: ${REQUESt_ID}"

IAM Role for the test account:


Trust relationships
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::999988887777:oidc-provider/oidc.eks.eu-central-1.amazonaws.com/id/${OIDC_ID}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.eu-central-1.amazonaws.com/id/${OIDC_ID}:sub": "system:serviceaccount:kube-system:external-dns",
          "oidc.eks.eu-central-1.amazonaws.com/id/${OIDC_ID}:aud": "sts.amazonaws.com"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999988887777:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Attached IAM policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": [
                "arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest
            ]
        }
    ]
}

IAM Role for the MGMT account:


Trust relationships
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999988887777:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}

Attached IAM policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Kubernetes YAML configuration for externalDNS:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: kube-system
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      automountServiceAccountToken: true
      containers:
      - name: external-dns
        image: registry.opensource.zalan.do/teapot/external-dns:v0.7.1
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=test.local
        - --provider=aws
        - --aws-assume-role="arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest"
        - --policy=upsert-only
        - --aws-zone-type=private
        - --registry=txt
        - --txt-owner-id="test account"
        - --log-level=debug
      securityContext:
        fsGroup: 65534
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: kube-system
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::999988887777:role/externalDNSEKSrole
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: kube-system
aidan-melen commented 4 years ago

yes, I have run into the exact same issue. all roles are correctly configured for cross-account trust; however, the external-dns operators fails to assume the trusting role.

michazt commented 4 years ago

A short update on this: we also tested it with the latest release with the same error messages.

Can anyone tell me if this is a bug or we are doing something wrong? For me it looks like a bug but maybe I am mistaken. And if it is a bug, I will of course file a bug report.

THX for any hint on this because it is preventing us from using externalDNS. Which would be very helpful if we could use it in our Setup.

linki commented 4 years ago

I would be interested in how ALB ingress controller handles this. We can probably do the same here.

michazt commented 4 years ago

@linki the ALB basically has the same IAM setup. The difference between the ALB ingress controller and externalDNS is that externalDNS needs to do a cross account access to create the DNS record. The resources the ALB creates are in the same AWS account.

From the various documentations and hints I have read externalDNS can do a cross account access or can use the OIDC provider. But I never found something where people use both. That's why I think there is a problem with the combination cross account access and OIDC provider. Or we simply do not understand how to set this up.

As AWS is promoting the OIDC provider for EKS more and more people will use this to authenticate a Kubernetes Cluster against AWS IAM. And I think it will be helpful for others to have a working example in the docs for this.

djschnei21 commented 4 years ago

Same issue here!

richrepko commented 4 years ago

Same issue here as well. Have yet to find a configuration that supports cross account hosted zones.

rust84 commented 4 years ago

I tried following https://aws.amazon.com/blogs/containers/cross-account-iam-roles-for-kubernetes-service-accounts/

  1. MAIN ACCOUNT
data "aws_iam_policy_document" "externaldns-assume-role-policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::$ACCOUNT2_ID:root"]
    }
  }

  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::$ACCOUNT2_ID:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/$ID"]
    }

    condition {
      test     = "StringEquals"
      variable = "oidc.eks.us-east-2.amazonaws.com/id/$ID:aud"

      values = [
        "sts.amazonaws.com",
      ]
    }
  }
}

data "aws_iam_policy_document" "externaldns" {
  statement {
    sid = "1"

    actions = [
      "route53:ChangeResourceRecordSets"
    ]

    resources = ["arn:aws:route53:::hostedzone/*"]
  }

  statement {
    sid = "2"

    actions = [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
    ]

    resources = ["*"]
  }
}
  1. EKS ACCOUNT
resource "aws_iam_policy" "external-dns-policy" {
  name_prefix = "external-dns"
  description = "Route53 policy for External DNS ${module.eks.cluster_id}"
  policy      = data.aws_iam_policy_document.external-dns.json
}

data "aws_iam_policy_document" "external-dns" {
  statement {
    sid = "1"

    actions = [
      "sts:AssumeRole"
    ]

    resources = ["arn:aws:iam::$MAIN_ACCOUNT_ID:role/external-dns"]
  }
}

And I get:

time="2020-07-20T16:04:10Z" level=error msg="WebIdentityErr: failed to retrieve credentials\ncaused by: InvalidIdentityToken: No OpenIDConnect provider found in your  │
│ account for https://oidc.eks.us-east-2.amazonaws.com/id/$ID\n\tstatus code: 400, request id: xxx-xxx-xxx-xxx"
aidan-melen commented 4 years ago

FYI related to https://github.com/kubernetes-sigs/external-dns/pull/524

michazt commented 4 years ago

@aidan-melen THX for the info but what does this mean for us? I see this is a commit from 2018 that was merged in 2018. So for me this would mean we are doing something wrong if the AWS cross account access really works. Then I would like to know what is not correct in the described setup above? And what would be a working example? A side note: with the setup from above I can use the AWS CLI with "aws sts get-caller-identity" successfully. So from my perspective we have a correct AWS IAM setup and I am really lost here.

aidan-melen commented 4 years ago

I was going to jump into the GO code this weekend if I have time.

michazt commented 4 years ago

@aidan-melen ah now I see that you have committed some code for this problem. I missed your entry at the end. Thanks a lot and I hope that it will fix our problem. As I am not a programmer I am not helpful at coding but willing to test it on our setup. And I will write some documentation when it works.

linki commented 4 years ago

I tested it in our setup and it seems to work fine. Here's what I did.

Let's assume we want to run ExternalDNS in account A managing records in account B. We want to use the IAM role in account A arn:aws:iam::A:role/external-dns to assume the IAM role in account B arn:aws:iam::B:role/external-dns-cross-account.

Head over to account B (the target account) and create the IAM role you want to assume. In the following this target IAM role is denoted with the ARN arn:aws:iam::B:role/external-dns-cross-account.

Then go back to AWS account A and annotate ExternalDNS' ServiceAccount in cluster A with:

eks.amazonaws.com/role-arn: arn:aws:iam::A:role/external-dns

Note, this should already be the case in your setup.

Then start ExternalDNS in cluster A with:

- --aws-assume-role=arn:aws:iam::B:role/external-dns-cross-account

For the IAM role for ExternalDNS in cluster A, add the permission to assume the role in account B:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::B:role/external-dns-cross-account"
        }
    ]
}

Normally this role would have Route53 permissions to manage DNS records in account A but that wasn't needed. For the OIDC stuff to work you need to keep the Trust Relationship to the OIDC provider and the StringsEqual stuff. This should already be there and can be left unchanged.

Then go to the IAM role in account B that you created at the beginning and that you want to assume. As a reminder this is denoted as ARN arn:aws:iam::B:role/external-dns-cross-account.

Give it permission to manage DNS records just like the IAM role for ExternalDNS in account A normally has, e.g.:

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

Then add a Trust Relation to the IAM role in account A with:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::A:role/external-dns"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Note that I removed the Trust Relationship to The identity provider(s) ec2.amazonaws.com as well. It works either way and I guess it's better to just keep it then.

Note, the IAM role in account B can be a "plain" IAM role. It must have permissions to modify DNS records as well as a Trust Relationship back to account A. If you want to assume another ServiceAccount's IAM role, such as ExternalDNS' role in a different account, then the role will also have the Trust Relationship to the OIDC provider. Both cases worked fine for me.

In my setup the target account has its own Kubernetes cluster with its own OIDC provider. I'm certain that this doesn't interfere with the experiment but who knows. Unfortunately, I can't delete it at the moment without causing some disruption.

To summarize you basically need to:

This was tested with v0.7.1 and v0.7.2.

Let me know if that helped you or if you still get stuck.

rust84 commented 4 years ago

Thanks @linki @aidan-melen the --aws-assume-role parameter was the piece I was missing. For anyone else who is using the Bitnami helm chart this can be set with .Values.aws.assumeRoleArn.

Annotated the service account with the role on account A which grants assume role permission for the account B role which is granting route53 access. Set --aws-assume-role to the role on account B.

The trust policy on account B is set to the root user on account A. I didn't have to use the Federated StringsEqual part, which might not be optimal. I could probably change that over once all clusters are using OIDC but some of them aren't yet I'm leaving it as is for now.

    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::$ACCOUNT_ID:root"]
    }

It's working perfectly. Thanks very much.

michazt commented 4 years ago

@linki THX for this example. I will try this next week and let you know if it succeeded. This week I am busy with other stuff. I think that I am also missing the --aws-assume-role=arn:aws:iam::B:role/external-dns-cross-account start parameter. Sounds promising.

djschnei21 commented 4 years ago

@rust84 or @linki any chance one of you can paste your working deployment yaml here??? We're definitely missing something simple... still can't get our pod which is using an OIDC provider to assume a cross account role.

ericrdgz commented 4 years ago

@linki we are facing similar issues. Trying to follow your guidance. How are you creating your IAM Roles. CLI, Terraform, or Console? We are using Terraform. Also are you on EKS non-fargate or EKS Fargate?

hetpats commented 4 years ago

IS there a way to pass External-Id for assuming the role in cross account?

michazt commented 4 years ago

So far we had no time to test this. Hopefully a colleague of mine will have time soon to test it.

mgrecar commented 4 years ago

I followed these directions to set up an identity provider and attach it to the role (following pretty much the same steps for the policy and role in the docs), and I was able to get cross-account ExternalDNS working for me. The ExternalDNS service account gets deployed with the annotation pointing to that role and it just works for us. Just needed to rollout the deployment again after making the update.

yves-vogl commented 4 years ago

To me it looks like the author of this issue did set up the trust relationship incorrectly. When using OIDC with an annotated service account (which linkes to an IAM role) a pod runs under a temporarly session which impersonates the role as its identity. Therefore this particular role should be trusted.

Also the error messages points out that the initial role has no permission to assume the cross-account role (which is required beside the trust relationship on the target role).

yves-vogl commented 4 years ago

What @linki says. That's how I do this (and did yesterday the last time) and how it's supposed to work.

chapitos commented 3 years ago

To add my 5 cents here.

I have done exactly the same steps as @linki described and run into the problem anyway (access denied, cannot assume role, as above). The cause was that I have provided the role ARN to be assumed between quotation marks (e.g. --aws-assume-role="arn:aws:iam::123456789012:role/role-to-be-assumed"). So basically, there is an error in args in the comment above, which cause the problem @michazt reported.

After removing the quotation masks, everything works fine.

michazt commented 3 years ago

I closed this issue because we resolved it somehow. We could not resolve the original problem described by me here but we did the latest approach recommended by AWS described here: https://aws.amazon.com/premiumsupport/knowledge-center/amazon-eks-cluster-access/ With this starting new every things works perfectly with externalDNS doing a cross account Route53 access. Unfortunately we could not find out what was the problem in our other setup but my guess is that we did something wrong in the trust relationship setup and it has nothing to do with externalDNS.

FriedCircuits commented 3 years ago

Just in case anyone comes across this issue trying to do the same thing. I found a way easier way to do this. Following this guide I was able to get it to work with ODIC and without needing roles on the AWS account with the EKS Cluster. See: https://aws.amazon.com/blogs/containers/cross-account-iam-roles-for-kubernetes-service-accounts/

I have it completely working in terraform with 2 copies of external-dns running, one for each account. You don't even need to use the assume role option in external-dns.

jurgen-weber-deltatre commented 2 years ago

To add my 5 cents here.

I have done exactly the same steps as @linki described and run into the problem anyway (access denied, cannot assume role, as above). The cause was that I have provided the role ARN to be assumed between quotation marks (e.g. --aws-assume-role="arn:aws:iam::123456789012:role/role-to-be-assumed"). So basically, there is an error in args in the comment above, which cause the problem @michazt reported.

After removing the quotation masks, everything works fine.

we just spent 4 hours on this and this was the issue.

maciejtulaza commented 2 years ago

In my case I had to remove this part, because if it was non-empty it seemed to skip IRSA mechanism and ignored annotation placed on the K8s service account - --aws-assume-role="arn:aws:iam::222233334444:role/externalDNSAccessRoute53CrossAccountTest"

I deploy external-dns with chart version 5.4.15, app 0.10.1. I am deploying using Helm. Specifically I had to remove Helm param: aws.assumeRoleArn but leave serviceAccount.annotations.eks.amazonaws...

At the end, if I describe the external-dns Deployment object, it doesnt have any ARN set in any param. ARN of the role is set only on the service account

vainkop commented 2 years ago

That doc pretty much covers it: https://aws.amazon.com/ru/blogs/containers/cross-account-iam-roles-for-kubernetes-service-accounts/

Pay attention to:

  1. Create an IAM OIDC provider in the shared_content account. The Provider URL corresponds to OpenID Connect provider URL from the EKS cluster in the developer account
ricardorqr commented 5 months ago

Hey guys,

I need help. I have been stuck on this problem for 3 days. I followed exactly what @linki said, but it is still not working. This is what I have.

Account A has deployed ExtrnalDNS and account B has the hosted zones.

Account A Settings

This is ExternalDNS account A role (arn:aws:iam::AAAAAAAAAA:role/external-dns-role) I deployed using Helm.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role"
            },
            "Action": [
                "sts:TagSession",
                "sts:AssumeRole"
            ]
        }
    ]
}

This is the annotation I added to my account A ExternalDNS ServiceAccount.

metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::AAAAAAAAAA:role/external-dns-role

This is the arg I added to my account A ExternalDNS Deployment.

spec:
  template:
    spec:
      containers:
        - name: external-dns
          args:
            - '--aws-assume-role=arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role'

Account B Settings

This is account B role (arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role) I created.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::AAAAAAAAAA:role/external-dns-role"
            },
            "Action": [
                "sts:TagSession",
                "sts:AssumeRole"
            ]
        }
    ]
}

This is the policy.

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

Lastly, This is the error I'm having.

time="2024-04-27T19:38:45Z" level=info msg="Instantiating new Kubernetes client"
time="2024-04-27T19:38:45Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2024-04-27T19:38:45Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2024-04-27T19:38:46Z" level=info msg="Assuming role: arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role"
time="2024-04-27T19:38:46Z" level=error msg="Failed to do run once: soft error\nrecords retrieval failed: soft error\nfailed to list hosted zones: WebIdentityErr: failed to retrieve credentials\ncaused by: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity\n\tstatus code: 403, request id: 906da8b2-f482-4bbe-abca-c34732c7e33c"

Can you guys help me with what I'm doing wrong?

vainkop commented 5 months ago

Can you guys help me with what I'm doing wrong?

@ricardorqr , do you have an IAM trust policy in place, allowing the specified Kubernetes service account to assume the IAM role? Your error mentions sts:AssumeRoleWithWebIdentity which isn't sts:AssumeRole

https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/

ricardorqr commented 5 months ago

Can you guys help me with what I'm doing wrong?

@ricardorqr , do you have an IAM trust policy in place, allowing the specified Kubernetes service account to assume the IAM role? Your error mentions sts:AssumeRoleWithWebIdentity which isn't sts:AssumeRole

https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/

This is one of my doubts. I understood by @linki example that I had to replace the old trust relationship role, which had sts:AssumeRoleWithWebIdentity with this new sts:AssumeRole role.

This is the old trust relationship role installed by the default deployment. It was working, but this account doesn't have the hosted zones. Only the account B has.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam:: AAAAAAAAAA:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.us-west-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16:sub": "system:serviceaccount:external-dns:external-dns",
                    "oidc.eks.us-west-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

And this is the policy.

{
    "Statement": [
        {
            "Action": "route53:ChangeResourceRecordSets",
            "Effect": "Allow",
            "Resource": "arn:aws:route53:::hostedzone/*"
        },
        {
            "Action": [
                "route53:ListTagsForResource",
                "route53:ListResourceRecordSets",
                "route53:ListHostedZones"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ],
    "Version": "2012-10-17"
}
ricardorqr commented 5 months ago

Hey @vainkop, I updated ExternalDNS account A trust relationship role (arn:aws:iam::AAAAAAAAAA:role/external-dns-role) to add both statements: sts:AssumeRoleWithWebIdentity and sts:AssumeRole. But it is still not working.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::AAAAAAAAAA:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.us-east-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16:sub": "system:serviceaccount:external-dns:external-dns",
                    "oidc.eks.us-east-2.amazonaws.com/id/5FB9E51C0F08ELKJHLJH7B25462C7A16:aud": "sts.amazonaws.com"
                }
            }
        },
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

I had a different error log:

time="2024-04-28T22:45:57Z" level=info msg="Instantiating new Kubernetes client"
time="2024-04-28T22:45:57Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2024-04-28T22:45:57Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2024-04-28T22:45:57Z" level=info msg="Assuming role: arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role"
time="2024-04-28T22:45:57Z" level=error msg="Failed to do run once: soft error\nrecords retrieval failed: soft error\nfailed to list hosted zones: AccessDenied: User: arn:aws:sts::AAAAAAAAAA:assumed-role/external-dns-role/1714344357614679364 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role\n\tstatus code: 403, request id: d491acf0-e06b-4f97-942c-df84feaf0ec1"
ricardorqr commented 5 months ago

The problem has been fixed. I just attached a second policy to the ExternalDNS account A role arn:aws:iam::AAAAAAAAAA:role/external-dns-role. Policy:

{
    "Statement": [
        {
            "Action": [
                "sts:AssumeRole"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:iam::BBBBBBBBBB:role/external-dns-cross-accounts-role"
            ],
            "Sid": "Statement1"
        }
    ],
    "Version": "2012-10-17"
}