Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
422 changes: 422 additions & 0 deletions modules/aws/aws-infra/README.md

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions modules/aws/aws-infra/components/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# IAM Component
# Creates cross-account roles, Unity Catalog roles, and associated policies

# Databricks-generated Cross-Account Assume Role Policy
data "databricks_aws_assume_role_policy" "cross_account" {
external_id = var.databricks_config.account_id
}

# Cross-Account Role for Databricks (Always created)
resource "aws_iam_role" "cross_account" {
name = local.iam_config.cross_account_role_name
assume_role_policy = data.databricks_aws_assume_role_policy.cross_account.json

tags = merge(local.common_tags, {
Name = local.iam_config.cross_account_role_name
Purpose = "Databricks Cross-Account Access"
Type = "CrossAccount"
})
}

# Cross-Account Role Policy
data "aws_iam_policy_document" "cross_account_policy" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# Databricks standard permissions
statement {
sid = "Databricks"
effect = "Allow"

actions = [
"ec2:AssociateIamInstanceProfile",
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CancelSpotInstanceRequests",
"ec2:CreateKeyPair",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeyPair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteTags",
"ec2:DeleteVolume",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceStatus",
"ec2:DescribeInstances",
"ec2:DescribeInternetGateways",
"ec2:DescribeKeyPairs",
"ec2:DescribeNetworkAcls",
"ec2:DescribePrefixLists",
"ec2:DescribeReservedInstancesOfferings",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSpotInstanceRequests",
"ec2:DescribeSpotPriceHistory",
"ec2:DescribeSubnets",
"ec2:DescribeVolumes",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcs",
"ec2:DetachVolume",
"ec2:DisassociateIamInstanceProfile",
"ec2:ModifyVpcAttribute",
"ec2:ReplaceIamInstanceProfileAssociation",
"ec2:RequestSpotInstances",
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RunInstances",
"ec2:TerminateInstances"
]

resources = ["*"]
}

# IAM permissions for instance profiles (only if roles_to_assume is populated)
dynamic "statement" {
for_each = length(var.roles_to_assume) > 0 ? [1] : []

content {
sid = "AllowPassRoleInstanceProfile"
effect = "Allow"

actions = [
"iam:PassRole"
]

resources = concat(
# Allow passing the cross-account role itself
["arn:aws:iam::${local.account_id}:role/${local.iam_config.cross_account_role_name}"],
# Allow passing additional roles specified in variables
var.roles_to_assume
)
}
}
}

# Attach policy to cross-account role
resource "aws_iam_role_policy" "cross_account_inline" {
name = "databricks-cross-account-policy"
role = aws_iam_role.cross_account.id
policy = data.aws_iam_policy_document.cross_account_policy.json
}

# Databricks-generated Unity Catalog Assume Role Policy
data "databricks_aws_unity_catalog_assume_role_policy" "unity_catalog" {
aws_account_id = local.account_id
role_name = local.iam_config.unity_catalog_role_name
external_id = var.external_id
}

# Unity Catalog Role (Always created)
resource "aws_iam_role" "unity_catalog" {
name = local.iam_config.unity_catalog_role_name
assume_role_policy = data.databricks_aws_unity_catalog_assume_role_policy.unity_catalog.json

tags = merge(local.common_tags, {
Name = local.iam_config.unity_catalog_role_name
Purpose = "Unity Catalog Metastore Access"
Type = "UnityCatalog"
})
}

# Databricks-generated Unity Catalog IAM Policy
data "databricks_aws_unity_catalog_policy" "unity_catalog" {
aws_account_id = local.account_id
role_name = local.iam_config.unity_catalog_role_name
bucket_name = var.create_metastore_bucket ? aws_s3_bucket.metastore[0].bucket : ""
}

# Attach policy to Unity Catalog role
resource "aws_iam_role_policy" "unity_catalog_inline" {
name = "unity-catalog-metastore-policy"
role = aws_iam_role.unity_catalog.id
policy = data.databricks_aws_unity_catalog_policy.unity_catalog.json
}

# Instance Profiles (optional)
resource "aws_iam_instance_profile" "databricks" {
count = var.create_instance_profiles ? 1 : 0

name = "${var.prefix}-databricks-instance-profile"
role = aws_iam_role.cross_account.name

tags = merge(local.common_tags, {
Name = "${var.prefix}-databricks-instance-profile"
Purpose = "Databricks Compute Instance Profile"
})
}

