From 1743145e9f4504c3487dee89cf4f2e2325cf1441 Mon Sep 17 00:00:00 2001 From: Hermes Agent service account Date: Thu, 4 Jun 2026 21:12:00 -0500 Subject: [PATCH] fix(harbor): disable TLS in expose config (Gateway API handles TLS) --- .../harbor/REGISTRY_HTPASSWD_SETUP.md | 117 ++++++++++++++++++ .../harbor/generate-registry-htpasswd.py | 83 +++++++++++++ cluster/platform/harbor/values.yaml | 5 +- 3 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 cluster/platform/harbor/REGISTRY_HTPASSWD_SETUP.md create mode 100755 cluster/platform/harbor/generate-registry-htpasswd.py diff --git a/cluster/platform/harbor/REGISTRY_HTPASSWD_SETUP.md b/cluster/platform/harbor/REGISTRY_HTPASSWD_SETUP.md new file mode 100644 index 0000000..d3fbdd4 --- /dev/null +++ b/cluster/platform/harbor/REGISTRY_HTPASSWD_SETUP.md @@ -0,0 +1,117 @@ +# Harbor Registry HTPASSWD Setup Instructions + +## Problem Fixed +The Harbor Helm chart requires TWO credential keys for registry authentication: +- `REGISTRY_PASSWD` - plain text password +- `REGISTRY_HTPASSWD` - bcrypt-hashed htpasswd format + +Previously, only REGISTRY_PASSWD was configured, causing Harbor registry pod failures. + +## Solution Implemented +**Option B: Pre-stored hash in 1Password** + +This approach was chosen because: +1. Aligns with mk-labs pattern of storing credentials directly in 1Password +2. More maintainable and predictable than template-based hashing +3. Simpler to troubleshoot and validate +4. No dependency on ExternalSecrets template engine capabilities + +## 1Password Setup Required + +### Step 1: Generate the htpasswd hash + +You need to generate a bcrypt hash of the registry password. The username must be `harbor_registry_user`. + +**Using Python (recommended):** +```bash +python3 -c "import bcrypt; password=input('Enter registry password: '); print(f'harbor_registry_user:{bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=10)).decode()}')" +``` + +**Using htpasswd tool (if available):** +```bash +htpasswd -nbB harbor_registry_user +``` + +### Step 2: Add field to 1Password + +1. Open 1Password and navigate to: **mk-labs vault** → **the-seas** item +2. Add a new field: + - **Label:** `registry-htpasswd` + - **Type:** password/text field + - **Value:** The full htpasswd line from Step 1 + - Example format: `harbor_registry_user:$2b$10$abcd1234...` + +### Step 3: Verify the configuration + +The ExternalSecret has been updated to pull both fields: +```yaml +- secretKey: REGISTRY_PASSWD + remoteRef: + key: the-seas + property: registry-password + +- secretKey: REGISTRY_HTPASSWD + remoteRef: + key: the-seas + property: registry-htpasswd +``` + +### Step 4: Apply and verify + +After adding the field to 1Password: + +```bash +# The ExternalSecret will automatically sync within 1h, or force sync: +kubectl delete externalsecret harbor-credentials -n harbor +kubectl apply -f /home/hermes/git/homelab/cluster/platform/harbor/externalsecret.yaml + +# Verify the secret contains both keys: +kubectl get secret harbor-credentials -n harbor -o jsonpath='{.data}' | jq 'keys' +``` + +Expected output should include both: +- `REGISTRY_PASSWD` +- `REGISTRY_HTPASSWD` + +## Verification Commands + +```bash +# Check if secret exists and has correct keys +kubectl get secret harbor-credentials -n harbor -o json | jq '.data | keys' + +# Verify REGISTRY_HTPASSWD format (should show username:$2b$...) +kubectl get secret harbor-credentials -n harbor -o jsonpath='{.data.REGISTRY_HTPASSWD}' | base64 -d + +# Check Harbor registry pod logs for authentication success +kubectl logs -n harbor -l component=registry --tail=50 +``` + +## Troubleshooting + +**ExternalSecret not syncing:** +- Check ExternalSecret status: `kubectl describe externalsecret harbor-credentials -n harbor` +- Verify 1Password Connect is running: `kubectl get pods -n external-secrets` +- Check that field name matches exactly: `registry-htpasswd` (lowercase, hyphen) + +**Hash format issues:** +- Ensure bcrypt hash starts with `$2b$` or `$2a$` +- Verify full line includes username: `harbor_registry_user:` +- No extra whitespace or newlines in the 1Password field + +**Registry pod still failing:** +- Verify Harbor values.yaml references the correct existingSecret name +- Check registry pod environment variables contain both keys +- Review registry pod logs for specific authentication errors + +## Files Modified + +- `/home/hermes/git/homelab/cluster/platform/harbor/externalsecret.yaml` + - Added REGISTRY_HTPASSWD secretKey mapping + - Updated documentation comment with new field name + +## Reference + +Harbor Helm Chart documentation: +https://github.com/goharbor/harbor-helm/blob/main/values.yaml + +Search for "REGISTRY_HTPASSWD" to see the requirement in the values.yaml file. diff --git a/cluster/platform/harbor/generate-registry-htpasswd.py b/cluster/platform/harbor/generate-registry-htpasswd.py new file mode 100755 index 0000000..88af59d --- /dev/null +++ b/cluster/platform/harbor/generate-registry-htpasswd.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Generate bcrypt htpasswd entry for Harbor registry credentials. + +Usage: + ./generate-registry-htpasswd.py + +This will prompt for the registry password and output the properly formatted +htpasswd line to add to 1Password as the 'registry-htpasswd' field. +""" + +import sys +import getpass + +try: + import bcrypt +except ImportError: + print("ERROR: bcrypt module not installed.") + print("Install with: pip3 install bcrypt") + sys.exit(1) + +def generate_htpasswd(username, password): + """Generate bcrypt htpasswd entry.""" + # Using cost factor 10 (2^10 = 1024 rounds) - standard bcrypt setting + hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=10)) + return f"{username}:{hashed.decode('utf-8')}" + +def main(): + username = "harbor_registry_user" + + print("=" * 70) + print("Harbor Registry HTPASSWD Generator") + print("=" * 70) + print() + print(f"Username: {username} (fixed)") + print() + + # Get password securely + password = getpass.getpass("Enter registry password: ") + + if not password: + print("ERROR: Password cannot be empty") + sys.exit(1) + + # Confirm password + password_confirm = getpass.getpass("Confirm password: ") + + if password != password_confirm: + print("ERROR: Passwords do not match") + sys.exit(1) + + print() + print("Generating bcrypt hash...") + htpasswd_entry = generate_htpasswd(username, password) + + print() + print("=" * 70) + print("SUCCESS! Copy the line below to 1Password:") + print("=" * 70) + print() + print(htpasswd_entry) + print() + print("=" * 70) + print() + print("Next steps:") + print("1. Copy the line above (entire line including username and hash)") + print("2. Open 1Password → mk-labs vault → the-seas item") + print("3. Add new field:") + print(" - Label: registry-htpasswd") + print(" - Value: ") + print("4. Save the 1Password item") + print("5. ExternalSecret will sync within 1 hour (or force delete/recreate)") + print() + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\nOperation cancelled.") + sys.exit(1) + except Exception as e: + print(f"ERROR: {e}", file=sys.stderr) + sys.exit(1) diff --git a/cluster/platform/harbor/values.yaml b/cluster/platform/harbor/values.yaml index 9399f2f..8598684 100644 --- a/cluster/platform/harbor/values.yaml +++ b/cluster/platform/harbor/values.yaml @@ -8,12 +8,11 @@ externalURL: https://the-seas.local.mk-labs.cloud # Expose Harbor via ClusterIP (Gateway API handles ingress) +# TLS termination handled by Gateway API, not Harbor expose: type: clusterIP tls: - enabled: true - # TLS termination handled by Gateway, not ingress - certSource: none + enabled: false clusterIP: name: the-seas ports: