Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟧 AWS CodeArtifact Terraform Module

Secure-by-default AWS CodeArtifact domain, repositories, and resource policies — customer-managed KMS encryption required (no AWS-managed-key fallback), internal upstream chaining, single-external-connection public upstreams, and package-manager endpoint resolution, all from one composite call. Built for the AWS provider v6.x.

Terraform aws module type resources


🧩 Overview

  • 📦 Provisions an aws_codeartifact_domain keystone — the encryption, billing, and namespace boundary for every repository inside it.
  • 🔐 Customer-managed KMS key REQUIREDkms_key_arn has no default. Unlike most of this library's optional-CMK pattern, this module refuses to fall back to the AWS-owned aws/codeartifact key, because CodeArtifact stores build/package assets that can be PII-adjacent or proprietary.
  • 🗂️ Repositories as a for_each collection (map(object(...))), keyed directly by the real repository name — no separate logical key to keep in sync.
  • 🔗 Internal upstream chaining — a real ordered list of sibling repository names, priority-ordered for package resolution.
  • 🌐 Single nullable external_connections — modeled as optional(object({...})), not a list, because the AWS API allows exactly one external (public-registry) connection per repository even though the provider schema renders it as a repeatable block.
  • 🧾 Domain and repository resource policies are optional singletons/for_eachaws_codeartifact_domain_permissions_policy (0-1) and aws_codeartifact_repository_permissions_policy (per repository), both not created unless a policy document is supplied.
  • 🎯 Package-manager endpoint resolution built in — an internal data "aws_codeartifact_repository_endpoint" read per repository that opts into endpoint_format (npm | pypi | maven | nuget), surfaced in the repository_endpoints output for CI .npmrc / pip.conf / settings.xml / NuGet.Config wiring.
  • 🏷️ Universal taggingvar.tags flows to the domain and every repository; the two permissions-policy resources have no tags argument at all.

💡 Why it matters: CodeArtifact is our package-management foundation — every CI pipeline that publishes or consumes npm/PyPI/Maven/NuGet packages authenticates against a domain this module creates. Requiring a customer-managed key and modeling external_connections as a real one-per-repository constraint closes off two of the most consequential mistakes in a regulated-FI build pipeline: silent AWS-managed-key encryption, and an unbounded public-internet egress path into internal package storage.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits in the family

terraform-aws-codeartifact consumes a customer-managed KMS key from terraform-aws-kms (required) and, optionally, IAM principal ARNs authored by the IAM family for its resource-policy documents. It is consumed by the CI/CD family (terraform-aws-codebuild, terraform-aws-codepipeline) and by terraform-aws-iam-policy for cross-resource ARN references.

flowchart LR
 kms["terraform-aws-kms"]
 iamrole["terraform-aws-iam-role"]
 iamuser["terraform-aws-iam-user"]
 iamgroup["terraform-aws-iam-group"]
 ca["terraform-aws-codeartifact"]
 codebuild["terraform-aws-codebuild"]
 codepipeline["terraform-aws-codepipeline"]
 iampolicy["terraform-aws-iam-policy"]
 vpcep["terraform-aws-vpc-endpoint"]

 kms -- "kms_key_arn (REQUIRED)" --> ca
 iamrole -. "principal arns in policy documents".-> ca
 iamuser -. "principal arns".-> ca
 iamgroup -. "principal arns".-> ca

 ca -- "arn / name" --> codebuild
 ca -- "arn / name" --> codepipeline
 ca -- "arn" --> iampolicy
 vpcep -. "private connectivity (optional, not wired)".-> ca

 style ca fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
Loading

🧬 What this module builds

flowchart TB
 subgraph CAMOD["terraform-aws-codeartifact"]
 domain["aws_codeartifact_domain.this<br/>(keystone)"]
 dpp["aws_codeartifact_domain_permissions_policy.this<br/>(0-1, optional singleton)"]
 repo["aws_codeartifact_repository.this<br/>(for_each over repositories map)"]
 rpp["aws_codeartifact_repository_permissions_policy.this<br/>(for_each, per repo with a policy)"]
 ep["data.aws_codeartifact_repository_endpoint.this<br/>(for_each, per repo with endpoint_format)"]

 domain --> dpp
 domain --> repo
 repo --> rpp
 repo --> ep
 repo -. "upstream (internal, ordered list)".-> repo
 repo -. "external_connections (single, optional)".-> pub["Public registry npmjs / PyPI / Maven Central / etc."]
 end

 style domain fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
