Installation Manual - 19 Greenfield HTPasswd Admin Identity

How the temporary greenfield HTPasswd cluster-admin identity is configured and validated on hub-dc-v7 and spoke-dc-v7.

This chapter configures a temporary HTPasswd identity provider for the greenfield OpenShift clusters after the hub and spoke clusters are installed and reachable.

Use this as the first human-login path before removing kubeadmin. Do not remove kubeadmin until this login path is tested on each cluster and has remained stable through OAuth rollout.

Target State

ItemValue
Clustershub-dc-v7, spoke-dc-v7
Userze
OAuth providerhtpasswd-ze
Secret namespaceopenshift-config
Secret namehtpasswd-ze
Rolecluster-admin
Password/hash storagelocal-only operator custody, never Git

Prerequisites

Run from gf-ocp-bootstrap-01 or from the operator workstation with SSH access to it.

The cluster kubeconfigs must exist on gf-ocp-bootstrap-01:

/home/ze/ocp-greenfield-deployment/artifacts/openshift/hub-dc-v7/auth/kubeconfig
/home/ze/ocp-greenfield-deployment/artifacts/openshift/spoke-dc-v7/auth/kubeconfig

The operator must have the intended ze password from secure local custody. Do not put the password in Git, issue comments, shell history, chat, or this manual.

Create Local-Only HTPasswd Files

Create one htpasswd file per cluster under the local ignored secrets/ directory in the operator workspace.

Use a command pattern that avoids echoing the password. The example below expects the password to be supplied interactively or from a local-only secure source.

cd /home/ze/codex-opp-agent
umask 077
mkdir -p secrets

read -rsp "HTPasswd password for ze: " ZE_HTPASSWD_PASSWORD
printf '\n'

for cluster in hub-dc-v7 spoke-dc-v7; do
  salt="$(openssl rand -base64 9 | tr -dc 'A-Za-z0-9' | head -c 8)"
  hash="$(printf '%s' "$ZE_HTPASSWD_PASSWORD" \
    | openssl passwd -apr1 -stdin -salt "$salt")"
  printf 'ze:%s\n' "$hash" > "secrets/${cluster}-ze-htpasswd"
  chmod 600 "secrets/${cluster}-ze-htpasswd"
done

unset ZE_HTPASSWD_PASSWORD hash salt

Do not print the generated htpasswd files. They contain password hashes and must remain local-only.

Create OpenShift Secrets

Copy each local htpasswd file to a temporary path on gf-ocp-bootstrap-01, create the OpenShift Secret, and remove the temporary file immediately.

cd /home/ze/codex-opp-agent

for cluster in hub-dc-v7 spoke-dc-v7; do
  remote="/tmp/${cluster}-ze-htpasswd.$$"

  scp "secrets/${cluster}-ze-htpasswd" \
    "ze@30.30.200.60:${remote}"

  ssh ze@30.30.200.60 "
    set -euo pipefail
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig

    oc -n openshift-config create secret generic htpasswd-ze \
      --from-file=htpasswd=${remote} \
      --dry-run=client -o yaml | oc apply -f -

    rm -f ${remote}
  "
done

Patch OAuth

Patch oauth/cluster on both clusters to use the htpasswd-ze provider.

for cluster in hub-dc-v7 spoke-dc-v7; do
  ssh ze@30.30.200.60 "
    set -euo pipefail
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig

    oc patch oauth cluster --type=merge -p '{
      \"spec\": {
        \"identityProviders\": [
          {
            \"name\": \"htpasswd-ze\",
            \"mappingMethod\": \"claim\",
            \"type\": \"HTPasswd\",
            \"htpasswd\": {
              \"fileData\": {
                \"name\": \"htpasswd-ze\"
              }
            }
          }
        ]
      }
    }'
  "
done

This replaces the current identity provider list. If a future cluster already has production identity providers, merge the provider deliberately instead of blindly replacing the list.