# Wait for IAM role propagation (Always runs since roles are always created)
resource "time_sleep" "iam_propagation_wait" {
create_duration = "20s"

depends_on = [
aws_iam_role.cross_account,
aws_iam_role.unity_catalog,
aws_iam_role_policy.cross_account_inline,
aws_iam_role_policy.unity_catalog_inline
]
}
159 changes: 159 additions & 0 deletions modules/aws/aws-infra/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# IAM Component
# Creates cross-account roles, Unity Catalog roles, and associated policies

# Databricks-generated Cross-Account Assume Role Policy
data "databricks_aws_assume_role_policy" "cross_account" {
external_id = var.databricks_config.account_id
}

# Cross-Account Role for Databricks (Always created)
resource "aws_iam_role" "cross_account" {
name = local.iam_config.cross_account_role_name
assume_role_policy = data.databricks_aws_assume_role_policy.cross_account.json

tags = merge(local.common_tags, {
Name = local.iam_config.cross_account_role_name
Purpose = "Databricks Cross-Account Access"
Type = "CrossAccount"
})
}

# Cross-Account Role Policy
data "aws_iam_policy_document" "cross_account_policy" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the duplication with modules/aws/aws-infra/components/iam.tf?


# Databricks standard permissions
statement {
sid = "Databricks"
effect = "Allow"

actions = [
"ec2:AssociateIamInstanceProfile",
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CancelSpotInstanceRequests",
"ec2:CreateKeyPair",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeyPair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteTags",
"ec2:DeleteVolume",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceStatus",
"ec2:DescribeInstances",
"ec2:DescribeInternetGateways",
"ec2:DescribeKeyPairs",
"ec2:DescribeNetworkAcls",
"ec2:DescribePrefixLists",
"ec2:DescribeReservedInstancesOfferings",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSpotInstanceRequests",
"ec2:DescribeSpotPriceHistory",
"ec2:DescribeSubnets",
"ec2:DescribeVolumes",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcs",
"ec2:DetachVolume",
"ec2:DisassociateIamInstanceProfile",
"ec2:ModifyVpcAttribute",
"ec2:ReplaceIamInstanceProfileAssociation",
"ec2:RequestSpotInstances",
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RunInstances",
"ec2:TerminateInstances"
]

resources = ["*"]
}

# IAM permissions for instance profiles (only if roles_to_assume is populated)
dynamic "statement" {
for_each = length(var.roles_to_assume) > 0 ? [1] : []

content {
sid = "AllowPassRoleInstanceProfile"
effect = "Allow"

actions = [
"iam:PassRole"
]

resources = concat(
# Allow passing the cross-account role itself
["arn:aws:iam::${local.account_id}:role/${local.iam_config.cross_account_role_name}"],
# Allow passing additional roles specified in variables
var.roles_to_assume
)
}
}
}

# Attach policy to cross-account role
resource "aws_iam_role_policy" "cross_account_inline" {
name = "databricks-cross-account-policy"
role = aws_iam_role.cross_account.id
policy = data.aws_iam_policy_document.cross_account_policy.json
}

# Databricks-generated Unity Catalog Assume Role Policy
data "databricks_aws_unity_catalog_assume_role_policy" "unity_catalog" {
aws_account_id = local.account_id
role_name = local.iam_config.unity_catalog_role_name
external_id = var.external_id
}

# Unity Catalog Role (Always created)
resource "aws_iam_role" "unity_catalog" {
name = local.iam_config.unity_catalog_role_name
assume_role_policy = data.databricks_aws_unity_catalog_assume_role_policy.unity_catalog.json

tags = merge(local.common_tags, {
Name = local.iam_config.unity_catalog_role_name
Purpose = "Unity Catalog Metastore Access"
Type = "UnityCatalog"
})
}

# Databricks-generated Unity Catalog IAM Policy
data "databricks_aws_unity_catalog_policy" "unity_catalog" {
aws_account_id = local.account_id
role_name = local.iam_config.unity_catalog_role_name
bucket_name = var.create_metastore_bucket ? aws_s3_bucket.metastore[0].bucket : ""
}