Loading
Resource Role Cardinality
aws_codeartifact_domain.this Keystone domain — encryption + S3 asset store boundary 1
aws_codeartifact_domain_permissions_policy.this Domain-level resource policy 0-1 (when domain_permissions_policy_document is set)
aws_codeartifact_repository.this Child repositories, each with upstream / external_connections per repositories entry
aws_codeartifact_repository_permissions_policy.this Repository-level resource policy per repository that sets permissions_policy_document
data.aws_codeartifact_repository_endpoint.this Package-manager endpoint URL (npm/pypi/maven/nuget) per repository that sets endpoint_format

✅ Provider / Versions

Requirement Version
Terraform >= 1.12.0
hashicorp/aws >= 6.0, < 7.0 (confirmed against live v6.53.0 provider schema)

No provider {} block is declared inside the module — the caller's configured provider (and its region/credentials) is inherited. CodeArtifact is a regional service; no region variable is exposed.


🔑 Required IAM Permissions

Least-privilege actions the Terraform identity needs to create, read, update, and delete everything this module manages:

Action Required for Notes
codeartifact:CreateDomain, codeartifact:DescribeDomain, codeartifact:DeleteDomain Domain lifecycle
codeartifact:CreateRepository, codeartifact:DescribeRepository, codeartifact:DeleteRepository, codeartifact:UpdateRepository Repository lifecycle UpdateRepository covers in-place description edits
codeartifact:AssociateExternalConnection, codeartifact:DisassociateExternalConnection Public upstream (external_connections) attach/detach Only one connection per repository — hard API limit
codeartifact:AssociateWithDownstreamRepository Internal upstream repository chaining
codeartifact:PutDomainPermissionsPolicy, codeartifact:GetDomainPermissionsPolicy, codeartifact:DeleteDomainPermissionsPolicy Domain resource policy Only when domain_permissions_policy_document is set
codeartifact:PutRepositoryPermissionsPolicy, codeartifact:GetRepositoryPermissionsPolicy, codeartifact:DeleteRepositoryPermissionsPolicy Repository resource policy Only for repositories that set permissions_policy_document
codeartifact:TagResource, codeartifact:UntagResource, codeartifact:ListTagsForResource Tagging Domain + repositories only — the two permissions-policy resources are not taggable
codeartifact:GetRepositoryEndpoint Internal data.aws_codeartifact_repository_endpoint read Only for repositories that set endpoint_format
kms:DescribeKey, kms:CreateGrant Validate/use the customer-managed key supplied via kms_key_arn CodeArtifact's KMS model is grant-based — see AWS Prerequisites below
iam:PassRole Not required CodeArtifact never assumes a service role on the caller's behalf

ℹ️ No service-linked role is auto-created by any of this module's resources. Runtime consumers (CI build roles, developers) separately need codeartifact:GetAuthorizationToken, codeartifact:GetRepositoryEndpoint, codeartifact:ReadFromRepository, and (for publishers) codeartifact:PublishPackageVersion — those are data-plane permissions granted via the repository/domain permissions policy or an IAM policy, not permissions the Terraform identity itself needs.


📋 AWS Prerequisites

  • No service-linked role is required for CodeArtifact domain or repository creation.
  • Naming constraints (confirmed against the live CreateDomain/CreateRepository API):
  • domain: 2-50 characters, pattern [a-z][a-z0-9-]{0,48}[a-z0-9] — lowercase, digits, hyphens only.
  • repository (the repositories map key): 2-100 characters, pattern [A-Za-z0-9][A-Za-z0-9._-]{1,99}.
  • Domain names are publicly discoverable as a DNS-hostname prefix — never put sensitive information in one.
  • KMS key policy is grant-based, not a direct service-principal grant. AWS's documented example domain key policy grants kms:CreateGrant and kms:DescribeKey to a Principal: {"AWS": "*"} statement scoped down with Condition: {StringEquals: {"kms:CallerAccount": "<account-id>", "kms:ViaService": "codeartifact.<region>.amazonaws.com"}} — i.e. it authorizes account principals acting through CodeArtifact to create a grant, rather than granting codeartifact.amazonaws.com a direct kms:Decrypt/kms:GenerateDataKey statement (the more familiar S3/RDS-style pattern). Configure this on the KMS side (terraform-aws-kms), not in this module.
  • External connections / supply-chain consideration (regulated-FI note). The complete, current set of supported external_connection_name values:
Name Ecosystem Name Ecosystem
public:npmjs npm public:nuget-org NuGet
public:pypi Python public:crates-io Rust (Cargo)
public:maven-central Maven public:ruby-gems-org Ruby
public:maven-clojars Maven (Clojars) public:maven-commonsware Maven (CommonsWare Android)
public:maven-googleandroid Maven (Google Android) public:maven-gradleplugins Maven (Gradle plugins)

Enabling one is an explicit egress and supply-chain-integrity decision — it lets the domain pull unvetted third-party code from the public internet into an build pipeline. Prefer one connector repository per domain per registry, with every other repository referencing it via upstream (never granting every repository its own direct public connection); record the justification and target registry in that repository's description; and route risk-acceptance to Security/Risk before applying in production — this module creates the connection on request but does not gate or approve it.

  • Quotas (confirmed against the live CodeArtifact Quotas page):
Quota Default Adjustable
Domains per account (per Region) 10 Yes
Repositories per domain (per Region) 1,000 Yes
Direct upstream repositories per repository 10 No
External connections per repository 1 No — hard API constraint
Upstream repositories searched (transitively) 25 No
  • Repository endpoints are a data source, not a resource attribute. format is a closed set at this provider version: npm, pypi, maven, nuget.
  • -store repositories are a console convenience this module does not create. Enabling an external connection via the AWS console auto-creates an intermediate <ecosystem>-store repository (npm-store, pypi-store, maven-central-store, nuget-store, cargo-store, clojars-store, commonsware-store, google-android-store, gradle-plugins-store, rubygems-store) and wires it as an upstream. Declaring repositories via Terraform, you name the connector repository yourself — there's no requirement to use the -store suffix.

📁 Module Structure

terraform-aws-codeartifact/
├── providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # domain, kms_key_arn (REQUIRED), domain policy, repositories map, tags
├── main.tf # locals (guards) + domain, policies, repositories, endpoint data source
├── outputs.tf # id + arn, domain attributes, repository maps, tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, emits, gotchas

⚙️ Quick Start

module "kms" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
  name   = "casey-codeartifact"
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn # REQUIRED — no AWS-managed-key fallback

  repositories = {
    npm-internal = {
      description = "Internal npm repository for -published packages."
    }
  }

  tags = {
    Environment = "prod"
    CostCenter  = "platform-engineering"
  }
}

⚠️ Pin the source with ?ref=v1.0.0 — never a branch. kms_key_arn is required; the module will fail terraform validate without it.


🔌 Cross-Module Contract

Consumes

Input Type Source module
kms_key_arn string (KMS key ARN) terraform-aws-kms
Principal ARNs inside domain_permissions_policy_document / each repository's permissions_policy_document string (IAM ARNs), caller-supplied JSON terraform-aws-iam-role / terraform-aws-iam-user / terraform-aws-iam-group

Emits

Output Description Consumed by
id Domain ARN (provider sets id to the ARN, not a bare name) Cross-resource reference / import
arn Domain ARN — arn:aws:codeartifact:<region>:<account>:domain/<domain> terraform-aws-iam-policy, terraform-aws-kms (key-policy principal scoping)
name Domain name terraform-aws-codebuild, terraform-aws-codepipeline, tagging
owner AWS account ID that owns the domain Cross-account policy authoring
s3_bucket_arn ARN of the AWS-managed asset-store S3 bucket Audit / CloudTrail data-event scoping
repository_ids / repository_arns Maps (keyed by repository name) of repository ARNs Import, terraform-aws-iam-policy, repository policy documents
repository_names Map of repository names terraform-aws-codebuild buildspec, CLI/CI wiring
repository_endpoints Map of resolved package-manager endpoint URLs (only for repos with endpoint_format set) CI .npmrc / pip.conf / settings.xml / NuGet.Config
tags_all All tags on the domain incl. provider default_tags Governance / audit

📚 Example Library

