diff --git a/nexus-apk-proxy/main.tf b/nexus-apk-proxy/main.tf new file mode 100644 index 0000000..b7f9f96 --- /dev/null +++ b/nexus-apk-proxy/main.tf @@ -0,0 +1,133 @@ +locals { + remote_url = "https://apk.cgr.dev/${var.chainguard_organization}" +} + +# Split APKINDEX requests from package requests so each proxy can use its own +# cache TTL. The block rule goes on the packages proxy; the allow rule goes on +# the index proxy. Both reference the same regex. + +resource "sonatyperepo_routing_rule" "block_index" { + name = "chainguard-apk-block-index" + description = "Block APKINDEX.tar.gz paths from the packages proxy." + mode = "BLOCK" + matchers = [".*APKINDEX\\.tar\\.gz"] +} + +resource "sonatyperepo_routing_rule" "only_index" { + name = "chainguard-apk-only-index" + description = "Restrict the index proxy to APKINDEX.tar.gz paths only." + mode = "ALLOW" + matchers = [".*APKINDEX\\.tar\\.gz"] +} + +# Packages proxy — handles .apk file requests. +# Packages are immutable, so content_max_age = -1 (never re-check the origin). +# +# MANUAL STEP REQUIRED: After applying, open this repository in the Nexus UI +# (Settings → Repository → Repositories → chainguard-apk-packages) and enable +# "Preserve encoded characters in URLs" under the HTTP section. This is not yet +# exposed by the Terraform provider. Without it, Nexus decodes percent-encoded +# characters (%2B, %2F, %3D) in the presigned R2 URLs that apk.cgr.dev +# redirects to, breaking the request signature. +resource "sonatyperepo_repository_raw_proxy" "packages" { + name = "chainguard-apk-packages" + online = true + routing_rule = sonatyperepo_routing_rule.block_index.name + + storage = { + blob_store_name = var.blob_store_name + strict_content_type_validation = false + } + + proxy = { + remote_url = local.remote_url + content_max_age = -1 + metadata_max_age = -1 + } + + negative_cache = { + enabled = true + time_to_live = 1440 + } + + http_client = { + blocked = false + auto_block = true + authentication = { + type = "username" + username = var.chainguard_pull_token_username + password = var.chainguard_pull_token_password + } + } + + raw = { + content_disposition = "ATTACHMENT" + } +} + +# Index proxy — handles APKINDEX.tar.gz requests. +# The index is regenerated upstream whenever a package is added, so a short TTL +# is used. Tune index_max_age_minutes up to reduce origin fetches, down for +# faster visibility of newly published packages. +# +# MANUAL STEP REQUIRED: Same as above — enable "Preserve encoded characters in +# URLs" for chainguard-apk-index in the Nexus UI after applying. +resource "sonatyperepo_repository_raw_proxy" "index" { + name = "chainguard-apk-index" + online = true + routing_rule = sonatyperepo_routing_rule.only_index.name + + storage = { + blob_store_name = var.blob_store_name + strict_content_type_validation = false + } + + proxy = { + remote_url = local.remote_url + content_max_age = var.index_max_age_minutes + metadata_max_age = var.index_max_age_minutes + } + + negative_cache = { + enabled = true + time_to_live = 1440 + } + + http_client = { + blocked = false + auto_block = true + authentication = { + type = "username" + username = var.chainguard_pull_token_username + password = var.chainguard_pull_token_password + } + } + + raw = { + content_disposition = "ATTACHMENT" + } +} + +# Group repository — the single URL clients point apk at. +# Packages proxy is listed first so its routing rule (BLOCK on APKINDEX) fires +# before the request falls through to the index proxy. +resource "sonatyperepo_repository_raw_group" "group" { + name = "chainguard-apk" + online = true + + storage = { + blob_store_name = var.blob_store_name + strict_content_type_validation = false + } + + group = { + member_names = [ + sonatyperepo_repository_raw_proxy.packages.name, + sonatyperepo_repository_raw_proxy.index.name, + ] + } + + raw = { + content_disposition = "ATTACHMENT" + } +} diff --git a/nexus-apk-proxy/outputs.tf b/nexus-apk-proxy/outputs.tf new file mode 100644 index 0000000..b3c1224 --- /dev/null +++ b/nexus-apk-proxy/outputs.tf @@ -0,0 +1,14 @@ +output "group_repository_url" { + description = "URL to use in /etc/apk/repositories. Point apk at this." + value = "${var.nexus_url}/repository/${sonatyperepo_repository_raw_group.group.name}" +} + +output "packages_proxy_url" { + description = "Direct URL of the packages proxy (for reference / debugging)" + value = "${var.nexus_url}/repository/${sonatyperepo_repository_raw_proxy.packages.name}" +} + +output "index_proxy_url" { + description = "Direct URL of the index proxy (for reference / debugging)" + value = "${var.nexus_url}/repository/${sonatyperepo_repository_raw_proxy.index.name}" +} diff --git a/nexus-apk-proxy/providers.tf b/nexus-apk-proxy/providers.tf new file mode 100644 index 0000000..31f995c --- /dev/null +++ b/nexus-apk-proxy/providers.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + sonatyperepo = { + source = "sonatype-nexus-community/sonatyperepo" + version = "~> 1.0" + } + } +} + +provider "sonatyperepo" { + url = var.nexus_url + username = var.nexus_username + password = var.nexus_password +} diff --git a/nexus-apk-proxy/terraform.tfvars.example b/nexus-apk-proxy/terraform.tfvars.example new file mode 100644 index 0000000..fe92da2 --- /dev/null +++ b/nexus-apk-proxy/terraform.tfvars.example @@ -0,0 +1,5 @@ +# Enter your nexus URL and port # +nexus_url="http://nexus.local:80" + +# Chainguard Organization name +chainguard_organization="justin.prince" diff --git a/nexus-apk-proxy/variables.tf b/nexus-apk-proxy/variables.tf new file mode 100644 index 0000000..6787d68 --- /dev/null +++ b/nexus-apk-proxy/variables.tf @@ -0,0 +1,44 @@ +variable "nexus_url" { + description = "Base URL of the Nexus Repository Manager instance (e.g. http://localhost:8081)" + type = string +} + +variable "nexus_username" { + description = "Nexus admin username" + type = string + default = "admin" +} + +variable "nexus_password" { + description = "Nexus admin password" + type = string + sensitive = true +} + +variable "chainguard_organization" { + description = "Chainguard organization name as shown in the Chainguard Console (used to form https://apk.cgr.dev/)" + type = string +} + +variable "chainguard_pull_token_username" { + description = "Identity ID from 'chainctl auth pull-token --repository=apk' (the Username field)" + type = string +} + +variable "chainguard_pull_token_password" { + description = "Token from 'chainctl auth pull-token --repository=apk' (the Password field)" + type = string + sensitive = true +} + +variable "blob_store_name" { + description = "Name of the Nexus blob store to use for all created repositories" + type = string + default = "default" +} + +variable "index_max_age_minutes" { + description = "Cache TTL for APKINDEX.tar.gz in minutes. Lower values give faster visibility of new packages at the cost of more origin fetches." + type = number + default = 15 +}