# Attach policy to Unity Catalog role
resource "aws_iam_role_policy" "unity_catalog_inline" {
name = "unity-catalog-metastore-policy"
role = aws_iam_role.unity_catalog.id
policy = data.databricks_aws_unity_catalog_policy.unity_catalog.json
}

# Instance Profiles (optional)
resource "aws_iam_instance_profile" "databricks" {
count = var.create_instance_profiles ? 1 : 0

name = "${var.prefix}-databricks-instance-profile"
role = aws_iam_role.cross_account.name

tags = merge(local.common_tags, {
Name = "${var.prefix}-databricks-instance-profile"
Purpose = "Databricks Compute Instance Profile"
})
}

# Wait for IAM role propagation (Always runs since roles are always created)
resource "time_sleep" "iam_propagation_wait" {
create_duration = "20s"

depends_on = [
aws_iam_role.cross_account,
aws_iam_role.unity_catalog,
aws_iam_role_policy.cross_account_inline,
aws_iam_role_policy.unity_catalog_inline
]
}
71 changes: 71 additions & 0 deletions modules/aws/aws-infra/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Data sources
data "aws_availability_zones" "available" {
state = "available"
}

data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

locals {
# Common tags applied to all resources
common_tags = merge(var.tags, {
"ManagedBy" = "terraform"
"Module" = "aws-infra"
"Prefix" = var.prefix
"Region" = var.region
"CreatedDate" = formatdate("YYYY-MM-DD", timestamp())
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using timestamp() in common tags will cause Terraform to detect changes on every plan/apply, even when no actual infrastructure changes are needed. This is a known anti-pattern that leads to unnecessary plan noise and potential state drift. Consider removing this tag or using a static value set once during initial deployment.

Suggested change
"CreatedDate" = formatdate("YYYY-MM-DD", timestamp())

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})

# Availability Zones
availability_zones = length(var.networking.availability_zones) > 0 ? var.networking.availability_zones : slice(data.aws_availability_zones.available.names, 0, min(length(data.aws_availability_zones.available.names), 3))

# Subnet CIDR calculations
private_subnet_cidrs = length(var.networking.private_subnet_cidrs) > 0 ? var.networking.private_subnet_cidrs : [
for i in range(length(local.availability_zones)) : cidrsubnet(var.networking.vpc_cidr, 8, i + 1)
]

public_subnet_cidrs = length(var.networking.public_subnet_cidrs) > 0 ? var.networking.public_subnet_cidrs : [
for i in range(length(local.availability_zones)) : cidrsubnet(var.networking.vpc_cidr, 8, i + 101)
]

# Storage configuration - hardcoded bucket names
root_bucket_name = "${var.prefix}-rootbucket"
metastore_bucket_name = "${var.prefix}-metastore"
data_bucket_name = "${var.prefix}-data"


# IAM configuration
iam_config = {
cross_account_role_name = "${var.prefix}-cross-account-role"
unity_catalog_role_name = "${var.prefix}-unity-catalog-role"

# Databricks trust relationship principal
databricks_principals = ["arn:aws:iam::${var.databricks_account_id}:root"]

# Unity Catalog specific configuration
unity_catalog_external_id = var.external_id
unity_catalog_principal = "arn:aws:iam::414351767826:role/unity-catalog-prod-UCMasterRole-14S5ZJVKOTYTL"
}

# Enable firewall if explicitly enabled OR if hub-spoke architecture is enabled
enable_firewall = var.security.enable_network_firewall || var.advanced_networking.hub_spoke_architecture

# Advanced networking configuration
transit_gateway_config = var.advanced_networking.enable_transit_gateway ? {
name = "${var.prefix}-transit-gateway"
hub_vpc_cidr = var.advanced_networking.hub_vpc_cidr
spoke_vpc_cidr = var.networking.vpc_cidr

# Hub VPC subnets (single subnet for each type)
hub_public_subnet_cidr = cidrsubnet(var.advanced_networking.hub_vpc_cidr, 8, 1)
hub_private_subnet_cidr = cidrsubnet(var.advanced_networking.hub_vpc_cidr, 8, 10)
hub_firewall_subnet_cidr = cidrsubnet(var.advanced_networking.hub_vpc_cidr, 8, 20)
} : null

# Current account ID
account_id = data.aws_caller_identity.current.account_id

# Current region name
current_region = data.aws_region.current.id
}
Loading