1 · Minimal — domain with a required customer-managed KMS key
module "kms" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
  name   = "casey-codeartifact"
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn
}
2 · npm repository with a public external connection (public:npmjs)
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    npm-connector = {
      description          = "Single public npmjs.com egress point for the domain (AWS-recommended one-connection-repository pattern). Risk-accepted by Security 2026-06-01."
      external_connections = { external_connection_name = "public:npmjs" }
    }
  }
}
3 · PyPI repository example
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    pypi-connector = {
      description          = "Single public PyPI egress point for the domain."
      external_connections = { external_connection_name = "public:pypi" }
    }
  }
}
4 · Maven repository example
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    maven-connector = {
      description          = "Single public Maven Central egress point for the domain."
      external_connections = { external_connection_name = "public:maven-central" }
    }
  }
}
5 · NuGet repository example
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    nuget-connector = {
      description          = "Single public NuGet Gallery egress point for the domain."
      external_connections = { external_connection_name = "public:nuget-org" }
    }
  }
}
6 · Internal-upstream chaining — connector repository + downstream repositories
# AWS-recommended topology: one repository per domain holds the public connection;
# every other repository references it via upstream, rather than granting each
# repository its own direct external_connections.
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    npm-connector = {
      description          = "Single public npmjs.com egress point (AWS-recommended pattern)."
      external_connections = { external_connection_name = "public:npmjs" }
    }
    npm-team-a = {
      description = "Team A's npm repository — resolves via npm-connector, never talks to the public internet directly."
      upstream    = [{ repository_name = "npm-connector" }]
    }
    npm-team-b = {
      description = "Team B's npm repository — same connector, independent permissions."
      upstream    = [{ repository_name = "npm-connector" }]
    }
  }
}
7 · Domain permissions policy — grant a partner account CreateRepository
data "aws_iam_policy_document" "domain_policy" {
  statement {
    effect  = "Allow"
    actions = ["codeartifact:CreateRepository", "codeartifact:GetDomainPermissionsPolicy"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::222233334444:root"]
    }

    resources = [module.codeartifact.arn]
  }
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  domain_permissions_policy_document = data.aws_iam_policy_document.domain_policy.json
}
8 · Repository permissions policy — read-only cross-account access
data "aws_iam_policy_document" "repo_policy" {
  statement {
    effect  = "Allow"
    actions = ["codeartifact:ReadFromRepository", "codeartifact:GetRepositoryEndpoint"]

    principals {
      type        = "AWS"
      identifiers = [module.build_role.arn] # terraform-aws-iam-role
    }

    resources = ["*"]
  }
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    npm-internal = {
      permissions_policy_document = data.aws_iam_policy_document.repo_policy.json
    }
  }
}
9 · repository_endpoints / endpoint_format resolution
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    npm-internal = {
      endpoint_format = "npm" # resolved via an internal data source read
    }
  }
}

# module.codeartifact.repository_endpoints["npm-internal"]
# => "https://casey-packages-111122223333.d.codeartifact.us-east-1.amazonaws.com/npm/npm-internal/"
10 · Tags — merge with provider default_tags
provider "aws" {
  region = "us-east-1"
  default_tags {
    tags = { Owner = "platform", ManagedBy = "terraform" }
  }
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  tags = {
    Environment = "prod"
    Owner       = "platform-engineering" # overrides default_tags Owner
  }

  repositories = {
    npm-internal = {
      tags = { Tier = "internal" } # merged on top of module tags for this repository
    }
  }
}
# module.codeartifact.tags_all => { Owner="platform-engineering", ManagedBy="terraform", Environment="prod" }
11 · Secure-by-default opt-outs — what this module does and does not let you relax
# kms_key_arn has NO opt-out at the module level — it is a required variable with
# no default, so there is no HCL flag to flip to fall back to the AWS-managed key.
# A caller who genuinely needs the AWS-managed aws/codeartifact key cannot express
# that through this module; that is a deliberate decision (see SCOPE.md), not
# an oversight, given CodeArtifact's role storing potentially proprietary build
# artifacts. If a documented exception is ever approved, it would require a
# separate module variant or a direct aws_codeartifact_domain resource outside
# this module — not a flag on this one.

# The opt-outs this module DOES expose:
module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    # external_connections defaults to null (disabled) on every repository —
    # this is the explicit opt-IN, not an opt-out:
    npm-connector = {
      external_connections = { external_connection_name = "public:npmjs" }
    }
  }
  # domain_permissions_policy_document defaults to null (no resource policy) —
  # leaving it unset is itself the secure default, requiring no opt-out.
}
12 · for_each multi-repository pattern
locals {
  ecosystems = {
    npm-internal   = { endpoint_format = "npm" }
    pypi-internal  = { endpoint_format = "pypi" }
    maven-internal = { endpoint_format = "maven" }
    nuget-internal = { endpoint_format = "nuget" }
  }
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain       = "casey-packages"
  kms_key_arn  = module.kms.arn
  repositories = local.ecosystems
}

