diff --git a/.circleci/config.yml b/.circleci/config.yml
index eb476826..536232b7 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -34,6 +34,7 @@ commands:
steps:
- terraform/install:
terraform_version: << parameters.terraform_version >>
+
install_hcledit:
description: "Install HCL edit"
parameters:
@@ -47,6 +48,7 @@ commands:
environment:
HCLEDIT_VERSION: << parameters.hcledit_version >>
command: bash ci-deploy.sh install_hcledit
+
set_mod_source_current:
description: "Set up module source to current branch."
steps:
@@ -54,6 +56,7 @@ commands:
name: Set module source to current branch
working_directory: tests/deploy
command: bash ci-deploy.sh set_mod_src_circle_branch
+
set_infra_imports:
description: "Set up root infra module imports."
steps:
@@ -61,6 +64,7 @@ commands:
name: Set root infra module imports
working_directory: tests/deploy
command: bash ci-deploy.sh set_infra_imports
+
pre_upgrade_updates:
description: "Updates necessary for upgrade."
steps:
@@ -68,6 +72,7 @@ commands:
name: Manual updates
working_directory: tests/deploy
command: bash ci-deploy.sh pre_upgrade_updates
+
set_cluster_imports:
description: "Set up root cluster module imports."
steps:
@@ -75,6 +80,7 @@ commands:
name: Set root cluster module imports
working_directory: tests/deploy
command: bash ci-deploy.sh set_cluster_imports
+
set_nodes_imports:
description: "Set up root nodes module imports."
steps:
@@ -82,6 +88,7 @@ commands:
name: Set root nodes module imports
working_directory: tests/deploy
command: bash ci-deploy.sh set_nodes_imports
+
set_mod_source_latest_rel:
description: "Set up module source to current branch"
steps:
@@ -89,12 +96,14 @@ commands:
name: Set module source to latest published release
working_directory: tests/deploy
command: bash ci-deploy.sh set_mod_src_latest_rel
+
set_aws_creds:
description: "Sets short-lived creds"
steps:
- aws-cli/setup:
role-arn: "${AWS_IAM_ROLE}"
session-duration: "900"
+
set_tf_vars:
description: "Sets Terraform variables"
steps:
@@ -105,14 +114,16 @@ commands:
name: Bootstrap terraform vars
working_directory: tests/deploy
command: bash ci-deploy.sh set_tf_vars
- set_tf_mods:
+
+ setup_tf_mods:
description: "Bootstrap modules using the CI branch"
steps:
- run:
name: Bootstrap terraform module using the CI branch
working_directory: tests/deploy
command: bash ci-deploy.sh setup_modules_ci_branch
- set_tf_mods_latest_rel:
+
+ setup_tf_mods_latest_rel:
description: "Bootstrap modules using latest release"
steps:
- run:
@@ -120,6 +131,14 @@ commands:
working_directory: tests/deploy
command: bash ci-deploy.sh setup_modules_latest_rel
+ setup_tf_mods_upgrade:
+ description: "Upgrade existing modules using current branch"
+ steps:
+ - run:
+ name: Bootstrap terraform module using latest release
+ working_directory: tests/deploy
+ command: bash ci-deploy.sh setup_modules_upgrade
+
install_helm:
description: "Install Helm"
parameters:
@@ -132,6 +151,7 @@ commands:
environment:
HELM_VERSION: << parameters.helm_version >>
command: bash ci-deploy.sh install_helm
+
tf_init_apply:
description: "Terraform Init, Validate, Apply"
steps:
@@ -139,6 +159,7 @@ commands:
name: Terraform init/validate/apply
working_directory: tests/deploy
command: bash ci-deploy.sh deploy
+
tf_deploy_infra:
description: "Terraform deploy Infra"
steps:
@@ -146,6 +167,7 @@ commands:
name: Terraform deploy Infra
working_directory: tests/deploy
command: bash ci-deploy.sh deploy_infra
+
tf_deploy_cluster:
description: "Terraform deploy Cluster"
steps:
@@ -153,6 +175,7 @@ commands:
name: Terraform deploy Cluster
working_directory: tests/deploy
command: bash ci-deploy.sh deploy_cluster
+
tf_deploy_nodes:
description: "Terraform deploy Nodes"
steps:
@@ -160,6 +183,7 @@ commands:
name: Terraform deploy Nodes
working_directory: tests/deploy
command: bash ci-deploy.sh deploy_nodes
+
tf_deploy_single_node:
description: "Terraform deploy single-node"
steps:
@@ -171,6 +195,7 @@ commands:
name: Deploy single-node
working_directory: tests/deploy
command: bash ci-deploy.sh deploy_single_node
+
tf_destroy_single_node:
description: "Terraform destroy single-node"
steps:
@@ -178,6 +203,7 @@ commands:
name: Destroy single-node
working_directory: tests/deploy
command: bash ci-deploy.sh destroy_single_node
+
tf_deploy:
description: "Terraform deploy"
steps:
@@ -187,6 +213,7 @@ commands:
- tf_deploy_infra
- tf_deploy_cluster
- tf_deploy_nodes
+
tf_destroy:
description: "Terraform destroy"
steps:
@@ -195,6 +222,7 @@ commands:
working_directory: tests/deploy
command: bash ci-deploy.sh destroy
when: always
+
tf_plan_test:
steps:
- set_aws_creds
@@ -202,6 +230,23 @@ commands:
name: Terraform plan test
working_directory: tests/plan
command: bash tf-plan-test.sh
+
+ store_deploy_artifacts:
+ parameters:
+ path:
+ type: string
+ default: deploy
+ steps:
+ - run:
+ name: Store artifacts
+ when: always
+ command: |
+ mkdir -p /tmp/artifacts/<< parameters.path >>/{cluster,infra,nodes}
+ cp tests/deploy/deploy-test/terraform/{cluster,infra,nodes}.tfvars /tmp/artifacts/<< parameters.path >>/
+ cp tests/deploy/deploy-test/terraform/cluster/main.tf /tmp/artifacts/<< parameters.path >>/cluster/
+ cp tests/deploy/deploy-test/terraform/infra/main.tf /tmp/artifacts/<< parameters.path >>/infra/
+ cp tests/deploy/deploy-test/terraform/nodes/main.tf /tmp/artifacts/<< parameters.path >>/nodes/
+
jobs:
tf-plan-test:
docker:
@@ -214,6 +259,7 @@ jobs:
- install_tf:
terraform_version: << parameters.terraform_version >>
- tf_plan_test
+
test-deploy:
docker:
- image: cimg/aws:2023.04.1
@@ -228,13 +274,17 @@ jobs:
terraform_version: << parameters.terraform_version >>
- install_helm:
helm_version: << parameters.helm_version >>
- - set_tf_mods
+ - setup_tf_mods
- set_tf_vars
- set_mod_source_current
- tf_deploy
+ - store_deploy_artifacts
- tf_deploy_single_node
- tf_destroy_single_node
- tf_destroy
+ - store_artifacts:
+ path: /tmp/artifacts/
+
test-upgrade:
docker:
- image: cimg/aws:2023.04.1
@@ -251,17 +301,24 @@ jobs:
terraform_version: << parameters.terraform_version >>
- install_helm:
helm_version: << parameters.helm_version >>
- - set_tf_mods_latest_rel
+ - setup_tf_mods_latest_rel
- set_tf_vars
- set_mod_source_latest_rel
- tf_deploy
+ - store_deploy_artifacts
+ - setup_tf_mods_upgrade
+ - set_tf_vars
- set_mod_source_current
- pre_upgrade_updates
- set_infra_imports
- set_cluster_imports
- set_nodes_imports
- tf_deploy
+ - store_deploy_artifacts:
+ path: upgrade
- tf_destroy
+ - store_artifacts:
+ path: /tmp/artifacts/
workflows:
test-deploy-workflow:
@@ -272,6 +329,7 @@ workflows:
context: aws-oidc
terraform_version: << pipeline.parameters.terraform_version >>
helm_version: << pipeline.parameters.helm_version >>
+
test-upgrade-workflow:
when:
equal: ["test-upgrade-workflow", << pipeline.parameters.GHA_Action >>]
@@ -281,6 +339,7 @@ workflows:
terraform_version: << pipeline.parameters.terraform_version >>
helm_version: << pipeline.parameters.helm_version >>
hcledit_version: << pipeline.parameters.hcledit_version >>
+
examples-plan-test-workflow:
when:
equal:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8bb8e0c6..bd934b50 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,5 @@
## NOTE: Changes(rename/add/delete) to pre-commit ids need to be replicated in .github/workflows/terraform-checks.yml(GHA).
+default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
@@ -61,7 +62,7 @@ repos:
args:
- "--args=--compact"
- "--args=--quiet"
- - "--args=--skip-check CKV_CIRCLECIPIPELINES_2,CKV_CIRCLECIPIPELINES_6,CKV2_AWS_11,CKV2_AWS_12,CKV2_AWS_6,CKV_AWS_109,CKV_AWS_111,CKV_AWS_135,CKV_AWS_144,CKV_AWS_145,CKV_AWS_158,CKV_AWS_18,CKV_AWS_184,CKV_AWS_19,CKV_AWS_21,CKV_AWS_66,CKV_AWS_88,CKV2_GHA_1,CKV_AWS_163,CKV_AWS_39,CKV_AWS_38,CKV2_AWS_61,CKV2_AWS_62,CKV_AWS_136,CKV_AWS_329,CKV_AWS_338,CKV_AWS_339,CKV_AWS_341,CKV_AWS_356,CKV2_AWS_19,CKV2_AWS_5,CKV_AWS_150,CKV_AWS_123,CKV2_AWS_65,CKV2_AWS_67,CKV2_AWS_57,CKV_AWS_149,CKV_AWS_117,CKV_AWS_116,CKV_AWS_173,CKV_AWS_115,CKV_AWS_7,CKV_AWS_124"
+ - "--args=--skip-check CKV_CIRCLECIPIPELINES_2,CKV_CIRCLECIPIPELINES_6,CKV2_AWS_11,CKV2_AWS_12,CKV2_AWS_6,CKV_AWS_107,CKV_AWS_109,CKV_AWS_111,CKV_AWS_135,CKV_AWS_144,CKV_AWS_145,CKV_AWS_158,CKV_AWS_18,CKV_AWS_184,CKV_AWS_19,CKV_AWS_21,CKV_AWS_66,CKV_AWS_88,CKV2_GHA_1,CKV_AWS_163,CKV_AWS_39,CKV_AWS_38,CKV2_AWS_61,CKV2_AWS_62,CKV_AWS_136,CKV_AWS_329,CKV_AWS_338,CKV_AWS_339,CKV_AWS_341,CKV_AWS_356,CKV2_AWS_19,CKV2_AWS_5,CKV_AWS_150,CKV_AWS_123,CKV2_AWS_65,CKV2_AWS_67,CKV2_AWS_57,CKV_AWS_149,CKV_AWS_117,CKV_AWS_116,CKV_AWS_173,CKV_AWS_115,CKV_AWS_7,CKV_AWS_124"
- id: terraform_trivy
args:
- "--args=--severity=HIGH,CRITICAL"
diff --git a/examples/deploy/meta.sh b/examples/deploy/meta.sh
index a69d87f5..1b32f105 100644
--- a/examples/deploy/meta.sh
+++ b/examples/deploy/meta.sh
@@ -11,13 +11,13 @@ declare -a MOD_DIRS=(
declare -A COMP_MODS
COMP_MODS["infra"]="infra"
-COMP_MODS["cluster"]="eks irsa_external_dns irsa_policies irsa_external_deployments_operator"
+COMP_MODS["cluster"]="eks irsa_external_dns irsa_policies external_deployments_operator"
COMP_MODS["nodes"]="nodes"
declare -A MOD_ADD
MOD_ADD["irsa_external_dns"]="irsa"
MOD_ADD["irsa_policies"]="irsa"
-MOD_ADD["irsa_external_deployments_operator"]="irsa"
+MOD_ADD["external_deployments_operator"]="external-deployments"
INFRA_DIR="${MOD_DIRS[0]}"
CLUSTER_DIR="${MOD_DIRS[1]}"
diff --git a/examples/deploy/terraform/cluster/README.md b/examples/deploy/terraform/cluster/README.md
index f69575e8..6b06a3f9 100644
--- a/examples/deploy/terraform/cluster/README.md
+++ b/examples/deploy/terraform/cluster/README.md
@@ -21,7 +21,7 @@
| Name | Source | Version |
|------|--------|---------|
| [eks](#module\_eks) | ./../../../../modules/eks | n/a |
-| [irsa\_external\_deployments\_operator](#module\_irsa\_external\_deployments\_operator) | ./../../../../modules/irsa | n/a |
+| [external\_deployments\_operator](#module\_external\_deployments\_operator) | ./../../../../modules/external-deployments | n/a |
| [irsa\_external\_dns](#module\_irsa\_external\_dns) | ./../../../../modules/irsa | n/a |
| [irsa\_policies](#module\_irsa\_policies) | ./../../../../modules/irsa | n/a |
@@ -38,7 +38,7 @@
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [eks](#input\_eks) | service\_ipv4\_cidr = CIDR for EKS cluster kubernetes\_network\_config.
creation\_role\_name = Name of the role to import.
k8s\_version = EKS cluster k8s version.
kubeconfig = {
extra\_args = Optional extra args when generating kubeconfig.
path = Fully qualified path name to write the kubeconfig file.
}
public\_access = {
enabled = Enable EKS API public endpoint.
cidrs = List of CIDR ranges permitted for accessing the EKS public endpoint.
}
Custom role maps for aws auth configmap
custom\_role\_maps = {
rolearn = string
username = string
groups = list(string)
}
master\_role\_names = IAM role names to be added as masters in eks.
cluster\_addons = EKS cluster addons. vpc-cni is installed separately.
vpc\_cni = Configuration for AWS VPC CNI
ssm\_log\_group\_name = CloudWatch log group to send the SSM session logs to.
identity\_providers = Configuration for IDP(Identity Provider).
} |
object({| `{}` | no | -| [irsa\_external\_deployments\_operator](#input\_irsa\_external\_deployments\_operator) | Config to create IRSA role for the external deployments operator. |
service_ipv4_cidr = optional(string)
creation_role_name = optional(string, null)
k8s_version = optional(string)
kubeconfig = optional(object({
extra_args = optional(string)
path = optional(string)
}), {})
public_access = optional(object({
enabled = optional(bool)
cidrs = optional(list(string))
}), {})
custom_role_maps = optional(list(object({
rolearn = string
username = string
groups = list(string)
})))
master_role_names = optional(list(string))
cluster_addons = optional(list(string))
ssm_log_group_name = optional(string)
vpc_cni = optional(object({
prefix_delegation = optional(bool)
annotate_pod_ip = optional(bool)
}))
identity_providers = optional(list(object({
client_id = string
groups_claim = optional(string)
groups_prefix = optional(string)
identity_provider_config_name = string
issuer_url = optional(string)
required_claims = optional(string)
username_claim = optional(string)
username_prefix = optional(string)
})))
})
object({| `{}` | no | +| [external\_deployments\_operator](#input\_external\_deployments\_operator) | Config to create IRSA role for the external deployments operator. |
enabled = optional(bool, false)
namespace = optional(string, "domino-compute")
service_account_name = optional(string, "pham-juno-operator")
})
object({| `{}` | no | | [irsa\_external\_dns](#input\_irsa\_external\_dns) | Mappings for custom IRSA configurations. |
enabled = optional(bool, false)
namespace = optional(string, "domino-compute")
operator_service_account_name = optional(string, "pham-juno-operator")
operator_role_suffix = optional(string, "external-deployments-operator")
repository_suffix = optional(string, "external-deployments")
bucket_suffix = optional(string, "external-deployments")
enable_assume_any_external_role = optional(bool, true)
enable_in_account_deployments = optional(bool, true)
})
object({| `{}` | no | | [irsa\_policies](#input\_irsa\_policies) | Mappings for custom IRSA configurations. |
enabled = optional(bool, false)
hosted_zone_name = optional(string, null)
namespace = optional(string, null)
serviceaccount_name = optional(string, null)
rm_role_policy = optional(object({
remove = optional(bool, false)
detach_from_role = optional(bool, false)
policy_name = optional(string, "")
}), {})
})
list(object({| `[]` | no | | [kms\_info](#input\_kms\_info) | Overrides the KMS key information. Meant for migrated configurations.
name = string
namespace = string
serviceaccount_name = string
policy = string #json
}))
object({| `null` | no | @@ -49,7 +49,7 @@ | Name | Description | |------|-------------| | [eks](#output\_eks) | EKS details. | -| [external\_deployments\_operator](#output\_external\_deployments\_operator) | "External\_deployments\_operator info"
key_id = string
key_arn = string
enabled = bool
})
object({| n/a | yes | +| [external\_deployments](#input\_external\_deployments) | Config to create IRSA role for the external deployments operator. |
cluster = object({
specs = object({
name = string
account_id = string
})
oidc = object({
arn = string
url = string
cert = object({
thumbprint_list = list(string)
url = string
})
})
})
})
object({| `{}` | no | +| [kms\_info](#input\_kms\_info) | key\_id = KMS key id.
namespace = optional(string, "domino-compute")
operator_service_account_name = optional(string, "pham-juno-operator")
operator_role_suffix = optional(string, "external-deployments-operator")
repository_suffix = optional(string, "external-deployments")
bucket_suffix = optional(string, "external-deployments")
enable_assume_any_external_role = optional(bool, true)
enable_in_account_deployments = optional(bool, true)
})
object({| n/a | yes | +| [region](#input\_region) | AWS region for the deployment | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [eks](#output\_eks) | External deployments eks info | + diff --git a/modules/external-deployments/main.tf b/modules/external-deployments/main.tf new file mode 100644 index 00000000..eba398fa --- /dev/null +++ b/modules/external-deployments/main.tf @@ -0,0 +1,15 @@ +data "aws_partition" "current" {} + +locals { + deploy_id = var.eks_info.cluster.specs.name + oidc_provider_arn = var.eks_info.cluster.oidc.arn + oidc_provider_url = var.eks_info.cluster.oidc.cert.url + account_id = var.eks_info.cluster.specs.account_id + blobs_s3_bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${local.deploy_id}-blobs" + environments_repository = "${local.deploy_id}/environment" + repository = "${local.deploy_id}/${var.external_deployments.repository_suffix}" + bucket = "${local.deploy_id}-${var.external_deployments.bucket_suffix}" + operator_role = "${local.deploy_id}-${var.external_deployments.operator_role_suffix}" + operator_role_needs_policy = var.external_deployments.enable_in_account_deployments || var.external_deployments.enable_assume_any_external_role + region = var.region +} diff --git a/modules/external-deployments/operator_role.tf b/modules/external-deployments/operator_role.tf new file mode 100644 index 00000000..c3d53ea2 --- /dev/null +++ b/modules/external-deployments/operator_role.tf @@ -0,0 +1,64 @@ +data "aws_iam_policy_document" "service_account_assume_role" { + statement { + sid = "ServiceAccountAssumeRole" + actions = ["sts:AssumeRoleWithWebIdentity"] + effect = "Allow" + principals { + type = "Federated" + identifiers = [local.oidc_provider_arn] + } + condition { + test = "StringEquals" + variable = "${trimprefix(local.oidc_provider_url, "https://")}:aud" + values = ["sts.amazonaws.com"] + } + condition { + test = "StringEquals" + variable = "${trimprefix(local.oidc_provider_url, "https://")}:sub" + values = [ + "system:serviceaccount:${var.external_deployments.namespace}:${var.external_deployments.operator_service_account_name}" + ] + } + } +} +data "aws_iam_policy_document" "self_sagemaker_assume_role" { + statement { + sid = "SelfAssumeRole" + actions = ["sts:AssumeRole"] + effect = "Allow" + principals { + type = "AWS" + identifiers = [ + "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:root" + ] + } + condition { + test = "ArnLike" + variable = "aws:PrincipalArn" + values = [ + "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:role/${local.operator_role}" + ] + } + } + statement { + sid = "SagemakerAssumeRole" + actions = ["sts:AssumeRole"] + effect = "Allow" + principals { + type = "Service" + identifiers = ["sagemaker.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "operator_assume_role_policy" { + source_policy_documents = concat( + [data.aws_iam_policy_document.service_account_assume_role.json], + var.external_deployments.enable_in_account_deployments ? [data.aws_iam_policy_document.self_sagemaker_assume_role.json] : [] + ) +} + +resource "aws_iam_role" "operator" { + name = local.operator_role + assume_role_policy = data.aws_iam_policy_document.operator_assume_role_policy.json +} diff --git a/modules/external-deployments/operator_role_policies.tf b/modules/external-deployments/operator_role_policies.tf new file mode 100644 index 00000000..955f5d74 --- /dev/null +++ b/modules/external-deployments/operator_role_policies.tf @@ -0,0 +1,205 @@ +data "aws_iam_policy_document" "decrypt_blobs_kms" { + count = var.kms_info.enabled ? 1 : 0 + statement { + sid = "KmsDecryptDominoBlobs" + effect = "Allow" + actions = ["kms:Decrypt"] + resources = [var.kms_info.key_arn] + } +} + +data "aws_iam_policy_document" "in_account_policies" { + source_policy_documents = var.kms_info.enabled ? [data.aws_iam_policy_document.decrypt_blobs_kms[0].json] : [] + statement { + sid = "StsAllowSelfAssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + resources = [ + "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:role/${local.operator_role}" + ] + } + statement { + sid = "IamAllowPassRole" + effect = "Allow" + actions = [ + "iam:GetRole", + "iam:PassRole", + ] + resources = [ + "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:role/${local.operator_role}" + ] + } + statement { + sid = "EcrRegistrySpecificSagemakerEnvironments" + effect = "Allow" + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchDeleteImage", + "ecr:BatchGetImage", + "ecr:CreateRepository", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:TagResource", + "ecr:UploadLayerPart", + ] + resources = [ + "arn:${data.aws_partition.current.partition}:ecr:${local.region}:${local.account_id}:repository/${local.repository}", + "arn:${data.aws_partition.current.partition}:ecr:${local.region}:${local.account_id}:repository/${local.repository}*" + ] + } + statement { + sid = "EcrGlobalSagemakerEnvironments" + effect = "Allow" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:DescribeRegistry" + ] + resources = ["*"] + } + statement { + sid = "S3AccessDominoBlobs" + effect = "Allow" + actions = [ + "s3:GetObject", + "s3:ListBucket" + ] + resources = [ + local.blobs_s3_bucket_arn, + "${local.blobs_s3_bucket_arn}/*" + ] + } + statement { + sid = "MetricsForSagemaker" + effect = "Allow" + actions = ["cloudwatch:PutMetricData"] + resources = ["*"] + } + statement { + sid = "LogsForSagemaker" + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ] + resources = [ + "arn:${data.aws_partition.current.partition}:logs:${local.region}:${local.account_id}:log-group:/aws/sagemaker/*" + ] + } + statement { + sid = "SagemakerManageResources" + effect = "Allow" + actions = [ + "sagemaker:AddTags", + "sagemaker:CreateEndpoint", + "sagemaker:CreateEndpointConfig", + "sagemaker:CreateModel", + "sagemaker:DeleteEndpoint", + "sagemaker:DeleteEndpointConfig", + "sagemaker:DeleteModel", + "sagemaker:DescribeEndpoint", + "sagemaker:DescribeEndpointConfig", + "sagemaker:DescribeModel", + "sagemaker:InvokeEndpoint", + "sagemaker:UpdateEndpointWeightsAndCapacities" + ] + resources = ["*"] + } + statement { + sid = "AutoscalingForSagemaker" + effect = "Allow" + actions = [ + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DeregisterScalableTarget", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingActivities", + "application-autoscaling:DescribeScalingPolicies", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:RegisterScalableTarget", + "application-autoscaling:TagResource" + ] + resources = ["*"] + } + statement { + sid = "CloudwatchForAutoscaling" + effect = "Allow" + actions = [ + "cloudwatch:PutMetricAlarm", + "cloudwatch:DeleteAlarms", + "cloudwatch:DescribeAlarms" + ] + resources = [ + "*" + ] + } + statement { + sid = "IamAllowCreateServiceLinkedRole" + effect = "Allow" + actions = [ + "iam:CreateServiceLinkedRole" + ] + resources = [ + "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:role/aws-service-role/sagemaker.application-autoscaling.amazonaws.com/*" + ] + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = [ + "sagemaker.application-autoscaling.amazonaws.com" + ] + } + } + statement { + sid = "EcrRegistryReadDominoEnvironments" + effect = "Allow" + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + ] + resources = [ + "arn:${data.aws_partition.current.partition}:ecr:${local.region}:${local.account_id}:repository/${local.environments_repository}", + "arn:${data.aws_partition.current.partition}:ecr:${local.region}:${local.account_id}:repository/${local.environments_repository}*" + ] + } +} + +data "aws_iam_policy_document" "assume_any_role" { + statement { + sid = "StsAllowOtherAccountsAssumeRole" + effect = "Allow" + actions = ["sts:AssumeRole"] + resources = ["*"] + condition { + test = "StringNotLike" + variable = "aws:ResourceAccount" + values = [local.account_id] + } + } +} + +data "aws_iam_policy_document" "operator_grant_policy" { + source_policy_documents = concat( + var.external_deployments.enable_in_account_deployments ? [data.aws_iam_policy_document.in_account_policies.json] : [], + var.external_deployments.enable_assume_any_external_role ? [data.aws_iam_policy_document.assume_any_role.json] : [] + ) +} + +resource "aws_iam_policy" "operator" { + count = local.operator_role_needs_policy ? 1 : 0 + name = "${local.deploy_id}-external-deployments-operator" + policy = data.aws_iam_policy_document.operator_grant_policy.json +} + +resource "aws_iam_role_policy_attachment" "operator" { + count = local.operator_role_needs_policy ? 1 : 0 + role = aws_iam_role.operator.name + policy_arn = aws_iam_policy.operator[0].arn +} diff --git a/modules/external-deployments/outputs.tf b/modules/external-deployments/outputs.tf new file mode 100644 index 00000000..60449b3d --- /dev/null +++ b/modules/external-deployments/outputs.tf @@ -0,0 +1,11 @@ +output "eks" { + description = "External deployments eks info" + value = { + operator_role_arn = aws_iam_role.operator.arn + operator_service_account_name = var.external_deployments.operator_service_account_name + repository = local.repository + bucket = local.bucket + can_assume_any_external_role = var.external_deployments.enable_assume_any_external_role + can_deploy_in_account = var.external_deployments.enable_in_account_deployments + } +} diff --git a/modules/external-deployments/variables.tf b/modules/external-deployments/variables.tf new file mode 100644 index 00000000..8e47048b --- /dev/null +++ b/modules/external-deployments/variables.tf @@ -0,0 +1,72 @@ +variable "external_deployments" { + description = "Config to create IRSA role for the external deployments operator." + + type = object({ + namespace = optional(string, "domino-compute") + operator_service_account_name = optional(string, "pham-juno-operator") + operator_role_suffix = optional(string, "external-deployments-operator") + repository_suffix = optional(string, "external-deployments") + bucket_suffix = optional(string, "external-deployments") + enable_assume_any_external_role = optional(bool, true) + enable_in_account_deployments = optional(bool, true) + }) + + default = {} +} + +variable "eks_info" { + description = <
key_id = string
key_arn = string
enabled = bool
})
list(object({| `[]` | no | | [eks\_info](#input\_eks\_info) | cluster = {
name = string
namespace = string
serviceaccount_name = string
policy = string #json
}))
object({| n/a | yes | -| [external\_deployments\_operator](#input\_external\_deployments\_operator) | Config to create IRSA role for the external deployments operator. |
nodes = object({
roles = list(object({
arn = string
name = string
}))
})
cluster = object({
specs = object({
name = string
account_id = string
})
oidc = object({
arn = string
url = string
cert = object({
thumbprint_list = list(string)
url = string
})
})
})
})
object({| `{}` | no | | [external\_dns](#input\_external\_dns) | Config to enable irsa for external-dns |
enabled = optional(bool, false)
namespace = optional(string, "domino-compute")
service_account_name = optional(string, "pham-juno-operator")
})
object({| `{}` | no | | [netapp\_trident\_operator](#input\_netapp\_trident\_operator) | Config to create IRSA role for the netapp-trident-operator. |
enabled = optional(bool, false)
hosted_zone_name = optional(string, null)
hosted_zone_private = optional(string, false)
namespace = optional(string, "domino-platform")
serviceaccount_name = optional(string, "external-dns")
rm_role_policy = optional(object({
remove = optional(bool, false)
detach_from_role = optional(bool, false)
policy_name = optional(string, "")
}), {})
})
object({| `{}` | no | | [use\_cluster\_odc\_idp](#input\_use\_cluster\_odc\_idp) | Toogle to uset the oidc idp connector in the trust policy.
enabled = optional(bool, false)
namespace = optional(string, "trident")
serviceaccount_name = optional(string, "trident-controller")
region = optional(string)
})
object({| `{}` | no | | [eks](#input\_eks) | k8s\_version = EKS cluster k8s version.
provision_cost_usage_report = optional(bool, false)
})
object({| `{}` | no | | [enable\_private\_link](#input\_enable\_private\_link) | Enable Private Link connections | `bool` | `false` | no | +| [external\_deployments\_operator](#input\_external\_deployments\_operator) | Config to create IRSA role for the external deployments operator. |
k8s_version = optional(string, "1.27")
nodes_master = optional(bool, false)
kubeconfig = optional(object({
extra_args = optional(string, "")
path = optional(string, null)
}), {})
public_access = optional(object({
enabled = optional(bool, false)
cidrs = optional(list(string), [])
}), {})
custom_role_maps = optional(list(object({
rolearn = string
username = string
groups = list(string)
})), [])
master_role_names = optional(list(string), [])
cluster_addons = optional(list(string), ["kube-proxy", "coredns"])
ssm_log_group_name = optional(string, "session-manager")
vpc_cni = optional(object({
prefix_delegation = optional(bool)
annotate_pod_ip = optional(bool)
}))
identity_providers = optional(list(object({
client_id = string
groups_claim = optional(string, null)
groups_prefix = optional(string, null)
identity_provider_config_name = string
issuer_url = optional(string, null)
required_claims = optional(string, null)
username_claim = optional(string, null)
username_prefix = optional(string, null)
})), [])
})
object({| `{}` | no | | [ignore\_tags](#input\_ignore\_tags) | Tag keys to be ignored by the aws provider. | `list(string)` | `[]` | no | -| [irsa\_external\_deployments\_operator](#input\_irsa\_external\_deployments\_operator) | Config to create IRSA role for the external deployments operator. |
enabled = optional(bool, false)
namespace = optional(string, "domino-compute")
operator_service_account_name = optional(string, "pham-juno-operator")
operator_role_suffix = optional(string, "external-deployments-operator")
repository_suffix = optional(string, "external-deployments")
bucket_suffix = optional(string, "external-deployments")
enable_assume_any_external_role = optional(bool, true)
enable_in_account_deployments = optional(bool, true)
})
object({| `{}` | no | | [kms](#input\_kms) | enabled = Toggle,if set use either the specified KMS key\_id or a Domino-generated one.
enabled = optional(bool, false)
namespace = optional(string, "domino-compute")
service_account_name = optional(string, "pham-juno-operator")
})
object({| `{}` | no | | [network](#input\_network) | vpc = {
enabled = optional(bool, true)
key_id = optional(string, null)
additional_policies = optional(list(string), [])
})
object({| `{}` | no | | [region](#input\_region) | AWS region for the deployment | `string` | n/a | yes | diff --git a/tests/plan/terraform/main.tf b/tests/plan/terraform/main.tf index 388fcc4e..e5092974 100644 --- a/tests/plan/terraform/main.tf +++ b/tests/plan/terraform/main.tf @@ -71,11 +71,14 @@ module "irsa_policies" { use_fips_endpoint = var.use_fips_endpoint } -module "irsa_external_deployments_operator" { - source = "./../../../modules/irsa" - eks_info = module.eks.info - external_deployments_operator = var.irsa_external_deployments_operator - use_fips_endpoint = var.use_fips_endpoint +module "external_deployments_operator" { + count = var.external_deployments_operator.enabled ? 1 : 0 + + source = "./../../../modules/external-deployments" + eks_info = module.eks.info + kms_info = module.infra.kms + region = module.infra.region + external_deployments = var.external_deployments_operator } module "nodes" { diff --git a/tests/plan/terraform/variables.tf b/tests/plan/terraform/variables.tf index 489ee938..cdec672c 100644 --- a/tests/plan/terraform/variables.tf +++ b/tests/plan/terraform/variables.tf @@ -483,13 +483,18 @@ variable "use_fips_endpoint" { default = false } -variable "irsa_external_deployments_operator" { +variable "external_deployments_operator" { description = "Config to create IRSA role for the external deployments operator." type = object({ - enabled = optional(bool, false) - namespace = optional(string, "domino-compute") - service_account_name = optional(string, "pham-juno-operator") + enabled = optional(bool, false) + namespace = optional(string, "domino-compute") + operator_service_account_name = optional(string, "pham-juno-operator") + operator_role_suffix = optional(string, "external-deployments-operator") + repository_suffix = optional(string, "external-deployments") + bucket_suffix = optional(string, "external-deployments") + enable_assume_any_external_role = optional(bool, true) + enable_in_account_deployments = optional(bool, true) }) default = {} diff --git a/tests/plan/terraform/versions.tf b/tests/plan/terraform/versions.tf index df931ef0..3417fb61 100644 --- a/tests/plan/terraform/versions.tf +++ b/tests/plan/terraform/versions.tf @@ -11,8 +11,9 @@ terraform { required_version = ">= 1.4.0" required_providers { aws = { - source = "hashicorp/aws" - version = "~> 5.0" + source = "hashicorp/aws" + # pinned until https://github.com/hashicorp/terraform-provider-aws/pull/39328 is released + version = "<5.67.0" } } }
vpc = optional(object({
id = optional(string, null)
subnets = optional(object({
private = optional(list(string), [])
public = optional(list(string), [])
pod = optional(list(string), [])
}), {})
}), {})
network_bits = optional(object({
public = optional(number, 27)
private = optional(number, 19)
pod = optional(number, 19)
}
), {})
cidrs = optional(object({
vpc = optional(string, "10.0.0.0/16")
pod = optional(string, "100.64.0.0/16")
}), {})
use_pod_cidr = optional(bool, true)
})