diff --git a/gitops/addons/bootstrap/default/addons.yaml b/gitops/addons/bootstrap/default/addons.yaml index a7c1f98cd..53caee475 100644 --- a/gitops/addons/bootstrap/default/addons.yaml +++ b/gitops/addons/bootstrap/default/addons.yaml @@ -163,6 +163,40 @@ ack-s3: managedFieldsManagers: - kube-controller-manager +ack-rds: + enabled: false + enableACK: false + annotationsAppSet: + argocd.argoproj.io/sync-wave: '-1' # Needs to be after kro RGD + namespace: ack-system + chartName: rds-chart + defaultVersion: '1.7.6' + chartNamespace: aws-controllers-k8s + chartRepository: public.ecr.aws + releaseName: rds-chart + selector: + matchExpressions: + - key: enable_ack_rds + operator: In + values: ['true'] + valuesObject: + aws: + region: '{{.metadata.annotations.aws_region}}' + serviceAccount: + name: '{{default "ack-rds-controller" (index .metadata.annotations "ack_rds_service_account")}}' + ignoreDifferences: + - group: apiextensions.k8s.io + kind: CustomResourceDefinition + jsonPointers: + - /metadata/annotations/controller-gen.kubebuilder.io~1version + jqPathExpressions: + - '.spec.versions[].schema.openAPIV3Schema | .. | .generateName?' + - '.spec.versions[].schema.openAPIV3Schema | .. | .description?' + - group: apiextensions.k8s.io + kind: CustomResourceDefinition + managedFieldsManagers: + - kube-controller-manager + ack-dynamodb: enabled: false enableACK: false @@ -617,7 +651,7 @@ kro-manifests: path: '{{.metadata.annotations.addons_repo_basepath}}charts/kro/resource-groups/manifests' directory: recurse: true - exclude: '{cicd-pipeline/**,huggingface/**,ray-service/**}' + exclude: '{cicd-pipeline/**,huggingface/**,ray-service/**,rds/**}' selector: matchExpressions: - key: enable_kro_manifests @@ -1046,6 +1080,11 @@ devlake: valuesObject: clusterName: '{{.metadata.annotations.aws_cluster_name}}' clusterRegion: '{{.metadata.annotations.aws_region}}' + awsAccountId: '{{.metadata.annotations.aws_account_id}}' + resourcePrefix: '{{.metadata.annotations.resource_prefix}}' + rds: + privateSubnetIds: '{{.metadata.annotations.aws_subnet_ids}}' + vpcId: '{{.metadata.annotations.aws_vpc_id}}' devlake: grafana: enabled: false diff --git a/gitops/addons/charts/devlake/templates/external-secret.yaml b/gitops/addons/charts/devlake/templates/external-secret.yaml index 4c6048b26..c42865321 100644 --- a/gitops/addons/charts/devlake/templates/external-secret.yaml +++ b/gitops/addons/charts/devlake/templates/external-secret.yaml @@ -77,13 +77,13 @@ spec: match: secretKey: EXTERNAL_URL remoteRef: - remoteKey: peeks-devlake/mysql-connection + remoteKey: {{ .Values.clusterName }}/devlake-mysql-connection property: db_url - conversionStrategy: None match: secretKey: MYSQL_ROOT_PASSWORD remoteRef: - remoteKey: peeks-devlake/mysql-connection + remoteKey: {{ .Values.clusterName }}/devlake-mysql-connection property: root_password diff --git a/gitops/addons/charts/devlake/templates/iam-role-selectors.yaml b/gitops/addons/charts/devlake/templates/iam-role-selectors.yaml new file mode 100644 index 000000000..21896391d --- /dev/null +++ b/gitops/addons/charts/devlake/templates/iam-role-selectors.yaml @@ -0,0 +1,33 @@ +apiVersion: services.k8s.aws/v1alpha1 +kind: IAMRoleSelector +metadata: + name: {{ .Values.namespace | default "devlake" }}-ec2 + annotations: + argocd.argoproj.io/sync-wave: "-3" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + arn: arn:aws:iam::{{ .Values.awsAccountId }}:role/{{ .Values.resourcePrefix }}-cluster-mgmt-ec2 + namespaceSelector: + names: + - {{ .Values.namespace | default "devlake" }} + resourceTypeSelector: + - group: ec2.services.k8s.aws + kind: "" + version: "" +--- +apiVersion: services.k8s.aws/v1alpha1 +kind: IAMRoleSelector +metadata: + name: {{ .Values.namespace | default "devlake" }}-rds + annotations: + argocd.argoproj.io/sync-wave: "-3" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + arn: arn:aws:iam::{{ .Values.awsAccountId }}:role/{{ .Values.resourcePrefix }}-cluster-mgmt-rds + namespaceSelector: + names: + - {{ .Values.namespace | default "devlake" }} + resourceTypeSelector: + - group: rds.services.k8s.aws + kind: "" + version: "" diff --git a/gitops/addons/charts/devlake/templates/rds-ack.yaml b/gitops/addons/charts/devlake/templates/rds-ack.yaml new file mode 100644 index 000000000..b4b97647f --- /dev/null +++ b/gitops/addons/charts/devlake/templates/rds-ack.yaml @@ -0,0 +1,15 @@ +apiVersion: kro.run/v1alpha1 +kind: RelationalDatabase +metadata: + name: devlake-mysql + namespace: {{ .Values.namespace | default "devlake" }} + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + databaseName: devlake + engineVersion: {{ .Values.rds.engineVersion | default "8.0.mysql_aurora.3.10.0" }} + instanceClass: {{ .Values.rds.instanceClass | default "db.r5.large" }} + region: {{ .Values.clusterRegion }} + privateSubnetIds: {{ splitList "," .Values.rds.privateSubnetIds | toJson }} + vpcId: {{ .Values.rds.vpcId }} + createService: true diff --git a/gitops/addons/charts/devlake/templates/rds.yaml b/gitops/addons/charts/devlake/templates/rds.yaml deleted file mode 100644 index 2b1e74345..000000000 --- a/gitops/addons/charts/devlake/templates/rds.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: core.oam.dev/v1beta1 -kind: Application -metadata: - name: devlake-rds-mysql - namespace: {{ .Values.namespace | default "devlake" }} - annotations: - argocd.argoproj.io/sync-wave: "-2" - argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true -spec: - components: - - name: devlake-mysql-db - type: rds-cluster-mysql - properties: - deploy: {{ .Values.clusterName }} - traits: - - type: component-iam-policy - properties: - service: rds-db - policy: | - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "rds-db:connect" - ], - "Resource": "*" - } - ] - } - - name: devlake-mysql-access - type: dp-service-account - properties: - clusterName: {{ .Values.clusterName }} - clusterRegion: {{ .Values.clusterRegion }} - componentNamesForAccess: - - devlake-mysql-db diff --git a/gitops/addons/charts/devlake/templates/setup-workflow.yaml b/gitops/addons/charts/devlake/templates/setup-workflow.yaml index 61cc6e325..0f23ce5fd 100644 --- a/gitops/addons/charts/devlake/templates/setup-workflow.yaml +++ b/gitops/addons/charts/devlake/templates/setup-workflow.yaml @@ -44,7 +44,7 @@ spec: arguments: parameters: - name: connection-secret-namespace - value: "crossplane-system" + value: {{ .Values.namespace | default "devlake" }} - name: create-secrets template: create-secrets arguments: @@ -75,16 +75,16 @@ spec: command: ["/bin/sh", "-c"] args: - | - SECRET_NAME=$(kubectl get secrets -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o name | grep "devlake-mysql.*cluster-mysql-connection$" | head -1 | cut -d'/' -f2) + SECRET_NAME=$(kubectl get secrets -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o name | grep "devlake-mysql-credentials" | head -1 | cut -d'/' -f2) if [ -z "$SECRET_NAME" ]; then echo "Secret not found yet" exit 1 fi - # Check if password is available in the secret - endpoint=$(kubectl get secret ${SECRET_NAME} -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o jsonpath='{.data.endpoint}' 2>/dev/null | base64 -d 2>/dev/null || true) - password=$(kubectl get secret ${SECRET_NAME} -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o jsonpath='{.data.attribute\.master_password}' 2>/dev/null | base64 -d 2>/dev/null || true) + # Read credentials from RGD-created secret (keys: host, password) + endpoint=$(kubectl get secret ${SECRET_NAME} -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o jsonpath='{.data.host}' 2>/dev/null | base64 -d 2>/dev/null || true) + password=$(kubectl get secret ${SECRET_NAME} -n {{`{{inputs.parameters.connection-secret-namespace}}`}} -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true) if [ -z "$password" ] || [ -z "$endpoint" ]; then echo "Secret found but values not available yet" @@ -130,10 +130,6 @@ spec: merico_password=$(kubectl get secret mysql-passwords -n devlake -o jsonpath='{.data.DEVLAKE_MYSQL_PASSWORD}' | base64 -d) grafana_password=$(kubectl get secret mysql-passwords -n devlake -o jsonpath='{.data.GRAFANA_MYSQL_PASSWORD}' | base64 -d) - # Create service pointing to the RDS endpoint - kubectl delete service devlake-mysql-service -n devlake --ignore-not-found - kubectl create service externalname devlake-mysql-service --external-name="${endpoint}" -n devlake --dry-run=client -o yaml | kubectl apply -f - - # Create auth secret with credentials kubectl delete secret devlake-mysql-auth -n devlake --ignore-not-found kubectl create secret generic devlake-mysql-auth -n devlake \ diff --git a/gitops/addons/charts/devlake/templates/wait-for-crds.yaml b/gitops/addons/charts/devlake/templates/wait-for-crds.yaml deleted file mode 100644 index 4b69480c0..000000000 --- a/gitops/addons/charts/devlake/templates/wait-for-crds.yaml +++ /dev/null @@ -1,74 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: devlake-crd-waiter - namespace: {{ .Values.namespace | default "devlake" }} - annotations: - argocd.argoproj.io/sync-wave: "-6" ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: devlake-crd-reader - annotations: - argocd.argoproj.io/sync-wave: "-6" -rules: -- apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["get", "list"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: devlake-crd-waiter-binding - annotations: - argocd.argoproj.io/sync-wave: "-6" -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: devlake-crd-reader -subjects: -- kind: ServiceAccount - name: devlake-crd-waiter - namespace: {{ .Values.namespace | default "devlake" }} ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: devlake-wait-for-crds - annotations: - argocd.argoproj.io/sync-wave: "-5" - argocd.argoproj.io/hook: Sync - argocd.argoproj.io/hook-delete-policy: BeforeHookCreation -spec: - template: - spec: - serviceAccountName: devlake-crd-waiter - restartPolicy: OnFailure - containers: - - name: devlake-wait-crds - image: alpine/k8s:1.32.8 - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - memory: 256Mi - command: ["/bin/sh", "-c"] - args: - - | - echo "Waiting for CRDs to be available..." - - # List your required CRDs - CRDS="xrelationaldatabases.awsblueprints.io subnetgroups.rds.aws.upbound.io securitygroups.ec2.aws.upbound.io securitygroupingressrules.ec2.aws.upbound.io clusters.rds.aws.upbound.io clusterinstances.rds.aws.upbound.io policies.iam.aws.upbound.io podidentityassociations.eks.aws.upbound.io roles.iam.aws.upbound.io rolepolicyattachments.iam.aws.upbound.io" - - for crd in $CRDS; do - echo "Waiting for CRD: $crd" - until kubectl get crd $crd 2>/dev/null; do - echo "CRD $crd not ready, waiting..." - sleep 5 - done - echo "CRD $crd is ready" - done - - echo "All CRDs are available!" diff --git a/gitops/addons/charts/grafana-dashboards/templates/external-secret.yaml b/gitops/addons/charts/grafana-dashboards/templates/external-secret.yaml index 241a26844..9ed1c700e 100644 --- a/gitops/addons/charts/grafana-dashboards/templates/external-secret.yaml +++ b/gitops/addons/charts/grafana-dashboards/templates/external-secret.yaml @@ -75,5 +75,5 @@ spec: conversionStrategy: Default decodingStrategy: None metadataPolicy: None - key: peeks-devlake/mysql-connection + key: {{ .Values.clusterName }}/devlake-mysql-connection property: db_url diff --git a/gitops/addons/charts/kro/resource-groups/manifests/rds/rds-aurora-mysql-ack-rgd.yaml b/gitops/addons/charts/kro/resource-groups/manifests/rds/rds-aurora-mysql-ack-rgd.yaml new file mode 100644 index 000000000..26795207e --- /dev/null +++ b/gitops/addons/charts/kro/resource-groups/manifests/rds/rds-aurora-mysql-ack-rgd.yaml @@ -0,0 +1,205 @@ +apiVersion: kro.run/v1alpha1 +kind: ResourceGraphDefinition +metadata: + name: rds-aurora-mysql-ack.kro.run +spec: + schema: + apiVersion: v1alpha1 + kind: RelationalDatabase + spec: + databaseName: string + engineVersion: string | default="8.0.mysql_aurora.3.10.0" + instanceClass: string | default="db.r5.xlarge" + region: string + privateSubnetIds: "[]string" + vpcId: string + createService: boolean | default=false + resources: + - id: subnetgroup + readyWhen: + - ${subnetgroup.status.conditions.exists(c, c.type == 'ACK.ResourceSynced' && c.status == 'True')} + template: + apiVersion: rds.services.k8s.aws/v1alpha1 + kind: DBSubnetGroup + metadata: + name: ${schema.metadata.name}-subnet-group + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + annotations: + services.k8s.aws/region: ${schema.spec.region} + spec: + name: ${schema.metadata.name}-subnet-group + description: rds-mysql + subnetIDs: ${schema.spec.privateSubnetIds} + + - id: securitygroup + readyWhen: + - ${securitygroup.status.conditions.exists(c, c.type == 'ACK.ResourceSynced' && c.status == 'True')} + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: SecurityGroup + metadata: + name: ${schema.metadata.name}-sg + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + annotations: + services.k8s.aws/region: ${schema.spec.region} + spec: + name: rds-mysql-sg-${schema.metadata.uid} + description: rds-mysql-sg + vpcID: ${schema.spec.vpcId} + ingressRules: + - fromPort: 3306 + ipProtocol: tcp + toPort: 3306 + ipRanges: + - cidrIP: 10.0.0.0/8 + description: 'in vpc traffic' + + - id: cluster + readyWhen: + - ${cluster.status.conditions.exists(c, c.type == 'ACK.ResourceSynced' && c.status == 'True')} + template: + apiVersion: rds.services.k8s.aws/v1alpha1 + kind: DBCluster + metadata: + name: ${schema.metadata.name}-cluster + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + annotations: + services.k8s.aws/region: ${schema.spec.region} + spec: + dbClusterIdentifier: ${schema.metadata.name}-cluster + databaseName: ${schema.spec.databaseName} + engine: aurora-mysql + engineVersion: ${schema.spec.engineVersion} + masterUsername: root + manageMasterUserPassword: true + dbSubnetGroupName: ${subnetgroup.spec.name} + vpcSecurityGroupIDs: + - ${securitygroup.status.id} + backupRetentionPeriod: 7 + preferredBackupWindow: 02:00-03:00 + preferredMaintenanceWindow: sun:04:00-sun:05:00 + storageEncrypted: true + + - id: clusterinstance + readyWhen: + - ${clusterinstance.status.conditions.exists(c, c.type == 'ACK.ResourceSynced' && c.status == 'True')} + template: + apiVersion: rds.services.k8s.aws/v1alpha1 + kind: DBInstance + metadata: + name: ${schema.metadata.name}-instance + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + annotations: + services.k8s.aws/region: ${schema.spec.region} + spec: + dbInstanceIdentifier: ${schema.metadata.name}-instance + dbInstanceClass: ${schema.spec.instanceClass} + engine: aurora-mysql + dbClusterIdentifier: ${cluster.spec.dbClusterIdentifier} + dbSubnetGroupName: ${subnetgroup.spec.name} + + - id: externalsecret + readyWhen: + - ${externalsecret.status.conditions.exists(c, c.type == 'Ready' && c.status == 'True')} + template: + apiVersion: external-secrets.io/v1 + kind: ExternalSecret + metadata: + name: ${schema.metadata.name}-credentials + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + spec: + refreshInterval: 1h + secretStoreRef: + name: aws-secrets-manager + kind: ClusterSecretStore + target: + name: ${schema.metadata.name}-credentials + template: + data: + username: "{{ .username }}" + password: "{{ .password }}" + host: "${cluster.status.endpoint}" + data: + - secretKey: username + remoteRef: + key: ${cluster.status.masterUserSecret.secretARN} + property: username + - secretKey: password + remoteRef: + key: ${cluster.status.masterUserSecret.secretARN} + property: password + + - id: configmap + readyWhen: + - ${configmap.metadata.name != ""} + template: + apiVersion: v1 + kind: ConfigMap + metadata: + name: ${schema.metadata.name}-config + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + data: + DB_PORT: "3306" + DB_NAME: ${schema.spec.databaseName} + DB_ENDPOINT: ${cluster.status.endpoint} + + - id: service + includeWhen: + - ${schema.spec.createService} + readyWhen: + - ${service.metadata.name != ""} + template: + apiVersion: v1 + kind: Service + metadata: + name: ${schema.metadata.name}-service + ownerReferences: + - apiVersion: kro.run/v1alpha1 + kind: RelationalDatabase + name: ${schema.metadata.name} + uid: ${schema.metadata.uid} + blockOwnerDeletion: true + controller: false + spec: + type: ExternalName + externalName: ${cluster.status.endpoint} + ports: + - port: 3306 + targetPort: 3306 + protocol: TCP diff --git a/gitops/addons/charts/multi-acct/templates/iam-role-selectors.yaml b/gitops/addons/charts/multi-acct/templates/iam-role-selectors.yaml index 43e44761e..d22e694a0 100644 --- a/gitops/addons/charts/multi-acct/templates/iam-role-selectors.yaml +++ b/gitops/addons/charts/multi-acct/templates/iam-role-selectors.yaml @@ -98,3 +98,18 @@ spec: kind: "" version: "" {{- end }} +--- +apiVersion: services.k8s.aws/v1alpha1 +kind: IAMRoleSelector +metadata: + name: rds-{{ $key }} +spec: + arn: "arn:aws:iam::{{ $value }}:role/{{ $.Values.global.resourcePrefix | default "peeks" }}-cluster-mgmt-rds" + namespaceSelector: + names: + - {{ $key }} + resourceTypeSelector: + - group: rds.services.k8s.aws + kind: "" + version: "" +--- diff --git a/gitops/addons/environments/control-plane/addons.yaml b/gitops/addons/environments/control-plane/addons.yaml index 438d62036..47099ba73 100644 --- a/gitops/addons/environments/control-plane/addons.yaml +++ b/gitops/addons/environments/control-plane/addons.yaml @@ -44,6 +44,8 @@ ack-ecr: enabled: true ack-efs: enabled: true +ack-rds: + enabled: true ack-s3: enabled: true ack-dynamodb: diff --git a/platform/infra/terraform/common/locals.tf b/platform/infra/terraform/common/locals.tf index 4c91acc3c..cffe536e3 100644 --- a/platform/infra/terraform/common/locals.tf +++ b/platform/infra/terraform/common/locals.tf @@ -20,6 +20,7 @@ locals { hub_subnet_ids = data.aws_eks_cluster.clusters[local.hub_cluster_key].vpc_config[0].subnet_ids spoke_clusters = { for k, v in var.clusters : k => v if v.environment != "control-plane" } cluster_vpc_ids = { for k, v in var.clusters : v.name => data.aws_eks_cluster.clusters[k].vpc_config[0].vpc_id } + cluster_subnet_ids = { for k, v in var.clusters : v.name => data.aws_eks_cluster.clusters[k].vpc_config[0].subnet_ids } cluster_oidc_issuers = { for k, v in var.clusters : v.name => replace(data.aws_eks_cluster.clusters[k].identity[0].oidc[0].issuer, "https://", "") } argocd_namespace = "argocd" ingress_name = { for k, v in var.clusters : v.name => "${v.name}-ingress" } @@ -130,6 +131,7 @@ locals { aws_account_id = data.aws_caller_identity.current.account_id eks_oidc_issuer = local.cluster_oidc_issuers[v.name] aws_vpc_id = local.cluster_vpc_ids[v.name] + aws_subnet_ids = join(",", local.cluster_subnet_ids[v.name]) aws_grafana_url = module.managed_grafana.workspace_endpoint resource_prefix = var.resource_prefix # Extract EKS Auto Mode node role name from cluster compute config diff --git a/platform/infra/terraform/common/pod-identity.tf b/platform/infra/terraform/common/pod-identity.tf index 45f1c25a2..ff9982213 100644 --- a/platform/infra/terraform/common/pod-identity.tf +++ b/platform/infra/terraform/common/pod-identity.tf @@ -35,7 +35,7 @@ module "external_secrets_pod_identity" { attach_external_secrets_policy = true external_secrets_kms_key_arns = ["arn:aws:kms:${each.value.region}:*:key/${each.value.name}/*"] - external_secrets_secrets_manager_arns = ["arn:aws:secretsmanager:${each.value.region}:*:secret:${local.context_prefix}*"] + external_secrets_secrets_manager_arns = ["arn:aws:secretsmanager:${each.value.region}:*:secret:${local.context_prefix}*", "arn:aws:secretsmanager:${each.value.region}:*:secret:rds!cluster*"] external_secrets_ssm_parameter_arns = ["arn:aws:ssm:${each.value.region}:*:parameter/${each.value.name}/*"] external_secrets_create_permission = each.value.environment == "control-plane" ? true : false #only for hub attach_custom_policy = each.value.environment == "control-plane" ? true : false #only for hub @@ -290,8 +290,8 @@ data "http" "inline_policy" { # Create locals for ACK cluster-service combinations locals { # Centralized list of ACK services - ack_services = ["iam", "ec2", "eks", "ecr", "s3", "dynamodb", "secretsmanager"] - + ack_services = ["iam", "ec2", "eks", "ecr", "s3", "dynamodb", "rds", "secretsmanager"] + ack_combinations = { for combination in flatten([ for cluster_key, cluster_value in var.clusters : [ @@ -420,6 +420,11 @@ locals { dynamodb = [ "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" ] + rds = [ + "arn:aws:iam::aws:policy/AmazonRDSFullAccess", + "arn:aws:iam::aws:policy/SecretsManagerReadWrite", + "arn:aws:iam::aws:policy/AWSKeyManagementServicePowerUser" + ] secretsmanager = [ "arn:aws:iam::aws:policy/SecretsManagerReadWrite" ]