# module.codeartifact.repository_endpoints => one URL per ecosystem, keyed by name
13 · Import block — domain and repository (ARN-based)
import {
  to = module.codeartifact.aws_codeartifact_domain.this
  id = "arn:aws:codeartifact:us-east-1:111122223333:domain/casey-packages"
}

import {
  to = module.codeartifact.aws_codeartifact_repository.this["npm-internal"]
  id = "arn:aws:codeartifact:us-east-1:111122223333:repository/casey-packages/npm-internal"
}

⚠️ Both aws_codeartifact_domain and aws_codeartifact_repository import by ARN, not by a bare name — the CodeArtifact provider's identity schema requires the full ARN for both resources (and for aws_codeartifact_repository_permissions_policy). aws_codeartifact_domain_permissions_policy also imports by the domain ARN, even though its own id attribute is the domain name — see Architecture Notes.

14 · End-to-end composition — KMS + CodeArtifact + CodeBuild/CodePipeline (finale)
module "kms" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
  name   = "casey-codeartifact"
}

module "codeartifact" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-codeartifact?ref=v1.0.0"

  domain      = "casey-packages"
  kms_key_arn = module.kms.arn

  repositories = {
    npm-connector = {
      description          = "Single public npmjs.com egress point. Risk-accepted 2026-06-01."
      external_connections = { external_connection_name = "public:npmjs" }
    }
    npm-internal = {
      description     = "Internal npm repository for -published packages."
      upstream        = [{ repository_name = "npm-connector" }]
      endpoint_format = "npm"

      permissions_policy_document = data.aws_iam_policy_document.repo_read.json
    }
  }

  tags = { Environment = "prod", App = "lending-portal" }
}

data "aws_iam_policy_document" "repo_read" {
  statement {
    effect    = "Allow"
    actions   = ["codeartifact:ReadFromRepository", "codeartifact:GetRepositoryEndpoint"]
    resources = ["*"]

    principals {
      type        = "AWS"
      identifiers = [module.codebuild_role.arn] # terraform-aws-iam-role
    }
  }
}

module "codebuild_role" {
  source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
  name   = "casey-lending-portal-codebuild"
  # trust policy: codebuild.amazonaws.com
}

module "codebuild" {
  source   = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
  name     = "lending-portal-build"
  role_arn = module.codebuild_role.arn

  environment_variables = {
    CODEARTIFACT_DOMAIN     = module.codeartifact.name
    CODEARTIFACT_REPOSITORY = module.codeartifact.repository_names["npm-internal"]
    NPM_REGISTRY_URL        = module.codeartifact.repository_endpoints["npm-internal"]
  }
}

module "codepipeline" {
  source   = "git::https://github.com/microsoftexpert/terraform-aws-codepipeline?ref=v1.0.0"
  name     = "lending-portal-pipeline"
  role_arn = module.pipeline_role.arn
  #... stages referencing module.codebuild.arn...
}

📥 Inputs

  • Core: domain (2-50 chars, force-new), kms_key_arn (REQUIRED, no default)
  • Domain resource policy: domain_permissions_policy_document, domain_permissions_policy_revision
  • Repositories: repositoriesmap(object({ description?, upstream?, external_connections?, endpoint_format?, permissions_policy_document?, permissions_policy_revision?, tags? })), keyed by repository name
  • Universal: tags

🧾 Outputs

  • Primary: id, arn
  • Domain attributes: name, owner, s3_bucket_arn, repository_count, created_time, asset_size_bytes
  • Domain permissions policy (try(...) → null when unset): domain_permissions_policy_resource_arn, domain_permissions_policy_revision
  • Repositories (maps keyed by repository name): repository_ids, repository_arns, repository_names, repository_administrator_accounts
  • Repository permissions policies: repository_permissions_policy_resource_arns (only repositories with a policy have an entry)
  • Repository endpoints: repository_endpoints (only repositories with endpoint_format set have an entry)
  • Tags: tags_all

ℹ️ No outputs are marked sensitive — this module emits no secrets. (The aws_codeartifact_authorization_token data source, which does emit a sensitive token, is deliberately not part of this module — see Architecture Notes.)


