Skip to content
Merged
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
12 changes: 10 additions & 2 deletions codeartifact-repo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,29 @@ This module is intended to configure AWS CodeArtifact domains and repositories.
| [aws_codeartifact_domain_permissions_policy.domain_permissions_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codeartifact_domain_permissions_policy) | resource |
| [aws_codeartifact_repository.repository](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codeartifact_repository) | resource |
| [aws_codeartifact_repository_permissions_policy.repo_permissions_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codeartifact_repository_permissions_policy) | resource |
| [aws_iam_role.admin_access_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.publisher_access_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.read_access_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.admin_access_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.publisher_access_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.read_only_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_kms_key.domain_encryption_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_admin_principals"></a> [admin\_principals](#input\_admin\_principals) | List of AWS principal ARNs that have admin access to domain and repositories | `list(string)` | `[]` | no |
| <a name="input_domain_encryption_key_policy_path"></a> [domain\_encryption\_key\_policy\_path](#input\_domain\_encryption\_key\_policy\_path) | Path to file containing IAM policy to be applied to created encryption key | `string` | `null` | no |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | Domain name of the repository | `string` | n/a | yes |
| <a name="input_domain_owner"></a> [domain\_owner](#input\_domain\_owner) | Account number of the account that owns the repository. If not set, defaults to the account running Terraform | `string` | `null` | no |
| <a name="input_domain_permissions_policy_revision"></a> [domain\_permissions\_policy\_revision](#input\_domain\_permissions\_policy\_revision) | Current revision of the domain permission policy to set | `string` | `null` | no |
| <a name="input_domain_policy_document_path"></a> [domain\_policy\_document\_path](#input\_domain\_policy\_document\_path) | Path to IAM policy document applied to Codeartifact domain | `string` | `null` | no |
| <a name="input_encryption_key_arn"></a> [encryption\_key\_arn](#input\_encryption\_key\_arn) | ARN of KMS key used for repository encryption. If not specified, and use\_default\_ecnryption\_key is false, creates new KMS key | `string` | `null` | no |
| <a name="input_publisher_principals"></a> [publisher\_principals](#input\_publisher\_principals) | List of AWS principal ARNS thet should have permissions to publish packages | `list(string)` | `[]` | no |
| <a name="input_reader_principals"></a> [reader\_principals](#input\_reader\_principals) | List of AWS principals ARNs that should have read access to domain and repositories | `list(string)` | `[]` | no |
| <a name="input_repo_region"></a> [repo\_region](#input\_repo\_region) | Region in which repository will be managed. If not specified, defaults to region configured for provider | `string` | `null` | no |
| <a name="input_repositories"></a> [repositories](#input\_repositories) | List of repositories within Codeartifact domain | <pre>list(object({<br/> repository_name = string<br/> description = optional(string, "")<br/> region = optional(string, null)<br/> domain_owner = optional(string, null)<br/> upstream = optional(string, null)<br/> external_connection = optional(string, null)<br/> policy_document_path = optional(string, null)<br/> default_read_access_principals = optional(list(string), null)<br/> default_write_access_principals = optional(list(string), null)<br/> }))</pre> | `[]` | no |
| <a name="input_repositories"></a> [repositories](#input\_repositories) | List of repositories within Codeartifact domain | <pre>list(object({<br/> repository_name = string<br/> description = optional(string, "")<br/> region = optional(string, null)<br/> domain_owner = optional(string, null)<br/> upstream = optional(string, null)<br/> external_connection = optional(string, null)<br/> policy_document_path = optional(string, null)<br/> }))</pre> | `[]` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to be applied to resources | `map(string)` | `{}` | no |
| <a name="input_use_default_ecnryption_key"></a> [use\_default\_ecnryption\_key](#input\_use\_default\_ecnryption\_key) | Whether to use default Codeartifact KMS key (defaults to true) | `bool` | `true` | no |

Expand All @@ -54,7 +63,6 @@ This module is intended to configure AWS CodeArtifact domains and repositories.
| Name | Description |
|------|-------------|
| <a name="output_created_repositories"></a> [created\_repositories](#output\_created\_repositories) | A list of names of the created repositories. |
| <a name="output_default_sts_policies"></a> [default\_sts\_policies](#output\_default\_sts\_policies) | Created STS policies |
| <a name="output_domain"></a> [domain](#output\_domain) | Name of the CodeArtifact domain |
| <a name="output_domain_owner"></a> [domain\_owner](#output\_domain\_owner) | Owner account of the CodeArtifact domain |
| <a name="output_policy_documents"></a> [policy\_documents](#output\_policy\_documents) | A map of repository names to their applied policy documents (if any). |
Expand Down
215 changes: 141 additions & 74 deletions codeartifact-repo/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,10 @@

locals {
should_create_kms_key = (!var.use_default_ecnryption_key && var.encryption_key_arn == null) ? true : false

repo_read_access_principals = {
for repo in var.repositories : repo.repository_name => repo.default_read_access_principals if(repo.default_read_access_principals != null
&& length(repo.default_read_access_principals) > 0)
}
repo_write_access_principals = {
for repo in var.repositories : repo.repository_name => repo.default_write_access_principals if(repo.default_write_access_principals != null
&& length(repo.default_write_access_principals) > 0)
}
repo_final_policy_documents = data.aws_iam_policy_document.combined_default_policies
# repos_with_policy_files = [
# for repo in var.repositories : repo.repository_name if repo.policy_document_path != null
# ]
all_sts_principals = {
for repo in var.repositories : repo.repository_name => distinct(concat(
repo.default_read_access_principals != null ? repo.default_read_access_principals : [],
repo.default_write_access_principals != null ? repo.default_write_access_principals : []
))
}
}

data "aws_caller_identity" "current" {}

resource "aws_codeartifact_domain" "repo_domain" {
domain = var.domain_name
region = var.repo_region != null ? var.repo_region : null
Expand Down Expand Up @@ -66,64 +49,124 @@ resource "aws_codeartifact_repository" "repository" {
tags = var.tags
}

data "aws_iam_policy_document" "default_readonly_repo_policy" {
for_each = { for k, v in local.repo_read_access_principals : k => v }

resource "aws_codeartifact_repository_permissions_policy" "repo_permissions_policy" {
for_each = { for repo in var.repositories : repo.repository_name => repo if repo.policy_document_path != null }
repository = aws_codeartifact_repository.repository[each.key].repository
domain = aws_codeartifact_domain.repo_domain.domain
policy_document = file(each.value.policy_document_path)
region = var.repo_region != null ? var.repo_region : null
domain_owner = each.value.domain_owner != null ? each.value.domain_owner : null
}

resource "aws_kms_key" "domain_encryption_key" {
count = local.should_create_kms_key ? 1 : 0
description = "KMS key for CodeArtifact domain ${var.domain_name}"
enable_key_rotation = true
policy = var.domain_encryption_key_policy_path != null ? file(var.domain_encryption_key_policy_path) : null
tags = var.tags
}

data "aws_iam_policy_document" "read_only_policy_document" {
count = var.reader_principals != null && length(var.reader_principals) > 0 ? 1 : 0
statement {
effect = "Allow"
actions = [
"codeartifact:DescribePackageVersion",
"codeartifact:DescribeRepository",
"codeartifact:GetPackageVersionReadme",
"codeartifact:GetRepositoryEndpoint",
"codeartifact:ListPackageVersionAssets",
"codeartifact:ListPackageVersionDependencies",
"codeartifact:ListPackageVersions",
"codeartifact:ListPackages",
"codeartifact:ReadFromRepository",
"codeartifact:Get*",
"codeartifact:Describe*",
"codeartifact:List*"
]
resources = [aws_codeartifact_repository.repository[each.key].arn]
resources = [aws_codeartifact_domain.repo_domain.arn, "${aws_codeartifact_domain.repo_domain.arn}/*"]
}
statement {
effect = "Allow"
actions = ["sts:GetServiceBearerToken"]
resources = [
aws_codeartifact_domain.repo_domain.arn,
"${aws_codeartifact_domain.repo_domain.arn}/*",
"arn:aws:sts::${data.aws_caller_identity.current.account_id}:assumed-role/${aws_iam_role.read_access_role[0].name}/*"
]
condition {
variable = "sts:AWSServiceName"
values = ["codeartifact.amazonaws.com"]
test = "StringEquals"
}
}
}

data "aws_iam_policy_document" "assume_read_only_role_document" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = each.value
identifiers = var.reader_principals
}
actions = ["sts:AssumeRole"]
}
}

data "aws_iam_policy_document" "default_write_access_repo_policy" {
for_each = { for k, v in local.repo_write_access_principals : k => v }
data "aws_iam_policy_document" "publisher_policy_document" {
count = var.publisher_principals != null && length(var.publisher_principals) > 0 ? 1 : 0
statement {
effect = "Allow"
actions = [
"codeartifact:DescribePackageVersion",
"codeartifact:DescribeRepository",
"codeartifact:GetPackageVersionReadme",
"codeartifact:GetRepositoryEndpoint",
"codeartifact:ListPackageVersionAssets",
"codeartifact:ListPackageVersionDependencies",
"codeartifact:ListPackageVersions",
"codeartifact:ListPackages",
"codeartifact:ReadFromRepository",
"codeartifact:Get*",
"codeartifact:Describe*",
"codeartifact:List*",
"codeartifact:PublishPackageVersion",
"codeartifact:PutPackageMetadata",
"codeartifact:ReadFromRepository",
"codeartifact:CreatePackageGroup",
"coeedartifact:UpdatePackageGroup",
"codeartifact:CopyPackageVersions"
]
resources = [
aws_codeartifact_domain.repo_domain.arn,
"${aws_codeartifact_domain.repo_domain.arn}/*",
replace("${aws_codeartifact_domain.repo_domain.arn}/*", ":domain/", ":package/")
]
resources = [aws_codeartifact_repository.repository[each.key].arn]
}
statement {
effect = "Allow"
actions = ["sts:GetServiceBearerToken"]
resources = [
aws_codeartifact_domain.repo_domain.arn,
"${aws_codeartifact_domain.repo_domain.arn}/*",
"arn:aws:sts::${data.aws_caller_identity.current.account_id}:assumed-role/${aws_iam_role.publisher_access_role[0].name}/*"
]
condition {
variable = "sts:AWSServiceName"
values = ["codeartifact.amazonaws.com"]
test = "StringEquals"
}
}
}

data "aws_iam_policy_document" "assume_publisher_role_document" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = each.value
identifiers = var.publisher_principals
}
actions = ["sts:AssumeRole"]
}
}

data "aws_iam_policy_document" "default_sts_policy" {
for_each = local.all_sts_principals
data "aws_iam_policy_document" "admin_policy_document" {
count = var.admin_principals != null && length(var.admin_principals) > 0 ? 1 : 0
statement {
effect = "Allow"
actions = [
"codeartifact:*",
]
resources = ["*"]
}
statement {
effect = "Allow"
actions = ["sts:GetServiceBearerToken"]
resources = [aws_codeartifact_repository.repository[each.key].arn]
principals {
type = "AWS"
identifiers = each.value
}
resources = ["*"]
condition {
variable = "sts:AWSServiceName"
values = ["codeartifact.amazonaws.com"]
Expand All @@ -132,31 +175,55 @@ data "aws_iam_policy_document" "default_sts_policy" {
}
}

data "aws_iam_policy_document" "combined_default_policies" {
for_each = { for repo in var.repositories : repo.repository_name => repo if contains(keys(local.repo_read_access_principals), repo.repository_name) || contains(keys(local.repo_write_access_principals), repo.repository_name) }
data "aws_iam_policy_document" "assume_admin_role_document" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = var.admin_principals
}
actions = ["sts:AssumeRole"]
}
}

resource "aws_iam_role" "read_access_role" {
count = var.reader_principals != null && length(var.reader_principals) > 0 ? 1 : 0
name = "CodeArtifactReadAccessRole-${aws_codeartifact_domain.repo_domain.domain}"
description = "Role providing read access to CodeArtifact domain ${aws_codeartifact_domain.repo_domain.domain}"
assume_role_policy = data.aws_iam_policy_document.assume_read_only_role_document.json
tags = var.tags
}

source_policy_documents = compact(
[
try(data.aws_iam_policy_document.default_readonly_repo_policy[each.key].json, null),
try(data.aws_iam_policy_document.default_write_access_repo_policy[each.key].json, null),
try(data.aws_iam_policy_document.default_sts_policy[each.key].json, null)
]
)
resource "aws_iam_role_policy" "read_only_role_policy" {
count = var.reader_principals != null && length(var.reader_principals) > 0 ? 1 : 0
policy = data.aws_iam_policy_document.read_only_policy_document[0].json
role = aws_iam_role.read_access_role[0].name
}

resource "aws_codeartifact_repository_permissions_policy" "repo_permissions_policy" {
for_each = { for repo in var.repositories : repo.repository_name => repo if repo.policy_document_path != null || contains(keys(local.repo_final_policy_documents), repo.repository_name) }
repository = aws_codeartifact_repository.repository[each.key].repository
domain = aws_codeartifact_domain.repo_domain.domain
policy_document = each.value.policy_document_path != null ? file(each.value.policy_document_path) : local.repo_final_policy_documents[each.key].json
region = var.repo_region != null ? var.repo_region : null
domain_owner = each.value.domain_owner != null ? each.value.domain_owner : null
resource "aws_iam_role" "publisher_access_role" {
count = var.publisher_principals != null && length(var.publisher_principals) > 0 ? 1 : 0
name = "CodeArtifactPublisherAccessRole-${aws_codeartifact_domain.repo_domain.domain}"
description = "Role providing publisher access to CodeArtifact domain ${aws_codeartifact_domain.repo_domain.domain}"
assume_role_policy = data.aws_iam_policy_document.assume_publisher_role_document.json
tags = var.tags
}

resource "aws_kms_key" "domain_encryption_key" {
count = local.should_create_kms_key ? 1 : 0
description = "KMS key for CodeArtifact domain ${var.domain_name}"
enable_key_rotation = true
policy = var.domain_encryption_key_policy_path != null ? file(var.domain_encryption_key_policy_path) : null
tags = var.tags
}
resource "aws_iam_role_policy" "publisher_access_role_policy" {
count = var.publisher_principals != null && length(var.publisher_principals) > 0 ? 1 : 0
policy = data.aws_iam_policy_document.publisher_policy_document[0].json
role = aws_iam_role.publisher_access_role[0].name
}

resource "aws_iam_role" "admin_access_role" {
count = var.admin_principals != null && length(var.admin_principals) > 0 ? 1 : 0
name = "CodeArtifactAdminAccessRole-${aws_codeartifact_domain.repo_domain.domain}"
description = "Role providing administrative access to CodeArtifact domain ${aws_codeartifact_domain.repo_domain.domain}"
assume_role_policy = data.aws_iam_policy_document.assume_admin_role_document.json
tags = var.tags
}

resource "aws_iam_role_policy" "admin_access_role_policy" {
count = var.admin_principals != null && length(var.admin_principals) > 0 ? 1 : 0
policy = data.aws_iam_policy_document.admin_policy_document[0].json
role = aws_iam_role.admin_access_role[0].name
}
5 changes: 0 additions & 5 deletions codeartifact-repo/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,3 @@ output "policy_documents" {
description = "A map of repository names to their applied policy documents (if any)."
value = { for repo_name, repo_policy in aws_codeartifact_repository_permissions_policy.repo_permissions_policy : repo_name => repo_policy.policy_document }
}

output "default_sts_policies" {
description = "Created STS policies"
value = { for repo_name, sts_policy in data.aws_iam_policy_document.default_sts_policy : repo_name => sts_policy.json }
}
6 changes: 2 additions & 4 deletions codeartifact-repo/tests/domain.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// SPDX-License-Identifier: MPL-2.0


mock_provider "aws" {

}
mock_provider "aws" {}

run "invalid_encryption_key_arn_should_produce_error" {
command = plan
Expand Down Expand Up @@ -153,4 +151,4 @@ run "domain_permissions_policy_is_not_created_when_path_is_not_provided" {
condition = length(aws_codeartifact_domain_permissions_policy.domain_permissions_policy) == 0
error_message = "The domain permissions policy was created despite no policy document path being provided."
}
}
}
Loading
Loading