Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions charts/ztvp-certificates/files/extract-certificates.sh.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -342,11 +342,18 @@ fi
{{- if .Values.proxyCA.enabled }}
log "Creating proxy CA ConfigMap for cluster trustedCA injection"

# Build a bundle with ONLY the custom CAs (ingress + service).
# The Cluster Network Operator automatically merges these with system CAs.
# Build the proxy CA bundle with ALL extracted custom CAs so that workloads
# trusting the injected bundle can reach both cluster-internal services
# (via ingress/service CA) and external hosts behind corporate CAs
# (via additionalCertificates / customCA).
# The Cluster Network Operator merges these with the system (Mozilla) CAs.
> "${TEMP_DIR}/proxy-ca-bundle.pem"
for cert_file in "${TEMP_DIR}"/ingress-ca-*.crt "${TEMP_DIR}/service-ca.crt"; do
for cert_file in "${TEMP_DIR}"/ingress-ca-*.crt "${TEMP_DIR}/service-ca.crt" "${TEMP_DIR}/custom-ca.crt" "${TEMP_DIR}"/*.crt; do
[[ -f "$cert_file" ]] || continue
# Deduplicate: skip if already appended (the *.crt glob may re-match earlier files)
if grep -qF "$(head -2 "$cert_file")" "${TEMP_DIR}/proxy-ca-bundle.pem" 2>/dev/null; then
continue
fi
cat "$cert_file" >> "${TEMP_DIR}/proxy-ca-bundle.pem"
echo "" >> "${TEMP_DIR}/proxy-ca-bundle.pem"
done
Expand Down Expand Up @@ -384,8 +391,38 @@ CURRENT_TRUSTED_CA=$(oc get proxy/cluster -o jsonpath='{.spec.trustedCA.name}' 2
if [[ "$CURRENT_TRUSTED_CA" == "{{ .Values.proxyCA.configMapName }}" ]]; then
log "Proxy trustedCA already set to {{ .Values.proxyCA.configMapName }}, skipping"
elif [[ -n "$CURRENT_TRUSTED_CA" && "$CURRENT_TRUSTED_CA" != "{{ .Values.proxyCA.configMapName }}" ]]; then
log "WARNING: Proxy trustedCA is already set to '$CURRENT_TRUSTED_CA' (not overwriting)"
log "WARNING: To use ZTVP proxy CA, manually run: oc patch proxy/cluster --type=merge -p '{\"spec\":{\"trustedCA\":{\"name\":\"{{ .Values.proxyCA.configMapName }}\"}}}}'"
# Merge any CAs from the existing proxy ConfigMap into our bundle so
# nothing is lost when we take over trustedCA management.
log "Existing proxy trustedCA ConfigMap: $CURRENT_TRUSTED_CA"
EXISTING_CA_DATA=$(oc get configmap "$CURRENT_TRUSTED_CA" -n {{ .Values.global.namespace }} \
-o jsonpath='{.data.ca-bundle\.crt}' 2>/dev/null || echo "")
if [[ -n "$EXISTING_CA_DATA" ]]; then
echo "$EXISTING_CA_DATA" > "${TEMP_DIR}/existing-proxy-ca.crt"
# Append any certs not already in our bundle
while IFS= read -r line; do
echo "$line"
done < "${TEMP_DIR}/existing-proxy-ca.crt" >> "${TEMP_DIR}/proxy-ca-bundle.pem"
PROXY_BUNDLE_SIZE=$(wc -c < "${TEMP_DIR}/proxy-ca-bundle.pem" 2>/dev/null || echo 0)
log "Merged existing proxy CAs into {{ .Values.proxyCA.configMapName }}"
# Re-create the ConfigMap with merged content
cat <<MERGEEOF | oc apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.proxyCA.configMapName }}
namespace: {{ .Values.global.namespace }}
labels:
{{- range $key, $value := .Values.configMapLabels }}
{{ $key }}: {{ $value | quote }}
{{- end }}
data:
ca-bundle.crt: |
$(cat "${TEMP_DIR}/proxy-ca-bundle.pem" | sed 's/^/ /')
MERGEEOF
fi
log "Updating proxy/cluster trustedCA from '$CURRENT_TRUSTED_CA' to {{ .Values.proxyCA.configMapName }}"
oc patch proxy/cluster --type=merge -p '{"spec":{"trustedCA":{"name":"{{ .Values.proxyCA.configMapName }}"}}}'
log "OK: Proxy trustedCA configured (previous: $CURRENT_TRUSTED_CA)"
else
if [[ $PROXY_BUNDLE_SIZE -gt 100 ]]; then
log "Setting proxy/cluster trustedCA to {{ .Values.proxyCA.configMapName }}"
Expand Down
253 changes: 253 additions & 0 deletions docs/private-repos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Deploying from a Private Repository

This document describes how to deploy the Layered Zero Trust Validated Pattern
from a private Git repository.

The Validated Patterns framework supports deploying from both SSH-secured and
HTTPS-secured (PAT) private repositories. The mechanism works by creating an
ArgoCD repository secret **before** the pattern is deployed, so that the VP
operator can propagate credentials to all ArgoCD instances managed by the
pattern.

> [!NOTE]
> The upstream documentation is at
> <https://validatedpatterns.io/learn/private-repos/>. This page provides
> ZTVP-specific guidance that builds on the framework docs.

## Prerequisites

* An OpenShift 4.16+ cluster with `oc` CLI access
* A fork or private copy of this repository
* A deploy key (SSH) or Personal Access Token (HTTPS) with **read** access

> [!IMPORTANT]
> The git remote URL in your local clone **must match** the auth type in
> your `bootstrap_secrets`. The Makefile passes the remote URL to the
> Pattern CR verbatim when `TOKEN_SECRET` is set:
>
> * SSH auth: remote must be `git@host:org/repo.git`
> * HTTPS/PAT auth: remote must be `https://host/org/repo.git`
>
> Set with: `git remote set-url origin <matching-url>`

## Option A: SSH Key Authentication

### 1. Generate a deploy key

```shell
ssh-keygen -t ed25519 -f ~/.ssh/ztvp-deploy-key -N ""
```

### 2. Register the public key

Add `~/.ssh/ztvp-deploy-key.pub` as a **deploy key** in your Git hosting
provider (GitHub Settings -> Deploy keys, GitLab Settings -> Repository ->
Deploy keys, etc.).

### 3. Configure values-secret

Copy the template and uncomment the SSH `bootstrap_secrets` block:

```shell
cp values-secret.yaml.template ~/values-secret.yaml
```

Edit `~/values-secret.yaml` and uncomment **Option A**
under the "BOOTSTRAP SECRETS" section. Update the `url` field with your
repository's SSH URL:

```yaml
bootstrap_secrets:
- name: private-repo
targetNamespaces:
- openshift-operators
labels:
argocd.argoproj.io/secret-type: repository
fields:
- name: type
value: git
- name: url
value: git@github.com:YOUR-ORG/layered-zero-trust.git
- name: insecureIgnoreHostKey
value: "true"
- name: sshPrivateKey
path: ~/.ssh/ztvp-deploy-key
```

### 4. Deploy

```shell
./pattern.sh make TOKEN_SECRET=private-repo TOKEN_NAMESPACE=openshift-operators install
```

## Option B: HTTPS with Personal Access Token (PAT)

### 1. Create a PAT

* **GitHub:** Settings -> Developer settings -> Personal access tokens ->
Fine-grained tokens. Grant **Contents: Read** on the target repository.
* **GitLab:** Settings -> Access Tokens. Grant **Reporter** role with
`read_repository` scope (Guest role is insufficient to clone code).

Store the token in a local file:

```shell
mkdir -p ~/.config/validated-patterns
echo -n "ghp_xxxxxxxxxxxxxxxxxxxx" > ~/.config/validated-patterns/git-pat
chmod 600 ~/.config/validated-patterns/git-pat
```

### 2. Configure values-secret

Copy the template and uncomment the HTTPS `bootstrap_secrets` block:

```shell
cp values-secret.yaml.template ~/values-secret.yaml
```

Edit `~/values-secret.yaml` and uncomment **Option B**
under the "BOOTSTRAP SECRETS" section. Update the `url`, `username`, and
`password` path:

```yaml
bootstrap_secrets:
- name: private-repo
targetNamespaces:
- openshift-operators
labels:
argocd.argoproj.io/secret-type: repository
fields:
- name: type
value: git
- name: url
value: https://github.com/YOUR-ORG/layered-zero-trust.git
- name: username
value: YOUR-USERNAME
- name: password
path: ~/.config/validated-patterns/git-pat
```

> [!NOTE]
> For GitLab, the `username` must be `oauth2`, not your GitLab handle.

### 3. Deploy

```shell
./pattern.sh make TOKEN_SECRET=private-repo TOKEN_NAMESPACE=openshift-operators install
```

## Skipping Origin Validation

The Makefile performs a pre-flight `git ls-remote` check against the HTTPS
form of the repository URL. For private repos this check will fail (or
prompt for credentials) because the local machine does not have HTTPS
credentials configured. Skip the check by setting:

```shell
./pattern.sh make DISABLE_VALIDATE_ORIGIN=true \
TOKEN_SECRET=private-repo TOKEN_NAMESPACE=openshift-operators install
```

This is safe -- the cluster uses the `private-repo` secret (SSH key or PAT)
for actual access; the validation is only a local convenience check.

## How It Works

1. The `bootstrap_secrets` section in `values-secret.yaml` instructs the
Validated Patterns framework to create the `private-repo` Kubernetes
Secret in the `openshift-operators` namespace **before** deploying the
pattern.

2. The `argocd.argoproj.io/secret-type: repository` label tells ArgoCD to
pick up the secret as a repository credential.

3. The `TOKEN_SECRET` and `TOKEN_NAMESPACE` Make variables set the
`tokenSecret` and `tokenSecretNamespace` fields on the Pattern Custom
Resource. The VP operator copies the secret as
`vp-private-repo-credentials` into `vp-gitops` (its managed ArgoCD
namespace).

4. The ACM chart (0.2.x+) `vp-private-hub-policy` copies credentials from
`global.vpArgoNamespace`, which the VP operator automatically sets to
`vp-gitops`. This allows the policy to find the secret the VP operator
placed there without any manual override.

## Verifying

After deployment, confirm the repository secret was created:

```shell
oc get secret private-repo -n openshift-operators \
-o jsonpath='{.metadata.labels.argocd\.argoproj\.io/secret-type}'
```

Expected output: `repository`

Confirm the VP operator propagated the credential to `vp-gitops`:

```shell
oc get secret vp-private-repo-credentials -n vp-gitops \
-o jsonpath='{.metadata.labels.argocd\.argoproj\.io/secret-type}'
```

Expected output: `repository`

Check the Cluster ArgoCD can see the repository:

```shell
oc get application layered-zero-trust-hub -n vp-gitops \
-o jsonpath='{.status.sync.status}'
```

Expected output: `Synced` (or `OutOfSync` if you have uncommitted changes).

## Troubleshooting

* **ACM shows Degraded (vp-private-hub-policy NonCompliant)** -- The ACM
chart 0.1.x has `openshift-gitops` hardcoded in the private-repo policy
template, but the VP operator (0.0.70+) places credentials in
`vp-gitops`. Ensure `values-hub.yaml` uses ACM chart 0.2.x or later
(`chartVersion: 0.2.*`), which reads `global.vpArgoNamespace` -- a value
the VP operator sets automatically.

* **ArgoCD shows "repository not accessible"** -- Verify the SSH key or PAT
has read access. For SSH, confirm the key has no passphrase (`ssh-keygen
-y -f ~/.ssh/ztvp-deploy-key` should not prompt).

* **SSH: "knownhosts: key is unknown"** -- The `insecureIgnoreHostKey: "true"`
field is missing from the bootstrap secret. The ArgoCD repo-server runs
in a container without your Git host's fingerprint in known_hosts.

* **HTTPS: "x509: certificate signed by unknown authority"** -- This
affects internal/self-hosted GitLab instances whose TLS certificates are
signed by a corporate CA. GitHub and public GitLab (`gitlab.com`) use
publicly trusted CAs and do not require this step.

The corporate CA must be in the cluster trust store **before** install
because the VP operator needs it to clone the repository. Add the internal CA
as a pre-install step:

```shell
oc create configmap custom-ca -n openshift-config \
--from-file=ca-bundle.crt=/path/to/corporate-ca-bundle.pem
oc patch proxy/cluster --type=merge \
-p '{"spec":{"trustedCA":{"name":"custom-ca"}}}'
```

Wait a few minutes for operator pods to restart with the updated bundle.

> [!NOTE]
> After the pattern deploys, the `ztvp-certificates` chart automatically
> merges your `custom-ca` content into its managed `ztvp-proxy-ca`
> ConfigMap and switches `proxy/cluster.spec.trustedCA` to
> `ztvp-proxy-ca`. This adds the cluster ingress and service CAs so
> that workloads like ACS Central can reach Keycloak without additional
> manual steps. You do **not** need to manually add the ingress CA to
> your `custom-ca`.

* **Secret not found during install** -- Ensure you ran
`./pattern.sh make load-secrets` *after* the bootstrap secret was created.
The `TOKEN_SECRET` and `TOKEN_NAMESPACE` values must match exactly.

* **GitLab HTTPS fails** -- Remember that GitLab PAT auth requires
`username: oauth2`, not your GitLab user handle.
2 changes: 1 addition & 1 deletion values-hub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ clusterGroup:
namespace: open-cluster-management
project: hub
chart: acm
chartVersion: 0.1.*
chartVersion: 0.2.*
annotations:
argocd.argoproj.io/sync-wave: "5"
ignoreDifferences:
Expand Down
66 changes: 66 additions & 0 deletions values-secret.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,69 @@ secrets:
# fields:
# - name: content
# path: ~/.kube/kubeconfig-ztvp-spoke-2

# ===========================================================================
# BOOTSTRAP SECRETS - Private Repository Access
# ===========================================================================
# Uncomment ONE of the sections below if deploying this pattern from a
# private git repository. These secrets are created directly as Kubernetes
# Secrets (not via Vault) so that ArgoCD can authenticate to the private
# repository before any other component is deployed.
#
# After uncommenting and filling in the values, deploy with:
# ./pattern.sh make TOKEN_SECRET=private-repo TOKEN_NAMESPACE=openshift-operators install
#
# See docs/private-repos.md for full instructions.
# ===========================================================================

# --- OPTION A: SSH key authentication ---
# Generate a passwordless deploy key:
# ssh-keygen -t ed25519 -f ~/.ssh/ztvp-deploy-key -N ""
# Add the public key (~/.ssh/ztvp-deploy-key.pub) as a deploy key in
# your Git hosting provider (GitHub, GitLab, etc.)
#
# insecureIgnoreHostKey is required because the ArgoCD repo-server runs
# in a container that does not have the Git host's SSH fingerprint in its
# known_hosts file. Without this flag, SSH connections fail with
# "knownhosts: key is unknown".
#
# The VP operator copies this secret as vp-private-repo-credentials into
# vp-gitops (its managed ArgoCD namespace). The ACM chart (0.2.x+) reads
# global.vpArgoNamespace which the VP operator sets automatically.

#bootstrap_secrets:
#- name: private-repo
# targetNamespaces:
# - openshift-operators
# labels:
# argocd.argoproj.io/secret-type: repository
# fields:
# - name: type
# value: git
# - name: url
# value: git@github.com:YOUR-ORG/layered-zero-trust.git
# - name: insecureIgnoreHostKey
# value: "true"
# - name: sshPrivateKey
# path: ~/.ssh/ztvp-deploy-key

# --- OPTION B: HTTPS with Personal Access Token (PAT) ---
# Create a PAT with read access to your repository.
# For GitHub: Settings -> Developer settings -> Personal access tokens
# For GitLab: Settings -> Access Tokens (username must be "oauth2")

#bootstrap_secrets:
#- name: private-repo
# targetNamespaces:
# - openshift-operators
# labels:
# argocd.argoproj.io/secret-type: repository
# fields:
# - name: type
# value: git
# - name: url
# value: https://github.com/YOUR-ORG/layered-zero-trust.git
# - name: username
# value: YOUR-USERNAME
# - name: password
# path: ~/.config/validated-patterns/git-pat
Loading