🧠 Architecture Notes

  • ARN / ID formats:
  • Domain arn / idarn:aws:codeartifact:<region>:<account>:domain/<domain> (both identical).
  • Repository arn / idarn:aws:codeartifact:<region>:<account>:repository/<domain>/<repository> (both identical).
  • aws_codeartifact_repository_permissions_policy id → the resource ARN (same as resource_arn).
  • aws_codeartifact_domain_permissions_policy id is the domain NAME, not the ARN — the one exception to "id == arn" across this composite. Its resource_arn attribute (surfaced here as domain_permissions_policy_resource_arn) carries the actual ARN.
  • Force-new / immutable fields: domain (cascades to every repository, since repository identity is domain/repository) and the repositories map key (the repository name itself — this module keys directly by name, so renaming means changing the key, which Terraform reads as destroy-then-create).
  • tagstags_alldefault_tags: var.tags flows to the domain and to every repository's tags (merged with each repository's own tags); tags_all is the provider-computed union over default_tags on the domain only. aws_codeartifact_domain_permissions_policy and aws_codeartifact_repository_permissions_policy have no tags argument at all — they are not taggable.
  • Repository endpoints are a data source, not a resource attributeGetRepositoryEndpoint is per package format (npm/pypi/maven/nuget), so this module resolves at most one format per repository via the optional endpoint_format field. A repository serving multiple ecosystems needs additional direct data-source calls in the consuming root module for the remaining formats.
  • external_connections is a real one-per-repository constraint, not a stylistic choice — confirmed max_items = 1 in the live provider schema, matching the AWS CLI documentation. Modeling it as a single nullable object converts what would be an apply-time API error into a terraform validate-time type error.
  • Eventual consistency: newly created repositories and their external_connections/upstream wiring are typically available within seconds, but package availability through a fresh external connection can lag — AWS notes up to ~30 minutes for new npm/PyPI/NuGet package versions to become fetchable through a CodeArtifact external connection after publishing upstream.
  • Destroy ordering: a domain cannot be deleted while it still contains repositories. Terraform infers this automatically from the domain =... reference in each repository resource; permissions policies detach automatically with their parent domain/repository.
  • No region variable and no us-east-1 constraint — CodeArtifact is a regional service; the module relies on provider inheritance.
  • The aws_codeartifact_authorization_token data source is intentionally NOT part of this module. It generates a short-lived, sensitive npm/pip/twine login token — a runtime CI concern evaluated by the consuming pipeline (terraform-aws-codebuild / terraform-aws-codepipeline buildspecs or the CI role itself), not something this authoring-time module should mint or cache in state.

🧱 Design Principles

Secure-by-default posture and the explicit opt-out for each:

Hardened default Behavior Opt-out / control
Domain encryption kms_key_arn is required, no default — never falls back to the AWS-managed aws/codeartifact key No module-level opt-out. A caller can only ever supply a CMK ARN; there is no flag to request the AWS-managed key from this module (deliberate divergence — see SCOPE.md)
Public external connections Every repository's external_connections defaults to null (disabled) Set external_connections = { external_connection_name = "public:npmjs" } (etc.) on the specific repository that needs it
Domain resource policy Not created unless domain_permissions_policy_document is supplied Supply a JSON policy document scoped to specific principals/actions
Repository resource policy Not created unless a repository's permissions_policy_document is supplied Same — scoped per repository
Tagging tags universal map flows to the domain and every repository N/A — always applied

Other principles: the domain is the keystone because it is the encryption/billing/quota boundary; repositories is keyed directly by the real repository name (not a separate logical key) so upstream references are simple literal strings with no drift risk; external_connections is a type-level singleton, not a list, to match the real one-per-repository API constraint; VPC endpoint wiring for private connectivity is deliberately excluded (owned by terraform-aws-vpc-endpoint).


🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires valid AWS credentials (profile / SSO / OIDC) + a region
terraform apply
terraform output

⚠️ plan / apply require valid AWS credentials and a configured region (provider block / AWS_PROFILE / SSO / OIDC). Always pin the module source with ?ref=v1.0.0, never a branch.


