From 121f35e41705733db60de6ace7bbf0a726b6c4dd Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Fri, 24 Apr 2026 09:16:32 -0400 Subject: [PATCH 1/4] Add Git auth for AGOF --- Chart.yaml | 2 +- README.md | 8 +- README.md.gotmpl | 3 + templates/_helpers.tpl | 97 ++++++++++++++++++- .../external-secrets-validation-job.yaml | 3 + templates/secret-agof-gitauth.yaml | 22 +++++ values.yaml | 14 +++ 7 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 templates/secret-agof-gitauth.yaml diff --git a/Chart.yaml b/Chart.yaml index aaa7279..4be6730 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -3,7 +3,7 @@ description: A Helm chart to build and deploy secrets using external-secrets for keywords: - pattern name: aap-config -version: 0.2.0 +version: 0.2.1 dependencies: - name: vp-rbac version: '0.1.*' diff --git a/README.md b/README.md index 2db22c1..630d29a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # aap-config -![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) +![Version: 0.2.1](https://img.shields.io/badge/Version-0.2.1-informational?style=flat-square) A Helm chart to build and deploy secrets using external-secrets for ansible-edge-gitops @@ -27,6 +27,9 @@ namespaces from external secrets validation. To use this version, you will also need to update your pattern to use the `openshift-external-secrets-operator` and `openshift-external-secrets` helm chart. +* v0.2.1: Support credential (HTTPS or SSH) injection for git client in AGOF config +jobs. + ## Requirements | Repository | Name | Version | @@ -42,6 +45,9 @@ To use this version, you will also need to update your pattern to use the | agof.agof_revision | string | `"v2"` | | | agof.automationHubTokenKey | string | `"secret/data/hub/automation-hub-token"` | | | agof.extraPlaybookOpts | string | `""` | | +| agof.gitAuthHttpsStyle | string | `"auto"` | | +| agof.gitAuthSecret | string | `""` | | +| agof.gitAuthVaultKey | string | `""` | | | agof.iac_repo | string | `"https://github.com/validatedpatterns-demos/ansible-edge-gitops-hmi-config-as-code.git"` | | | agof.iac_revision | string | `"main"` | | | agof.vaultFileKey | string | `"secret/data/hub/agof-vault-file"` | | diff --git a/README.md.gotmpl b/README.md.gotmpl index 60b9a6d..4785605 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -28,6 +28,9 @@ namespaces from external secrets validation. To use this version, you will also need to update your pattern to use the `openshift-external-secrets-operator` and `openshift-external-secrets` helm chart. +* v0.2.1: Support credential (HTTPS or SSH) injection for git client in AGOF config +jobs. + {{ template "chart.homepageLine" . }} {{ template "chart.maintainersSection" . }} diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 40de043..c743173 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -8,6 +8,11 @@ volumes: - name: agof-vault-file secret: secretName: agof-vault-file +{{- if $.Values.agof.gitAuthSecret }} + - name: agof-git-auth + secret: + secretName: {{ $.Values.agof.gitAuthSecret | quote }} +{{- end }} initContainers: - name: agof-init image: {{ .Values.configJob.image }} @@ -19,15 +24,99 @@ initContainers: command: - /bin/bash - -c - - > - base64 -d /pattern-home/agof-vault-file/agof-vault-file > ~/agof_vault.yml && - git clone --recurse-submodules --single-branch --branch "{{ .Values.agof.agof_revision }}" - -- "{{ .Values.agof.agof_repo }}" /pattern-home/agof_repo + - | + set -euo pipefail + export GIT_TERMINAL_PROMPT=0 + agof_repo_url={{ $.Values.agof.agof_repo | quote }} + base64 -d /pattern-home/agof-vault-file/agof-vault-file > ~/agof_vault.yml +{{- if $.Values.agof.gitAuthSecret }} + GIT_AUTH_DIR=/pattern-home/git-auth + if [[ -f "$GIT_AUTH_DIR/.git-credentials" ]]; then + cp "$GIT_AUTH_DIR/.git-credentials" ~/.git-credentials + chmod 600 ~/.git-credentials + git config --global credential.helper store + elif [[ -f "$GIT_AUTH_DIR/ssh-privatekey" ]]; then + mkdir -p ~/.ssh + cp "$GIT_AUTH_DIR/ssh-privatekey" ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + if [[ -f "$GIT_AUTH_DIR/known_hosts" ]]; then + cp "$GIT_AUTH_DIR/known_hosts" ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes' + else + export GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new' + fi + else + agof_git_host_from_url() { + local u="$1" + if [[ "$u" =~ ^https?://([^/@]+) ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ "$u" =~ ^https?://[^@]+@([^/]+) ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ "$u" =~ ^git@([^:]+): ]]; then + echo "${BASH_REMATCH[1]}" + fi + } + agof_https_store_credential() { + local host="$1" user="$2" pass="$3" + git config --global credential.helper store + printf 'protocol=https\nhost=%s\nusername=%s\npassword=%s\n\n' "$host" "$user" "$pass" | git credential approve + } + agof_https_user_for_style() { + local host_lc style + host_lc=$(echo "$1" | tr '[:upper:]' '[:lower:]') + style="$2" + case "$style" in + github) printf '%s' 'git' ;; + gitlab) printf '%s' 'oauth2' ;; + gitea) printf '%s' 'oauth2' ;; + auto) + if [[ "$host_lc" == *"github.com"* ]]; then printf '%s' 'git' + elif [[ "$host_lc" == *"gitlab"* ]]; then printf '%s' 'oauth2' + elif [[ "$host_lc" == *"gitea"* ]] || [[ "$host_lc" == *"forgejo"* ]] || [[ "$host_lc" == *"codeberg"* ]]; then printf '%s' 'oauth2' + else printf '%s' 'git' + fi + ;; + *) printf '%s' 'git' ;; + esac + } + host="$(agof_git_host_from_url "$agof_repo_url")" + https_style={{ default "auto" $.Values.agof.gitAuthHttpsStyle | quote }} + if [[ -f "$GIT_AUTH_DIR/username" ]] && { [[ -f "$GIT_AUTH_DIR/password" ]] || [[ -f "$GIT_AUTH_DIR/token" ]]; }; then + if [[ -z "$host" ]]; then + echo "agof.gitAuthSecret: could not parse git host from agof_repo for HTTPS credentials" >&2 + exit 1 + fi + u=$(cat "$GIT_AUTH_DIR/username") + if [[ -f "$GIT_AUTH_DIR/token" ]]; then + p=$(cat "$GIT_AUTH_DIR/token") + else + p=$(cat "$GIT_AUTH_DIR/password") + fi + agof_https_store_credential "$host" "$u" "$p" + elif [[ -f "$GIT_AUTH_DIR/token" ]] && [[ ! -f "$GIT_AUTH_DIR/username" ]]; then + if [[ -z "$host" ]]; then + echo "agof.gitAuthSecret: could not parse git host from agof_repo for HTTPS token" >&2 + exit 1 + fi + p=$(cat "$GIT_AUTH_DIR/token") + u="$(agof_https_user_for_style "$host" "$https_style")" + agof_https_store_credential "$host" "$u" "$p" + fi + fi +{{- end }} + git clone --recurse-submodules --single-branch --branch "{{ $.Values.agof.agof_revision }}" \ + -- "$agof_repo_url" /pattern-home/agof_repo volumeMounts: - name: agof-scratch-space mountPath: /pattern-home - name: agof-vault-file mountPath: /pattern-home/agof-vault-file +{{- if $.Values.agof.gitAuthSecret }} + - name: agof-git-auth + mountPath: /pattern-home/git-auth + readOnly: true +{{- end }} containers: - name: agof-config image: {{ .Values.configJob.image }} diff --git a/templates/external-secrets-validation-job.yaml b/templates/external-secrets-validation-job.yaml index 931b335..cebe43c 100644 --- a/templates/external-secrets-validation-job.yaml +++ b/templates/external-secrets-validation-job.yaml @@ -32,6 +32,9 @@ spec: "aap-manifest" "agof-vault-file" "automation-hub-token" +{{- if and $.Values.agof.gitAuthSecret $.Values.agof.gitAuthVaultKey }} + "{{ $.Values.agof.gitAuthSecret }}" +{{- end }} ) # Function to check if a secret exists and has data diff --git a/templates/secret-agof-gitauth.yaml b/templates/secret-agof-gitauth.yaml new file mode 100644 index 0000000..aadc26b --- /dev/null +++ b/templates/secret-agof-gitauth.yaml @@ -0,0 +1,22 @@ +{{- if and $.Values.agof.gitAuthVaultKey (not $.Values.agof.gitAuthSecret) -}} +{{- fail "agof.gitAuthVaultKey requires agof.gitAuthSecret (Kubernetes Secret / ExternalSecret name)" -}} +{{- end }} +{{- if and $.Values.agof.gitAuthSecret $.Values.agof.gitAuthVaultKey }} +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ $.Values.agof.gitAuthSecret }} + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ $.Values.secretStore.name }} + kind: {{ $.Values.secretStore.kind }} + target: + name: {{ $.Values.agof.gitAuthSecret }} + dataFrom: + - extract: + key: {{ $.Values.agof.gitAuthVaultKey }} +{{- end }} diff --git a/values.yaml b/values.yaml index 0f94b85..312bb6b 100644 --- a/values.yaml +++ b/values.yaml @@ -13,6 +13,20 @@ agof: agof_repo: https://github.com/validatedpatterns/agof.git agof_revision: v2 + # Optional: name of an existing Secret (same namespace) whose keys are mounted for + # cloning agof_repo in the agof-init container. Use one of: + # - .git-credentials: full git-credential-store line(s) for HTTPS + # - ssh-privatekey (+ optional known_hosts): SSH private key (e.g. kubernetes.io/ssh-auth) + # - HTTPS: username + (password or token), or token-only with gitAuthHttpsStyle below + gitAuthSecret: '' + # Optional Vault path: when set (with gitAuthSecret), an ExternalSecret is created that uses + # dataFrom.extract to copy all fields from that Vault secret into the Kubernetes Secret named + # gitAuthSecret (same pattern as vaultFileKey / agof-vault-file). + gitAuthVaultKey: '' + # When the Secret has only a "token" key: how to pick the HTTPS username (PAT / OAuth). + # auto: from agof_repo host (github.com -> git; gitlab / gitea / forgejo / codeberg -> oauth2) + # github|gitlab|gitea: force that platform's usual clone user (git / oauth2 / oauth2) + gitAuthHttpsStyle: auto iac_repo: https://github.com/validatedpatterns-demos/ansible-edge-gitops-hmi-config-as-code.git iac_revision: main From 9c23f7c4c97f8976e8ecf8568ddf36039365f969 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Fri, 24 Apr 2026 11:21:53 -0400 Subject: [PATCH 2/4] Ensure handling for iac_repo and agof_repo both --- templates/_helpers.tpl | 42 +++++++++++++++++++++++++++++++----------- values.yaml | 5 +++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index c743173..b7bbad9 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -28,6 +28,7 @@ initContainers: set -euo pipefail export GIT_TERMINAL_PROMPT=0 agof_repo_url={{ $.Values.agof.agof_repo | quote }} + iac_repo_url={{ $.Values.agof.iac_repo | quote }} base64 -d /pattern-home/agof-vault-file/agof-vault-file > ~/agof_vault.yml {{- if $.Values.agof.gitAuthSecret }} GIT_AUTH_DIR=/pattern-home/git-auth @@ -55,8 +56,18 @@ initContainers: echo "${BASH_REMATCH[1]}" elif [[ "$u" =~ ^git@([^:]+): ]]; then echo "${BASH_REMATCH[1]}" + elif [[ "$u" =~ ^ssh://[^@]+@([^/:]+) ]]; then + echo "${BASH_REMATCH[1]}" fi } + agof_unique_git_hosts() { + local url h + for url in "$@"; do + [[ -z "$url" ]] && continue + h=$(agof_git_host_from_url "$url") || true + [[ -n "$h" ]] && printf '%s\n' "$h" + done | awk '!x[$0]++' + } agof_https_store_credential() { local host="$1" user="$2" pass="$3" git config --global credential.helper store @@ -80,28 +91,37 @@ initContainers: *) printf '%s' 'git' ;; esac } - host="$(agof_git_host_from_url "$agof_repo_url")" https_style={{ default "auto" $.Values.agof.gitAuthHttpsStyle | quote }} if [[ -f "$GIT_AUTH_DIR/username" ]] && { [[ -f "$GIT_AUTH_DIR/password" ]] || [[ -f "$GIT_AUTH_DIR/token" ]]; }; then - if [[ -z "$host" ]]; then - echo "agof.gitAuthSecret: could not parse git host from agof_repo for HTTPS credentials" >&2 - exit 1 - fi u=$(cat "$GIT_AUTH_DIR/username") if [[ -f "$GIT_AUTH_DIR/token" ]]; then p=$(cat "$GIT_AUTH_DIR/token") else p=$(cat "$GIT_AUTH_DIR/password") fi - agof_https_store_credential "$host" "$u" "$p" - elif [[ -f "$GIT_AUTH_DIR/token" ]] && [[ ! -f "$GIT_AUTH_DIR/username" ]]; then - if [[ -z "$host" ]]; then - echo "agof.gitAuthSecret: could not parse git host from agof_repo for HTTPS token" >&2 + host_any="" + while IFS= read -r host; do + [[ -z "$host" ]] && continue + host_any=1 + agof_https_store_credential "$host" "$u" "$p" + done < <(agof_unique_git_hosts "$agof_repo_url" "$iac_repo_url") + if [[ -z "$host_any" ]]; then + echo "agof.gitAuthSecret: could not parse git host from agof_repo or iac_repo for HTTPS credentials" >&2 exit 1 fi + elif [[ -f "$GIT_AUTH_DIR/token" ]] && [[ ! -f "$GIT_AUTH_DIR/username" ]]; then p=$(cat "$GIT_AUTH_DIR/token") - u="$(agof_https_user_for_style "$host" "$https_style")" - agof_https_store_credential "$host" "$u" "$p" + host_any="" + while IFS= read -r host; do + [[ -z "$host" ]] && continue + host_any=1 + u="$(agof_https_user_for_style "$host" "$https_style")" + agof_https_store_credential "$host" "$u" "$p" + done < <(agof_unique_git_hosts "$agof_repo_url" "$iac_repo_url") + if [[ -z "$host_any" ]]; then + echo "agof.gitAuthSecret: could not parse git host from agof_repo or iac_repo for HTTPS token" >&2 + exit 1 + fi fi fi {{- end }} diff --git a/values.yaml b/values.yaml index 312bb6b..734faaa 100644 --- a/values.yaml +++ b/values.yaml @@ -14,7 +14,8 @@ agof: agof_repo: https://github.com/validatedpatterns/agof.git agof_revision: v2 # Optional: name of an existing Secret (same namespace) whose keys are mounted for - # cloning agof_repo in the agof-init container. Use one of: + # git in the agof-init container (clone agof_repo) and the agof-config container + # (e.g. clone/fetch iac_repo during make). Use one of: # - .git-credentials: full git-credential-store line(s) for HTTPS # - ssh-privatekey (+ optional known_hosts): SSH private key (e.g. kubernetes.io/ssh-auth) # - HTTPS: username + (password or token), or token-only with gitAuthHttpsStyle below @@ -24,7 +25,7 @@ agof: # gitAuthSecret (same pattern as vaultFileKey / agof-vault-file). gitAuthVaultKey: '' # When the Secret has only a "token" key: how to pick the HTTPS username (PAT / OAuth). - # auto: from agof_repo host (github.com -> git; gitlab / gitea / forgejo / codeberg -> oauth2) + # auto: from each repo URL host (github.com -> git; gitlab / gitea / forgejo / codeberg -> oauth2) # github|gitlab|gitea: force that platform's usual clone user (git / oauth2 / oauth2) gitAuthHttpsStyle: auto From 961ed14a10d9a1ff492f3eae97babf799dcbafe2 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Fri, 24 Apr 2026 13:49:57 -0400 Subject: [PATCH 3/4] Experiment with documenting secrets --- Chart.yaml | 1 + README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ README.md.gotmpl | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/Chart.yaml b/Chart.yaml index 4be6730..40e7c0c 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -1,3 +1,4 @@ +--- apiVersion: v2 description: A Helm chart to build and deploy secrets using external-secrets for ansible-edge-gitops keywords: diff --git a/README.md b/README.md index 630d29a..14dbe84 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,48 @@ To use this version, you will also need to update your pattern to use the * v0.2.1: Support credential (HTTPS or SSH) injection for git client in AGOF config jobs. +### VP-Secrets-v2 + +```yaml +--- +# NEVER COMMIT THESE VALUES TO GIT +version: "2.0" +secrets: + - name: aap-manifest + fields: + - name: b64content + path: 'full pathname of file containing Satellite Manifest for entitling Ansible Automation Platform' + base64: true + + - name: automation-hub-token + fields: + - name: token + value: 'An automation hub token for retrieving Certified and Validated Ansible content' + + # Optional + - name: agof-vault-file + fields: + - name: agof-vault-file + path: 'full pathname of a valid agof_vault file for secrets to overlay the iac config' + base64: true + + # Optional, if git auth is needed + - name: git-auth-secret + fields: + # HTTPS auth + - name: username + value: "Username to authenticate with" + - value: password + value: "Password to authenticate with" + # SSH auth + - name: .git-credentials + value: "git credentials" + - name: ssh-privatekey + value: "An ssh private key" + - name: known_hosts + value: "SSH known hosts for SSH authentication" +``` + ## Requirements | Repository | Name | Version | diff --git a/README.md.gotmpl b/README.md.gotmpl index 4785605..d73423f 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -31,6 +31,48 @@ To use this version, you will also need to update your pattern to use the * v0.2.1: Support credential (HTTPS or SSH) injection for git client in AGOF config jobs. +### VP-Secrets-v2 + +```yaml +--- +# NEVER COMMIT THESE VALUES TO GIT +version: "2.0" +secrets: + - name: aap-manifest + fields: + - name: b64content + path: 'full pathname of file containing Satellite Manifest for entitling Ansible Automation Platform' + base64: true + + - name: automation-hub-token + fields: + - name: token + value: 'An automation hub token for retrieving Certified and Validated Ansible content' + + # Optional + - name: agof-vault-file + fields: + - name: agof-vault-file + path: 'full pathname of a valid agof_vault file for secrets to overlay the iac config' + base64: true + + # Optional, if git auth is needed + - name: git-auth-secret + fields: + # HTTPS auth + - name: username + value: "Username to authenticate with" + - value: password + value: "Password to authenticate with" + # SSH auth + - name: .git-credentials + value: "git credentials" + - name: ssh-privatekey + value: "An ssh private key" + - name: known_hosts + value: "SSH known hosts for SSH authentication" +``` + {{ template "chart.homepageLine" . }} {{ template "chart.maintainersSection" . }} From 66600159a5cf1479728fef0a8b66a34faf2c2a78 Mon Sep 17 00:00:00 2001 From: Martin Jackson Date: Fri, 24 Apr 2026 13:52:58 -0400 Subject: [PATCH 4/4] Add home field --- Chart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Chart.yaml b/Chart.yaml index 40e7c0c..a78c640 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -1,6 +1,7 @@ --- apiVersion: v2 description: A Helm chart to build and deploy secrets using external-secrets for ansible-edge-gitops +home: https://github.com/validatedpatterns/aap-config-chart.git keywords: - pattern name: aap-config