Grant Cluster Admin

Grant cluster-admin to ze on both clusters.

for cluster in hub-dc-v7 spoke-dc-v7; do
  ssh ze@30.30.200.60 "
    set -euo pipefail
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig

    oc adm policy add-cluster-role-to-user cluster-admin ze
  "
done

OpenShift may warn that user ze is not found before the first login. That is expected. The RBAC binding is still created.

Wait For OAuth Rollout

Wait until authentication is healthy and OAuth pods are Running.

for cluster in hub-dc-v7 spoke-dc-v7; do
  ssh ze@30.30.200.60 "
    set -euo pipefail
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig

    oc get co authentication
    oc -n openshift-authentication get pods
  "
done

Expected state:

  • authentication ClusterOperator is Available=True, Progressing=False, Degraded=False;
  • OAuth pods in openshift-authentication are Running.

Validate Login And RBAC

Use a temporary kubeconfig for validation so the generated token is not stored in the repository.

read -rsp "HTPasswd password for ze: " ZE_HTPASSWD_PASSWORD
printf '\n'

for cluster in hub-dc-v7 spoke-dc-v7; do
  server="$(ssh ze@30.30.200.60 "
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig
    oc whoami --show-server
  ")"

  tmp="$(mktemp)"
  KUBECONFIG="$tmp" oc login "$server" \
    -u ze \
    -p "$ZE_HTPASSWD_PASSWORD" \
    --insecure-skip-tls-verify=true

  KUBECONFIG="$tmp" oc whoami
  KUBECONFIG="$tmp" oc auth can-i '*' '*' --all-namespaces

  rm -f "$tmp"
done

unset ZE_HTPASSWD_PASSWORD

Expected output for each cluster:

ze
yes

Final Validation

Validate the provider, Secret, cluster-admin binding, and operator health without printing Secret data.

for cluster in hub-dc-v7 spoke-dc-v7; do
  ssh ze@30.30.200.60 "
    set -euo pipefail
    export KUBECONFIG=/home/ze/ocp-greenfield-deployment/artifacts/openshift/${cluster}/auth/kubeconfig

    echo \"== ${cluster} ==\"

    oc get oauth cluster \
      -o jsonpath='{range .spec.identityProviders[*]}{.name}{\"|\"}{.type}{\"\\n\"}{end}'

    oc -n openshift-config get secret htpasswd-ze \
      -o custom-columns=NAME:.metadata.name,TYPE:.type --no-headers

    oc get clusterrolebinding -o json \
      | jq -r '.items[]
        | select(.roleRef.name==\"cluster-admin\")
        | select(any(.subjects[]?; .kind==\"User\" and .name==\"ze\"))
        | .metadata.name'

    oc get co --no-headers \
      | awk '\$3!=\"True\" || \$4!=\"False\" || \$5!=\"False\" {print}'
  "
done

Expected state:

  • OAuth provider htpasswd-ze|HTPasswd exists on both clusters;
  • Secret htpasswd-ze exists in openshift-config on both clusters;
  • a cluster-admin ClusterRoleBinding for ze exists on both clusters;
  • the non-steady ClusterOperator command returns no output.

Kubeadmin Removal

Do not remove kubeadmin in this chapter.

Remove kubeadmin only in a later hardening gate after:

  • ze login is validated again;
  • ze can perform cluster-admin actions;
  • the operator has a second break-glass access plan or an explicit reinstall-only policy.

Completion State

For the recorded greenfield installation:

  • issue: OP-GF-IDP-1;
  • OAuth provider: htpasswd-ze;
  • user: ze;
  • role: cluster-admin;
  • clusters:
    • hub-dc-v7;
    • spoke-dc-v7;
  • validation:
    • real ze login succeeded on both clusters;
    • oc auth can-i '*' '*' --all-namespaces returned yes on both clusters;
    • no non-steady ClusterOperators were reported after rollout.

Last reviewed: 2026-05-16