Secure-by-default AWS CodeArtifact domain, repositories, and resource policies — customer-managed KMS encryption required (no AWS-managed-key fallback), internal
upstreamchaining, single-external-connection public upstreams, and package-manager endpoint resolution, all from one composite call. Built for the AWS provider v6.x.
- 📦 Provisions an
aws_codeartifact_domainkeystone — the encryption, billing, and namespace boundary for every repository inside it. - 🔐 Customer-managed KMS key REQUIRED —
kms_key_arnhas no default. Unlike most of this library's optional-CMK pattern, this module refuses to fall back to the AWS-ownedaws/codeartifactkey, because CodeArtifact stores build/package assets that can be PII-adjacent or proprietary. - 🗂️ Repositories as a
for_eachcollection (map(object(...))), keyed directly by the real repository name — no separate logical key to keep in sync. - 🔗 Internal
upstreamchaining — a real ordered list of sibling repository names, priority-ordered for package resolution. - 🌐 Single nullable
external_connections— modeled asoptional(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_each —
aws_codeartifact_domain_permissions_policy(0-1) andaws_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 intoendpoint_format(npm|pypi|maven|nuget), surfaced in therepository_endpointsoutput for CI.npmrc/pip.conf/settings.xml/NuGet.Configwiring. - 🏷️ Universal tagging —
var.tagsflows to the domain and every repository; the two permissions-policy resources have notagsargument 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_connectionsas 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.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
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!
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
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
| 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 |
| 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.
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.
- No service-linked role is required for CodeArtifact domain or repository creation.
- Naming constraints (confirmed against the live
CreateDomain/CreateRepositoryAPI): domain: 2-50 characters, pattern[a-z][a-z0-9-]{0,48}[a-z0-9]— lowercase, digits, hyphens only.repository(therepositoriesmap 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:CreateGrantandkms:DescribeKeyto aPrincipal: {"AWS": "*"}statement scoped down withCondition: {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 grantingcodeartifact.amazonaws.coma directkms:Decrypt/kms:GenerateDataKeystatement (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_namevalues:
| 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.
formatis a closed set at this provider version:npm,pypi,maven,nuget. -storerepositories are a console convenience this module does not create. Enabling an external connection via the AWS console auto-creates an intermediate<ecosystem>-storerepository (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-storesuffix.
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
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_arnis required; the module will failterraform validatewithout it.
| 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 |
| 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 |
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 name13 · 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"
}
⚠️ Bothaws_codeartifact_domainandaws_codeartifact_repositoryimport by ARN, not by a bare name — the CodeArtifact provider's identity schema requires the full ARN for both resources (and foraws_codeartifact_repository_permissions_policy).aws_codeartifact_domain_permissions_policyalso imports by the domain ARN, even though its ownidattribute 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...
}- 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:
repositories—map(object({ description?, upstream?, external_connections?, endpoint_format?, permissions_policy_document?, permissions_policy_revision?, tags? })), keyed by repository name - Universal:
tags
- Primary:
id,arn - Domain attributes:
name,owner,s3_bucket_arn,repository_count,created_time,asset_size_bytes - Domain permissions policy (
try(...) → nullwhen 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 withendpoint_formatset have an entry) - Tags:
tags_all
ℹ️ No outputs are marked
sensitive— this module emits no secrets. (Theaws_codeartifact_authorization_tokendata source, which does emit a sensitive token, is deliberately not part of this module — see Architecture Notes.)
- ARN / ID formats:
- Domain
arn/id→arn:aws:codeartifact:<region>:<account>:domain/<domain>(both identical). - Repository
arn/id→arn:aws:codeartifact:<region>:<account>:repository/<domain>/<repository>(both identical). aws_codeartifact_repository_permissions_policyid→ the resource ARN (same asresource_arn).aws_codeartifact_domain_permissions_policyidis the domain NAME, not the ARN — the one exception to "id == arn" across this composite. Itsresource_arnattribute (surfaced here asdomain_permissions_policy_resource_arn) carries the actual ARN.- Force-new / immutable fields:
domain(cascades to every repository, since repository identity isdomain/repository) and therepositoriesmap key (the repository name itself — this module keys directly by name, so renaming means changing the key, which Terraform reads as destroy-then-create). tags↔tags_all↔default_tags:var.tagsflows to the domain and to every repository'stags(merged with each repository's owntags);tags_allis the provider-computed union overdefault_tagson the domain only.aws_codeartifact_domain_permissions_policyandaws_codeartifact_repository_permissions_policyhave notagsargument at all — they are not taggable.- Repository endpoints are a data source, not a resource attribute —
GetRepositoryEndpointis per packageformat(npm/pypi/maven/nuget), so this module resolves at most one format per repository via the optionalendpoint_formatfield. A repository serving multiple ecosystems needs additional direct data-source calls in the consuming root module for the remaining formats. external_connectionsis a real one-per-repository constraint, not a stylistic choice — confirmedmax_items = 1in the live provider schema, matching the AWS CLI documentation. Modeling it as a single nullable object converts what would be anapply-time API error into aterraform validate-time type error.- Eventual consistency: newly created repositories and their
external_connections/upstreamwiring 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
regionvariable and nous-east-1constraint — CodeArtifact is a regional service; the module relies on provider inheritance. - The
aws_codeartifact_authorization_tokendata source is intentionally NOT part of this module. It generates a short-lived,sensitivenpm/pip/twine login token — a runtime CI concern evaluated by the consuming pipeline (terraform-aws-codebuild/terraform-aws-codepipelinebuildspecs or the CI role itself), not something this authoring-time module should mint or cache in state.
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).
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/applyrequire 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.
terraform init -backend=false && terraform validate— schema + reference integrity (confirmed clean against the live v6.53.0 provider).terraform fmt -check— formatting.terraform planagainst a sandbox account — confirm the domain, each repository, and any policies/data-source reads plan as expected, and that omittingkms_key_arnfailsvalidate(required variable).- After
apply, confirmrepository_endpointsresolves for any repository withendpoint_formatset, and thatnpm config set/pip config set/ equivalent against the returned URL authenticates using a token fromaws codeartifact get-authorization-token. - Destroy test in a throwaway account — confirm repositories delete before the domain (automatic ordering) and that no
ResourceNotFoundExceptionsurfaces when removing an entry fromrepositories.
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" }
terraform validatefails with a missingkms_key_arn: Expected — this module has no AWS-managed-key fallback. Wire an ARN fromterraform-aws-kms.AccessDeniedExceptiononapplyeven though the Terraform identity hascodeartifact:*: Check the KMS key policy — CodeArtifact's grant-based model requires the key policy itself to allowkms:CreateGrant/kms:DescribeKeyscoped viakms: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_connectionsblock fails atterraform validate, notapply: 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 APIValidationExceptionatapply. upstream.repository_namevalidation error referencing "another key present in this same repositories map": The internal-chaining validation only accepts upstream targets that are themselves keys in the samerepositoriesmap 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 samerepositoriesmap.- Tag drift / unexpected tags: Caused by
default_tagsoverlap on the domain or a repository.tags_allmerges resource tags over providerdefault_tagswith resource tags winning — set the value explicitly invar.tags(or a repository's owntags) if it differs from what you expect. - Credential-chain failures (
NoCredentialProviders/ExpiredToken): No valid credentials resolved. SetAWS_PROFILE, refresh SSO, or confirm the OIDC role assumption in CI. The module never takes credentials as variables. ResourceNotFoundExceptionwhen removing a repository fromrepositories: If a repository still has downstreamupstreamreferences from other repositories in the sameapply, 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-1errors: None expected — CodeArtifact is a regional service with no global-resource coupling in this module.
- Terraform Registry —
hashicorp/awsprovider: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,
-storerepositories, 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."