|
| 1 | +--- |
| 2 | +id: cloud-iam |
| 3 | +title: Cloud IAM |
| 4 | +--- |
| 5 | + |
| 6 | +Sometimes applications will need to integrate with other cloud resources as they require things like persistent data storage. When working with XKS each namespace is accompanied by a Azure resource |
| 7 | +group or a AWS account. This is where cloud resources can be created by each tenant. To keep things simple it may be a good idea to not share these resources across multiple tenants, as one of the |
| 8 | +tenants has to own the resource. Instead look at other options like exposing an API inside the cluster instead. As ony may expect the authentication methods differ when running XKS in Azure and AWS. |
| 9 | +This is because the APIs and underlying authentication methods differ greatly. It is important to take this into consideration when reading these documentation. |
| 10 | + |
| 11 | +## Cloud Providers |
| 12 | + |
| 13 | +### Azure |
| 14 | + |
| 15 | +The reccomended way to authenticate towards Azure in XKS is to make use of [AAD Pod Identity](https://github.com/Azure/aad-pod-identity) which runs inside the cluster. AAD Pod Identity allows Pods |
| 16 | +within the cluster to use [managed identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to authenticate towards Azure. This removes the need |
| 17 | +for static credentials that have to be passed to the Pods. It works by intercepting API requests before the leave the cluster and will attach the correct credential based on the Pod source of the |
| 18 | + request. |
| 19 | + |
| 20 | +Each tenant namespace comes preconfigured with an [AzureIdentity](https://azure.github.io/aad-pod-identity/docs/concepts/azureidentity/) and |
| 21 | +[AzureIdentityBinding](https://azure.github.io/aad-pod-identity/docs/concepts/azureidentitybinding/). These have been setup so that the identity has access to the tenants resource group. All that has |
| 22 | +to be done to enable the managed identity is to add the label `foo` to the Pod. The preconfigured AzureIdentity has a labelselector which expects the label to have the same value as the namespace |
| 23 | +name. |
| 24 | + |
| 25 | +This example will deploy a Pod with the Azure CLI so that you can test access to the Azure API. |
| 26 | + |
| 27 | +```yaml |
| 28 | +apiVersion: v1 |
| 29 | +kind: Pod |
| 30 | +metadata: |
| 31 | + name: msi-test |
| 32 | + namespace: tenant |
| 33 | + labels: |
| 34 | + aadpodidbinding: ${NAMESPACE_NAME} |
| 35 | +spec: |
| 36 | + containers: |
| 37 | + - name: msi-test |
| 38 | + image: mcr.microsoft.com/azure-cli |
| 39 | + tty: true |
| 40 | + volumeMounts: |
| 41 | + - name: az-cli |
| 42 | + mountPath: /root/.azure |
| 43 | + volumes: |
| 44 | + - name: az-cli |
| 45 | + emptyDir: {} |
| 46 | + |
| 47 | +``` |
| 48 | + |
| 49 | +After the Pod has started you can execute a shell in the Pod and verify that the managed identity is working. |
| 50 | + |
| 51 | +```bash |
| 52 | +kubectl -n tenant exec -it msi-test |
| 53 | +az login --identity |
| 54 | +az account show |
| 55 | +``` |
| 56 | + |
| 57 | +#### SDK |
| 58 | + |
| 59 | +A common scenario is that an application may need API access to an Azure resources through the API. In these cases the best solution is to use the language specific SDKs which will most of the time |
| 60 | +support MSI credentials. Below are examples for how to create a client using MSI credentials that can interact with Azure storage account blobs. |
| 61 | + |
| 62 | +<!-- markdownlint-disable --> |
| 63 | +** Golang ** |
| 64 | + |
| 65 | +```go |
| 66 | +package main |
| 67 | + |
| 68 | +import ( |
| 69 | + "time" |
| 70 | + |
| 71 | + "github.com/Azure/go-autorest/autorest/azure/auth" |
| 72 | + blob "github.com/Azure/azure-storage-blob-go/azblob" |
| 73 | +) |
| 74 | + |
| 75 | +func main() { |
| 76 | + msiConfig := auth.NewMSIConfig() |
| 77 | + |
| 78 | + spt, err := msiConfig.ServicePrincipalToken() |
| 79 | + if err != nil { |
| 80 | + return nil, err |
| 81 | + } |
| 82 | + if err := spt.Refresh(); err != nil { |
| 83 | + return nil, err |
| 84 | + } |
| 85 | + |
| 86 | + tc, err := blob.NewTokenCredential(spt.Token().AccessToken, func(tc blob.TokenCredential) time.Duration { |
| 87 | + err := spt.Refresh() |
| 88 | + if err != nil { |
| 89 | + return 30 * time.Second |
| 90 | + } |
| 91 | + tc.SetToken(spt.Token().AccessToken) |
| 92 | + return spt.Token().Expires().Sub(time.Now().Add(2 * time.Minute)) |
| 93 | + }), nil |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +** C# ** |
| 98 | + |
| 99 | +```aspnet |
| 100 | +using Azure; |
| 101 | +using Azure.Identity; |
| 102 | +using Azure.Storage.Blobs; |
| 103 | +
|
| 104 | +async static Task CreateBlockBlobAsync(string accountName, string containerName, string blobName) |
| 105 | +{ |
| 106 | + string containerEndpoint = string.Format("https://{0}.blob.core.windows.net/{1}", |
| 107 | + accountName, |
| 108 | + containerName); |
| 109 | + BlobContainerClient containerClient = new BlobContainerClient(new Uri(containerEndpoint), |
| 110 | + new DefaultAzureCredential()); |
| 111 | +} |
| 112 | +``` |
| 113 | +<!-- markdownlint-restore --> |
| 114 | + |
| 115 | +#### Limiting Permissions |
| 116 | + |
| 117 | +TBD |
| 118 | + |
| 119 | +### AWS |
| 120 | + |
| 121 | +When authenticating towards AWS in XKS we recommend using [IAM Roles for Service Accounts](https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-enable-IAM.html) (IRSA). IRSA |
| 122 | +works by intercepting AWS API calls before leaving the cluster and appending the correct authentication token to the request. This removes the need to static security credentials as it is handled |
| 123 | +outside the app. IRSA works by annotating a Service Account with a reference to a specfic AWS IAM role. When that Service Account is attacthed to a Pod, the Pod will be able to assume the IAM role. |
| 124 | +The reason IRSA works in a multi tenant cluster is because the reference is multi directional. The Service Account has to specify the full role ARN it wants to assume and the IAM role has to specify |
| 125 | +the name and namespace of the Service Account whihc is allowed to assume the role. So it is not enough to know the ARN of the role unless you have access to the correct namespace and Service Account. |
| 126 | + |
| 127 | +Start by defining a variable for the OIDC URLs that need to be trusted. Currently this is a static definition that needs to be specified but work is planned to make this value discoverable in the |
| 128 | +future. |
| 129 | + |
| 130 | +```hcl |
| 131 | +variable "oidc_urls" { |
| 132 | + description = "List of EKS OIDC URLs to trust." |
| 133 | + type = list(string) |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +A new OIDC provider has to be created for each trusted URL. The simplest way to do this is to iterate the URL list. This should only be done once per tenant account, so try to define all roles in the |
| 138 | +same Terraform state. |
| 139 | + |
| 140 | +```hcl |
| 141 | +data "tls_certificate" "this" { |
| 142 | + for_each = { |
| 143 | + for v in var.oidc_urls : |
| 144 | + v => v |
| 145 | + } |
| 146 | + url = each.value |
| 147 | +} |
| 148 | +
|
| 149 | +resource "aws_iam_openid_connect_provider" "this" { |
| 150 | + for_each = { |
| 151 | + for v in var.oidc_urls : |
| 152 | + v => v |
| 153 | + } |
| 154 | + client_id_list = ["sts.amazonaws.com"] |
| 155 | + thumbprint_list = [data.tls_certificate.this[each.value].certificates[0].sha1_fingerprint] |
| 156 | + url = each.value |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +Define an AWS IAM policy document and an instance of the [IRSA Terraform module](https://github.com/XenitAB/terraform-modules/tree/main/modules/aws/irsa). The policy docuemnt describes which |
| 161 | +permissions should be granted to a Pod and the IRSA module creates the IAM policy and role for a Service Account in a specific namespace. The example below will for example only work with a Service |
| 162 | +Account called `irsa-test` in the namespace `tenant`. Keep in mind that a policy document and module instance is required for each unique permission set. |
| 163 | + |
| 164 | +```hcl |
| 165 | +data "aws_iam_policy_document" "get_login_profile" { |
| 166 | + statement { |
| 167 | + effect = "Allow" |
| 168 | + actions = [ |
| 169 | + "iam:GetLoginProfile", |
| 170 | + ] |
| 171 | + resources = ["*"] |
| 172 | + } |
| 173 | +} |
| 174 | +
|
| 175 | +module "irsa_test" { |
| 176 | + source = "github.com/xenitab/terraform-modules//modules/aws/irsa?ref=2021.08.9" |
| 177 | +
|
| 178 | + name = "irsa-test" |
| 179 | + oidc_providers = [ |
| 180 | + for v in var.oidc_urls : |
| 181 | + { |
| 182 | + url = v |
| 183 | + arn = aws_iam_openid_connect_provider.this[v].arn |
| 184 | + } |
| 185 | + ] |
| 186 | + kubernetes_namespace = "tenant" |
| 187 | + kubernetes_service_account = "irsa-test" |
| 188 | + policy_json = data.aws_iam_policy_document.get_login_profile.json |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +It is a good idea to output the arn of the creted role, as it will be needed in the next step. |
| 193 | + |
| 194 | +```hcl |
| 195 | +output "irsa_test_arn" { |
| 196 | + value = module.irsa_test.role_arn |
| 197 | +} |
| 198 | +``` |
| 199 | + |
| 200 | +The correct IAM roles and policies should be created after the Terraform has been applied. The next step is to create a Service Account with the same name as specied in the IRSA module and annotate it |
| 201 | +with the key `eks.amazonaws.com/role-arn` where the value should be the full ARN of the created IAM role, note that the account id is part of the ARN as the IAM role is created in a different account |
| 202 | +than the one the cluster is located in. |
| 203 | + |
| 204 | +```yaml |
| 205 | +apiVersion: v1 |
| 206 | +kind: ServiceAccount |
| 207 | +metadata: |
| 208 | + name: irsa-test |
| 209 | + namespace: tenant |
| 210 | + annotations: |
| 211 | + eks.amazonaws.com/role-arn: arn:aws:iam::111111111111:role/irsa-test |
| 212 | +``` |
| 213 | +
|
| 214 | +Create a Pod using the newly created Service Account to test using the IAM role. |
| 215 | +
|
| 216 | +```yaml |
| 217 | +apiVersion: v1 |
| 218 | +kind: Pod |
| 219 | +metadata: |
| 220 | + name: irsa-test |
| 221 | + namespace: tenant |
| 222 | +spec: |
| 223 | + serviceAccountName: irsa-test |
| 224 | + containers: |
| 225 | + - name: irsa-test |
| 226 | + image: amazon/aws-cli |
| 227 | + command: ["sh"] |
| 228 | + stdin: true |
| 229 | + tty: true |
| 230 | +``` |
| 231 | +
|
| 232 | +After the Pod has started you can execute a shell in the Pod and verify that the managed identity is working. |
| 233 | +
|
| 234 | +```bash |
| 235 | +kubectl -n tenant exec -it irsa-test |
| 236 | +aws sts get-caller-identity |
| 237 | +``` |
| 238 | + |
| 239 | +#### SDK |
| 240 | + |
| 241 | +TBD |
0 commit comments