🧪 Testing

  • terraform init -backend=false && terraform validate — schema + reference integrity (confirmed clean against the live v6.53.0 provider).
  • terraform fmt -check — formatting.
  • terraform plan against a sandbox account — confirm the domain, each repository, and any policies/data-source reads plan as expected, and that omitting kms_key_arn fails validate (required variable).
  • After apply, confirm repository_endpoints resolves for any repository with endpoint_format set, and that npm config set / pip config set / equivalent against the returned URL authenticates using a token from aws codeartifact get-authorization-token.
  • Destroy test in a throwaway account — confirm repositories delete before the domain (automatic ordering) and that no ResourceNotFoundException surfaces when removing an entry from repositories.

💬 Example Output

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

arn = "arn:aws:codeartifact:us-east-1:123456789012:domain/casey-packages"
id = "arn:aws:codeartifact:us-east-1:123456789012:domain/casey-packages"
name = "casey-packages"
owner = "123456789012"
s3_bucket_arn = "arn:aws:s3:::codeartifact-us-east-1-a1b2c3d4e5f6"
repository_arns = { "npm-internal" = "arn:aws:codeartifact:us-east-1:123456789012:repository/casey-packages/npm-internal" }
repository_ids = { "npm-internal" = "arn:aws:codeartifact:us-east-1:123456789012:repository/casey-packages/npm-internal" }
repository_names = { "npm-internal" = "npm-internal" }
repository_endpoints = { "npm-internal" = "https://casey-packages-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/npm-internal/" }
tags_all = { "Environment" = "prod", "CostCenter" = "platform-engineering" }

🔍 Troubleshooting

  • terraform validate fails with a missing kms_key_arn: Expected — this module has no AWS-managed-key fallback. Wire an ARN from terraform-aws-kms.
  • AccessDeniedException on apply even though the Terraform identity has codeartifact:*: Check the KMS key policy — CodeArtifact's grant-based model requires the key policy itself to allow kms:CreateGrant/kms:DescribeKey scoped via kms:ViaService = codeartifact.<region>.amazonaws.com. An IAM policy alone is not sufficient if the key policy doesn't also authorize the caller.
  • A second external_connections block fails at terraform validate, not apply: By design — the object type is a single nullable object, not a list, so a caller who tries to pass two connections gets a type error immediately rather than an AWS API ValidationException at apply.
  • upstream.repository_name validation error referencing "another key present in this same repositories map": The internal-chaining validation only accepts upstream targets that are themselves keys in the same repositories map in this module call. If the target repository is managed by a different module call (a different domain, or a repository this module doesn't own), this module cannot express that reference — the upstream repository must be declared in the same repositories map.
  • Tag drift / unexpected tags: Caused by default_tags overlap on the domain or a repository. tags_all merges resource tags over provider default_tags with resource tags winning — set the value explicitly in var.tags (or a repository's own tags) if it differs from what you expect.
  • Credential-chain failures (NoCredentialProviders / ExpiredToken): No valid credentials resolved. Set AWS_PROFILE, refresh SSO, or confirm the OIDC role assumption in CI. The module never takes credentials as variables.
  • ResourceNotFoundException when removing a repository from repositories: If a repository still has downstream upstream references from other repositories in the same apply, or the domain is being destroyed in the same run, ordering can surprise you — remove leaf repositories first, or apply in stages.
  • Package not found through a fresh external_connections: Newly published upstream packages (npm/PyPI/NuGet) can take up to ~30 minutes to become fetchable through a new external connection — this is an AWS-side caching behavior, not a misconfiguration.
  • us-east-1 errors: None expected — CodeArtifact is a regional service with no global-resource coupling in this module.

🔗 Related Docs

  • Terraform Registry — hashicorp/aws provider: aws_codeartifact_domain, aws_codeartifact_repository, aws_codeartifact_domain_permissions_policy, aws_codeartifact_repository_permissions_policy, aws_codeartifact_repository_endpoint (data source), aws_codeartifact_authorization_token (data source)
  • AWS — What is CodeArtifact? (CodeArtifact User Guide)
  • AWS — Working with upstream repositories in CodeArtifact
  • AWS — Connect a CodeArtifact repository to a public repository (external connections, -store repositories, supported registries)
  • AWS — CodeArtifact domain and repository resource policies
  • AWS — Quotas in AWS CodeArtifact
  • terraform-aws-kms, terraform-aws-iam-role, terraform-aws-iam-policy, terraform-aws-codebuild, terraform-aws-codepipeline, terraform-aws-vpc-endpoint (sibling modules)

🧡 "Infrastructure as Code should be standardized, consistent, and secure."

Releases

Packages

Contributors

Languages