add htpasswd auth to private docker registry + expose at registry.viktorbarzin.me
- Add auth.htpasswd section to config-private.yml - Mount htpasswd file in registry-private container, fix healthcheck for 401 - Rename registry UI from registry.viktorbarzin.me → docker.viktorbarzin.me - Add Docker CLI ingress at registry.viktorbarzin.me (HTTPS backend, no rate-limit, unlimited body) - Add docker to cloudflare_proxied_names (registry stays non-proxied) - Add Kyverno ClusterPolicy to sync registry-credentials secret to all namespaces - Update infra provisioning to install apache2-utils and generate htpasswd from Vault
This commit is contained in:
parent
e4f478b490
commit
36171bcda4
6 changed files with 123 additions and 5 deletions
BIN
config.tfvars
BIN
config.tfvars
Binary file not shown.
|
|
@ -16,6 +16,10 @@ storage:
|
||||||
age: 168h
|
age: 168h
|
||||||
interval: 4h
|
interval: 4h
|
||||||
dryrun: false
|
dryrun: false
|
||||||
|
auth:
|
||||||
|
htpasswd:
|
||||||
|
realm: "Registry Realm"
|
||||||
|
path: /auth/htpasswd
|
||||||
http:
|
http:
|
||||||
addr: :5000
|
addr: :5000
|
||||||
headers:
|
headers:
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,12 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /opt/registry/data/private:/var/lib/registry
|
- /opt/registry/data/private:/var/lib/registry
|
||||||
- /opt/registry/config-private.yml:/etc/docker/registry/config.yml:ro
|
- /opt/registry/config-private.yml:/etc/docker/registry/config.yml:ro
|
||||||
|
- /opt/registry/htpasswd:/auth/htpasswd:ro
|
||||||
networks:
|
networks:
|
||||||
- registry
|
- registry
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "sh", "-c", "wget -qO- http://localhost:5000/v2/ >/dev/null 2>&1"]
|
# 401 is expected (auth required) — any HTTP response means the registry is healthy
|
||||||
|
test: ["CMD", "sh", "-c", "wget -qS -O /dev/null http://localhost:5000/v2/ 2>&1 | grep -q 'HTTP/'"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ data "vault_kv_secret_v2" "secrets" {
|
||||||
name = "infra"
|
name = "infra"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "vault_kv_secret_v2" "viktor" {
|
||||||
|
mount = "secret"
|
||||||
|
name = "viktor"
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Locals
|
# Locals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -176,8 +181,8 @@ module "docker-registry-template" {
|
||||||
|
|
||||||
# Setup registry config and start container
|
# Setup registry config and start container
|
||||||
provision_cmds = [
|
provision_cmds = [
|
||||||
# Install and enable QEMU guest agent for remote management
|
# Install dependencies (QEMU guest agent + htpasswd for registry auth)
|
||||||
"apt-get install -y qemu-guest-agent",
|
"apt-get install -y qemu-guest-agent apache2-utils",
|
||||||
"systemctl enable qemu-guest-agent",
|
"systemctl enable qemu-guest-agent",
|
||||||
"systemctl start qemu-guest-agent",
|
"systemctl start qemu-guest-agent",
|
||||||
# Stop host nginx — we run nginx inside Docker instead
|
# Stop host nginx — we run nginx inside Docker instead
|
||||||
|
|
@ -185,6 +190,11 @@ module "docker-registry-template" {
|
||||||
"systemctl disable nginx || true",
|
"systemctl disable nginx || true",
|
||||||
# Create directory structure
|
# Create directory structure
|
||||||
"mkdir -p /opt/registry/data/dockerhub /opt/registry/data/ghcr /opt/registry/data/quay /opt/registry/data/k8s /opt/registry/data/kyverno /opt/registry/data/private /opt/registry/tls",
|
"mkdir -p /opt/registry/data/dockerhub /opt/registry/data/ghcr /opt/registry/data/quay /opt/registry/data/k8s /opt/registry/data/kyverno /opt/registry/data/private /opt/registry/tls",
|
||||||
|
# Generate htpasswd file for private registry authentication
|
||||||
|
format("htpasswd -Bbn %s %s > /opt/registry/htpasswd",
|
||||||
|
data.vault_kv_secret_v2.viktor.data["registry_user"],
|
||||||
|
data.vault_kv_secret_v2.viktor.data["registry_password"]
|
||||||
|
),
|
||||||
# Write Docker Compose file
|
# Write Docker Compose file
|
||||||
format("echo %s | base64 -d > /opt/registry/docker-compose.yml",
|
format("echo %s | base64 -d > /opt/registry/docker-compose.yml",
|
||||||
base64encode(file("${path.root}/../../modules/docker-registry/docker-compose.yml"))
|
base64encode(file("${path.root}/../../modules/docker-registry/docker-compose.yml"))
|
||||||
|
|
|
||||||
83
stacks/kyverno/modules/kyverno/registry-credentials.tf
Normal file
83
stacks/kyverno/modules/kyverno/registry-credentials.tf
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Private Docker Registry Credentials — Auto-sync to all namespaces
|
||||||
|
# =============================================================================
|
||||||
|
# Source secret in kyverno namespace, cloned by ClusterPolicy into every NS.
|
||||||
|
# Pods use imagePullSecrets: [{name: registry-credentials}] to pull from
|
||||||
|
# registry.viktorbarzin.me (or 10.0.20.10:5050 internally).
|
||||||
|
|
||||||
|
data "vault_kv_secret_v2" "viktor" {
|
||||||
|
mount = "secret"
|
||||||
|
name = "viktor"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_secret" "registry_credentials" {
|
||||||
|
metadata {
|
||||||
|
name = "registry-credentials"
|
||||||
|
namespace = kubernetes_namespace.kyverno.metadata[0].name
|
||||||
|
}
|
||||||
|
type = "kubernetes.io/dockerconfigjson"
|
||||||
|
data = {
|
||||||
|
".dockerconfigjson" = jsonencode({
|
||||||
|
auths = {
|
||||||
|
"registry.viktorbarzin.me" = {
|
||||||
|
auth = base64encode("${data.vault_kv_secret_v2.viktor.data["registry_user"]}:${data.vault_kv_secret_v2.viktor.data["registry_password"]}")
|
||||||
|
}
|
||||||
|
"10.0.20.10:5050" = {
|
||||||
|
auth = base64encode("${data.vault_kv_secret_v2.viktor.data["registry_user"]}:${data.vault_kv_secret_v2.viktor.data["registry_password"]}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_manifest" "sync_registry_credentials" {
|
||||||
|
manifest = {
|
||||||
|
apiVersion = "kyverno.io/v1"
|
||||||
|
kind = "ClusterPolicy"
|
||||||
|
metadata = {
|
||||||
|
name = "sync-registry-credentials"
|
||||||
|
}
|
||||||
|
spec = {
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
name = "sync-registry-secret"
|
||||||
|
match = {
|
||||||
|
any = [
|
||||||
|
{
|
||||||
|
resources = {
|
||||||
|
kinds = ["Namespace"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
exclude = {
|
||||||
|
any = [
|
||||||
|
{
|
||||||
|
resources = {
|
||||||
|
namespaces = ["kube-system", "kube-public", "kube-node-lease"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
generate = {
|
||||||
|
apiVersion = "v1"
|
||||||
|
kind = "Secret"
|
||||||
|
name = "registry-credentials"
|
||||||
|
namespace = "{{request.object.metadata.name}}"
|
||||||
|
synchronize = true
|
||||||
|
clone = {
|
||||||
|
namespace = "kyverno"
|
||||||
|
name = "registry-credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
helm_release.kyverno,
|
||||||
|
kubernetes_secret.registry_credentials,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -186,10 +186,10 @@ module "proxmox" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# https://registry.viktorbarzin.me/
|
# https://docker.viktorbarzin.me/ (registry web UI)
|
||||||
module "docker-registry-ui" {
|
module "docker-registry-ui" {
|
||||||
source = "./factory"
|
source = "./factory"
|
||||||
name = "registry"
|
name = "docker"
|
||||||
external_name = "docker-registry.viktorbarzin.lan"
|
external_name = "docker-registry.viktorbarzin.lan"
|
||||||
port = 8080
|
port = 8080
|
||||||
tls_secret_name = var.tls_secret_name
|
tls_secret_name = var.tls_secret_name
|
||||||
|
|
@ -206,6 +206,25 @@ module "docker-registry-ui" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# https://registry.viktorbarzin.me/ (Docker CLI push/pull endpoint)
|
||||||
|
module "docker-registry-cli" {
|
||||||
|
source = "./factory"
|
||||||
|
name = "registry"
|
||||||
|
external_name = "docker-registry.viktorbarzin.lan"
|
||||||
|
port = 5050
|
||||||
|
backend_protocol = "HTTPS"
|
||||||
|
tls_secret_name = var.tls_secret_name
|
||||||
|
protected = false # Docker CLI uses htpasswd, NOT Authentik
|
||||||
|
max_body_size = "0" # unlimited - Docker layers can be large
|
||||||
|
depends_on = [kubernetes_namespace.reverse-proxy]
|
||||||
|
extra_annotations = {
|
||||||
|
# Skip rate-limit (Docker push/pull generates many rapid requests)
|
||||||
|
# Keep CrowdSec for L7 protection
|
||||||
|
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-csp-headers@kubernetescrd,traefik-crowdsec@kubernetescrd"
|
||||||
|
"gethomepage.dev/enabled" = "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# https://valchedrym.viktorbarzin.me/
|
# https://valchedrym.viktorbarzin.me/
|
||||||
module "valchedrym" {
|
module "valchedrym" {
|
||||||
source = "./factory"
|
source = "./factory"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue