Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save loganpowell/1e5d5edfa1f0deb620d1d1fe841c7c2b to your computer and use it in GitHub Desktop.

Select an option

Save loganpowell/1e5d5edfa1f0deb620d1d1fe841c7c2b to your computer and use it in GitHub Desktop.
Expose GH repo secrets as env vars in running containers via GH Action -> Argo CD ConfigManagementPlugin

Enterprise Custom Argo CD plugin for GitHub Secrets injection:

Overview

This plugin fetches secrets from a GitHub repository and injects them as environment variables into Kubernetes pod manifests. The plugin runs as a sidecar container alongside the argocd-repo-server. The sidecar is built and deployed via GitHub Actions, which has access to GitHub Secrets values during the build process.

Plugin Architecture

  1. GitHub Action - Builds sidecar with secrets baked in at build time
  2. Sidecar container - Runs the ConfigManagementPlugin server with pre-loaded secrets
  3. Plugin script - Reads secrets from environment and modifies manifests
  4. Plugin configuration - Defines how the plugin discovers and generates manifests

Implementation Steps

1. Create Self-Contained Reusable Workflow

Note: Secrets can only use inherit from intra-org workflow

Create .github/workflows/build-argocd-plugin.yml as a completely self-contained reusable workflow:

name: Build and Deploy Argo CD Plugin Sidecar (Self-Contained)

on:
  workflow_call:
    inputs:
      registry:
        description: "Container registry URL (optional override for AF_DOCKER_HOST)"
        required: false
        type: string
        default: "" # Will use AF_DOCKER_HOST secret from tpe-automation runner
      image_name:
        description: "Image name (defaults to repository name with suffix)"
        required: false
        type: string
        default: ""
      argocd_namespace:
        description: "ArgoCD namespace"
        required: false
        type: string
        default: "argocd"
      deployment_name:
        description: "ArgoCD repo-server deployment name"
        required: false
        type: string
        default: "argocd-repo-server"
      container_name:
        description: "Plugin container name in deployment"
        required: false
        type: string
        default: "github-secrets-plugin"
      excluded_secrets:
        description: 'Comma-separated list of secret names to exclude from injection (e.g., "GITHUB_TOKEN,KUBE_CONFIG")'
        required: false
        type: string
        default: "GITHUB_TOKEN,KUBE_CONFIG"
      enable_k8s_deployment:
        description: "Whether to update Kubernetes deployment (requires KUBE_CONFIG)"
        required: false
        type: boolean
        default: true

env:
  IMAGE_NAME: ${{ inputs.image_name != '' && inputs.image_name || format('{0}/argocd-github-secrets-plugin', github.repository) }}

jobs:
  build-and-push:
    runs-on: [self-hosted, tpe-automation] # Use Windows self-hosted runner for JFrog access
    permissions:
      contents: read
      packages: write

    steps:
      - name: Add Git to PATH (Windows)
        shell: pwsh
        run: echo "C:\Program Files\Git\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
      - name: Create temporary build directory
        shell: bash
        run: |
          mkdir -p /tmp/plugin-build
          cd /tmp/plugin-build

      - name: Generate Python secrets injector script
        shell: bash
        run: |
          cat > /tmp/plugin-build/github-secrets-injector.py << 'EOF'
          import os
          import yaml
          import sys
          from pathlib import Path

          def load_secrets_from_env():
              """Load secrets that were baked into the container during build"""
              secrets = {}
              # Read all environment variables prefixed with GITHUB_SECRET_
              for key, value in os.environ.items():
                  if key.startswith("GITHUB_SECRET_"):
                      secret_name = key.replace("GITHUB_SECRET_", "")
                      secrets[secret_name] = value
              return secrets

          def inject_env_vars(manifest, secrets):
              """Inject environment variables into Pod/Deployment manifests"""
              if manifest.get("kind") in ["Pod", "Deployment", "StatefulSet", "DaemonSet"]:
                  containers = manifest.get("spec", {}).get("containers", [])
                  if manifest.get("kind") == "Deployment":
                      containers = manifest.get("spec", {}).get("template", {}).get("spec", {}).get("containers", [])

                  for container in containers:
                      env = container.get("env", [])
                      # Add secrets as direct environment variables
                      for secret_name, secret_value in secrets.items():
                          env.append({
                              "name": secret_name,
                              "value": secret_value
                          })
                      container["env"] = env
              return manifest

          def main():
              secrets = load_secrets_from_env()
              
              if not secrets:
                  print("No GITHUB_SECRET_* environment variables found", file=sys.stderr)
              
              # Read all YAML files in current directory
              found_files = False
              for yaml_file in Path(".").glob("*.yaml"):
                  found_files = True
                  with open(yaml_file) as f:
                      manifests = yaml.safe_load_all(f)
                      for manifest in manifests:
                          if manifest:
                              modified = inject_env_vars(manifest, secrets)
                              print("---")
                              print(yaml.dump(modified))
              
              if not found_files:
                  print("No YAML files found in current directory", file=sys.stderr)

          if __name__ == "__main__":
              main()
          EOF

      - name: Generate plugin configuration
        shell: bash
        run: |
          cat > /tmp/plugin-build/plugin.yaml << 'EOF'
          apiVersion: argoproj.io/v1alpha1
          kind: ConfigManagementPlugin
          metadata:
            name: github-secrets-injector
          spec:
            version: v1.0
            init:
              command: [sh, -c, 'echo "Initializing GitHub Secrets Injector"']
            generate:
              command: [python3, /usr/local/bin/github-secrets-injector.py]
            discover:
              fileName: "*.yaml"
          EOF

      - name: Generate entrypoint wrapper script
        shell: bash
        run: |
          cat > /tmp/plugin-build/entrypoint-wrapper.sh << 'EOF'
          #!/bin/bash

          # This script dynamically sets environment variables from build-time environment variables
          # All GITHUB_SECRET_* variables are already set during container build

          echo "GitHub Secrets Injector starting..."

          # List available GITHUB_SECRET_* environment variables (without values for security)
          echo "Available secrets:"
          env | grep "^GITHUB_SECRET_" | cut -d= -f1 | sort

          # Execute the original command
          exec "$@"
          EOF

      - name: Generate dynamic Dockerfile with secrets
        shell: bash
        run: |
          # Start building the Dockerfile
          cat > /tmp/plugin-build/Dockerfile << 'EOF'
          FROM quay.io/argoproj/argocd:latest

          USER root

          RUN apt-get update && \
              apt-get install -y python3 python3-pip && \
              pip3 install pyyaml && \
              rm -rf /var/lib/apt/lists/*

          EOF

          # Add ARG and ENV statements dynamically for each secret
          echo "# Dynamic ARG and ENV statements for secrets" >> /tmp/plugin-build/Dockerfile

          # This jq command processes all GitHub secrets and generates secret names
          echo '${{ toJSON(secrets) }}' | jq -r --arg excluded '${{ inputs.excluded_secrets }}' '
            # Split the excluded_secrets string into an array, filtering out empty values
            ($excluded | split(",") | map(select(length > 0))) as $exclude_list |
            # Convert the secrets object to an array of key-value pairs
            to_entries | 
            # Filter out any secrets that are in the exclusion list OR start with AF_DOCKER_
            map(select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not))) |
            # Extract just the secret names and prefix with GITHUB_SECRET_
            .[] | "GITHUB_SECRET_" + .key
          ' | while read secret_name; do
            # For each secret, add both ARG (build-time) and ENV (runtime) statements
            echo "ARG ${secret_name}" >> /tmp/plugin-build/Dockerfile
            echo "ENV ${secret_name}=\${${secret_name}}" >> /tmp/plugin-build/Dockerfile
          done

          # Complete the Dockerfile
          cat >> /tmp/plugin-build/Dockerfile << 'EOF'

          COPY github-secrets-injector.py /usr/local/bin/
          COPY plugin.yaml /home/argocd/cmp-server/config/
          COPY entrypoint-wrapper.sh /usr/local/bin/

          RUN chmod +x /usr/local/bin/github-secrets-injector.py /usr/local/bin/entrypoint-wrapper.sh

          USER argocd

          ENTRYPOINT ["/usr/local/bin/entrypoint-wrapper.sh", "/var/run/argocd/argocd-cmp-server"]
          EOF

          echo "Generated Dockerfile:"
          cat /tmp/plugin-build/Dockerfile

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.AF_DOCKER_HOST }}
          username: ${{ secrets.AF_DOCKER_USER }}
          password: ${{ secrets.AF_DOCKER_PASS }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ inputs.registry != '' && inputs.registry || secrets.AF_DOCKER_HOST }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix={{branch}}-
            type=ref,event=branch
            type=semver,pattern={{version}}

      - name: Prepare secrets as build args
        id: build-args
        shell: bash
        run: |
          # Convert excluded secrets input to array for jq processing
          EXCLUDED_SECRETS='${{ inputs.excluded_secrets }}'

          echo "Processing secrets (excluding: $EXCLUDED_SECRETS)"

          # Create build args from secrets, excluding specified ones
          BUILD_ARGS=""
          echo '${{ toJSON(secrets) }}' | jq -r --arg excluded "$EXCLUDED_SECRETS" '
            ($excluded | split(",") | map(select(length > 0))) as $exclude_list |
            to_entries | 
            map(select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not))) |
            .[] | "GITHUB_SECRET_" + .key + "=" + .value
          ' | while IFS='=' read -r key value; do
            if [ -n "$BUILD_ARGS" ]; then
              BUILD_ARGS="$BUILD_ARGS\n"
            fi
            # Escape special characters for build args
            escaped_value=$(echo "$value" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
            BUILD_ARGS="${BUILD_ARGS}${key}=\"${escaped_value}\""
          done

          # Output build args for the Docker build step
          echo "build-args<<EOF" >> $GITHUB_OUTPUT
          echo '${{ toJSON(secrets) }}' | jq -r --arg excluded "$EXCLUDED_SECRETS" '
            ($excluded | split(",") | map(select(length > 0))) as $exclude_list |
            to_entries | 
            map(select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not))) |
            .[] | "GITHUB_SECRET_" + .key + "=" + .value
          ' >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

          # Show which secrets will be included (names only, no values)
          echo "Secrets to be included:"
          echo '${{ toJSON(secrets) }}' | jq -r --arg excluded "$EXCLUDED_SECRETS" '
            ($excluded | split(",") | map(select(length > 0))) as $exclude_list |
            to_entries | 
            map(select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not))) |
            .[] | "GITHUB_SECRET_" + .key
          '

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: /tmp/plugin-build
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: ${{ steps.build-args.outputs.build-args }}

      - name: Clean up build directory
        if: always()
        shell: bash
        run: |
          rm -rf /tmp/plugin-build

      - name: Update Argo CD repo-server deployment
        if: ${{ inputs.enable_k8s_deployment == true }}
        shell: bash
        run: |
          # Check if KUBE_CONFIG is available
          if [ -z "${{ secrets.KUBE_CONFIG }}" ]; then
            echo "Warning: KUBE_CONFIG secret not provided. Skipping Kubernetes deployment update."
            exit 0
          fi

          # Install kubectl
          curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
          chmod +x kubectl

          # Configure kubectl with your cluster credentials
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=./kubeconfig

          # Update the image in the deployment
          ./kubectl set image deployment/${{ inputs.deployment_name }} \
            ${{ inputs.container_name }}=${{ inputs.registry != '' && inputs.registry || secrets.AF_DOCKER_HOST }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            -n ${{ inputs.argocd_namespace }}

          # Restart the deployment
          ./kubectl rollout restart deployment/${{ inputs.deployment_name }} -n ${{ inputs.argocd_namespace }}
          ./kubectl rollout status deployment/${{ inputs.deployment_name }} -n ${{ inputs.argocd_namespace }}

Detailed Breakdown: Dynamic Dockerfile Generation

Let's break down the complex secret processing logic step by step:

Step 1: Secret Filtering with jq

echo '${{ toJSON(secrets) }}' | jq -r --arg excluded '${{ inputs.excluded_secrets }}' '...'
  • ${{ toJSON(secrets) }}: GitHub Actions converts all repository secrets to a JSON object
  • --arg excluded '${{ inputs.excluded_secrets }}': Passes the exclusion list as a jq variable
  • -r: Raw output (no JSON quotes around strings)

Step 2: Parse Exclusion List

($excluded | split(",") | map(select(length > 0))) as $exclude_list
  • Takes "GITHUB_TOKEN,KUBE_CONFIG,OTHER_SECRET"
  • Splits by comma: ["GITHUB_TOKEN", "KUBE_CONFIG", "OTHER_SECRET"]
  • Filters out empty strings in case of trailing commas
  • Stores as $exclude_list variable

Step 3: Process Secrets Object

to_entries | map(select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not)))
  • to_entries: Converts {"DB_PASS":"value1", "API_KEY":"value2"} to [{"key":"DB_PASS","value":"value1"}, ...]
  • select(.key as $k | ($exclude_list | index($k) | not) and ($k | startswith("AF_DOCKER_") | not)): For each secret, check if:
    • Its name is NOT in the exclusion list, AND
    • Its name does NOT start with "AFDOCKER"
  • Result: Only application secrets that should be included

Step 4: Generate Secret Names

.[] | "GITHUB_SECRET_" + .key
  • Extracts each secret name and prefixes with GITHUB_SECRET_
  • Example: "DB_PASS" becomes "GITHUB_SECRET_DB_PASS"

Step 5: Generate Dockerfile Statements

while read secret_name; do
    echo "ARG ${secret_name}" >> /tmp/plugin-build/Dockerfile
    echo "ENV ${secret_name}=\${${secret_name}}" >> /tmp/plugin-build/Dockerfile
done
  • For each secret name, adds two Dockerfile instructions:
    • ARG GITHUB_SECRET_DB_PASS: Accepts build-time argument
    • ENV GITHUB_SECRET_DB_PASS=${GITHUB_SECRET_DB_PASS}: Sets runtime environment variable

Example Transformation:

Input Secrets:

{
  "DB_PASSWORD": "secret123",
  "API_KEY": "key456",
  "GITHUB_TOKEN": "github789",
  "KUBE_CONFIG": "k8s-config",
  "AF_DOCKER_HOST": "jfrog.company.com",
  "AF_DOCKER_USER": "svc-account",
  "AF_DOCKER_PASS": "jfrog-token"
}

Exclusion List: "GITHUB_TOKEN,KUBE_CONFIG" Auto-Excluded: All AF_DOCKER_* secrets

Generated Dockerfile Additions:

# Dynamic ARG and ENV statements for secrets
ARG GITHUB_SECRET_DB_PASSWORD
ENV GITHUB_SECRET_DB_PASSWORD=${GITHUB_SECRET_DB_PASSWORD}
ARG GITHUB_SECRET_API_KEY
ENV GITHUB_SECRET_API_KEY=${GITHUB_SECRET_API_KEY}

Why ARG + ENV Pattern:

  • ARG: Allows Docker build to accept the secret value via --build-arg
  • ENV: Makes the secret available to the running container as an environment variable
  • ${GITHUB_SECRET_DB_PASSWORD}: References the ARG value to set the ENV value

2. Using the Self-Contained Reusable Workflow

Basic Usage - No local files required, just reference the workflow:

name: Deploy Argo CD Plugin

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy-plugin:
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner automatically
      argocd_namespace: argocd
    secrets:
      # Pass through ALL secrets automatically
      inherit

With Custom Exclusions - Control which secrets to include:

name: Deploy Argo CD Plugin (Custom Configuration)

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy-plugin:
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner
      argocd_namespace: argocd
      # Exclude different secrets (infrastructure secrets auto-excluded)
      excluded_secrets: "KUBE_CONFIG,SOME_OTHER_SECRET,INTERNAL_TOKEN"
      # Disable automatic K8s deployment if no KUBE_CONFIG
      enable_k8s_deployment: false
    secrets: inherit

Explicit Secret Passing - Full control over which secrets are shared:

name: Deploy Argo CD Plugin (Explicit Secrets)

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy-plugin:
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner
      argocd_namespace: argocd
      # No exclusions needed since we're explicitly passing secrets
      excluded_secrets: ""
      enable_k8s_deployment: true
    secrets:
      # Infrastructure secrets (required for workflow operation)
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}

      # Application-specific secrets (only these will be injected)
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
      API_KEY: ${{ secrets.API_KEY }}
      SERVICE_TOKEN: ${{ secrets.SERVICE_TOKEN }}
      REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
      SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}

      # Optional: Environment-specific secrets
      # DEV_DB_URL: ${{ secrets.DEV_DB_URL }}
      # PROD_API_ENDPOINT: ${{ secrets.PROD_API_ENDPOINT }}

Container Registry Only - Just build and push, no K8s deployment:

name: Build Plugin Container Only

on:
  workflow_dispatch:

jobs:
  build-only:
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner
      # Disable Kubernetes deployment
      enable_k8s_deployment: false
      # Include all secrets except infrastructure ones (AF_DOCKER_* auto-excluded)
      excluded_secrets: ""
    secrets: inherit

Multi-Environment Setup:

name: Deploy to Multiple Environments

on:
  workflow_dispatch:
  push:
    branches: [main, develop]

jobs:
  deploy-dev:
    if: github.ref == 'refs/heads/develop'
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner
      excluded_secrets: "PROD_DB_PASSWORD,PROD_API_KEY"
      argocd_namespace: argocd-dev
      image_name: ${{ github.repository }}/argocd-plugin-dev
    secrets: inherit

  deploy-prod:
    if: github.ref == 'refs/heads/main'
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Uses AF_DOCKER_HOST from tpe-automation runner
      excluded_secrets: "DEV_DB_PASSWORD,DEV_API_KEY"
      argocd_namespace: argocd-prod
      image_name: ${{ github.repository }}/argocd-plugin-prod
    secrets: inherit

External Registry Override - Only if you need a different registry:

name: Deploy to External Registry

on:
  workflow_dispatch:

jobs:
  deploy-external:
    uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main
    with:
      # Override only when needed for external registries
      registry: "ghcr.io"
      # AF_DOCKER_* secrets automatically excluded (not needed for GHCR)
      excluded_secrets: ""
    secrets: inherit

3. Configure Argo CD Repo Server

Add the sidecar to your argocd-repo-server deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-repo-server
spec:
  template:
    spec:
      containers:
        - name: argocd-repo-server
          # ... existing config
        - name: github-secrets-plugin
          image: your-jfrog-host.jfrog.io/vertexinc/your-repo/argocd-github-secrets-plugin:main
          command: [/var/run/argocd/argocd-cmp-server]
          volumeMounts:
            - name: var-files
              mountPath: /var/run/argocd
            - name: plugins
              mountPath: /home/argocd/cmp-server/plugins
            - name: tmp
              mountPath: /tmp
            - name: cmp-tmp
              mountPath: /home/argocd/cmp-server/tmp
      volumes:
        - name: cmp-tmp
          emptyDir: {}
      # ... existing volumes

4. Reference Plugin in Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  source:
    repoURL: https://github.com/vertexinc/your-repo
    targetRevision: main
    path: manifests
    plugin:
      name: github-secrets-injector

Key Benefits of the Self-Contained Reusable Workflow

  1. πŸš€ Zero Setup: No files required in application repositories - just call the workflow
  2. πŸ”§ Centralized Maintenance: All plugin code maintained in one place
  3. πŸ“¦ Self-Contained: Everything needed is generated dynamically in the workflow
  4. πŸ” Secure Secret Handling: Secrets passed through build args, never stored in files
  5. πŸŽ›οΈ Fully Configurable: Same flexibility with secret exclusions and deployment options
  6. πŸ”„ Version Control: Plugin logic versioned with the shared workflow repository
  7. ⚑ Instant Adoption: New repositories can use it immediately without setup
  8. πŸ› οΈ Easy Updates: Plugin improvements automatically available to all consumers
  9. πŸ“ Simplified Documentation: No complex setup instructions for developers

Security Improvements

Enhanced Secret Security:

  • βœ… No File Storage: Secrets never written to temporary files or JSON
  • βœ… Build-time Only: Secrets only exist during Docker build process
  • βœ… Environment Variables: Secrets baked into container as environment variables
  • βœ… No Disk Persistence: No secrets left on GitHub Actions runners
  • βœ… Dynamic Generation: Dockerfile generated with exact secrets needed

Security Flow:

GitHub Secrets β†’ Build Args β†’ Dockerfile ARG/ENV β†’ Container Environment β†’ Application

Use in Workflows:

# Developer just adds to workflow:
uses: vertexinc/shared-workflows/.github/workflows/build-argocd-plugin.yml@main

Important Notes

  • Security: Secrets are baked into the container image at build time. Ensure your container registry has appropriate access controls.
  • Secret Updates: When secrets change in GitHub, trigger the workflow to rebuild and redeploy the sidecar, this can be done at the same time as application deployments - prior to the deployment step.
  • Sidecar Resources: Ensure adequate CPU/memory limits for the sidecar container.
  • Plugin Discovery: The plugin will process all .yaml files in the specified path.

Architecture Diagrams

System Architecture

graph TB
    subgraph "GitHub Repository"
        GS[GitHub Secrets<br/>πŸ“¦ Application Secrets<br/>πŸ” Infrastructure Secrets<br/>AF_DOCKER_...]
        GW[GitHub Actions Workflow]
        DR[Docker Registry<br/>GHCR/ECR/JFrog]
    end

    subgraph "Build Process"
        GW -->|1. Triggered on push| BA[Build Action]
        GS -->|2. Provides all secrets| BA
        BA -->|3. Auto-filters secrets| FILTER[Secret Filtering<br/>βœ… Application Secrets<br/>❌ AF_DOCKER_<br/>❌ User exclusions]
        FILTER -->|4. Creates build args| ARGS[Docker Build Args<br/>GITHUB_SECRET_...<br/>πŸ“± App secrets only]
        BA -->|5. Builds container| CI[Container Image<br/>with app env vars]
        CI -->|6. Pushes using AF_DOCKER_*| DR
    end

    subgraph "Kubernetes Cluster"
        subgraph "ArgoCD Namespace"
            ARS[argocd-repo-server<br/>Deployment]
            subgraph "Repo Server Pod"
                ARC[argocd-repo-server<br/>container]
                PSC[Plugin Sidecar<br/>container]
            end
        end

        subgraph "Application Namespace"
            APP[Application<br/>Pods]
        end
    end

    subgraph "ArgoCD Application Process"
        AAPP[ArgoCD Application]
        MANIFEST[Kubernetes<br/>Manifests]
    end

    DR -->|7. Pulls image| PSC
    PSC -->|8. App secrets already<br/>in environment| ENV[Environment Variables<br/>GITHUB_SECRET_*<br/>🎯 App secrets only]
    AAPP -->|9. Processes manifests<br/>via plugin| PSC
    PSC -->|10. Injects app secrets<br/>as env vars| MANIFEST
    MANIFEST -->|11. Deploys with<br/>injected app secrets| APP

    classDef github fill:#f9f,stroke:#333,stroke-width:2px
    classDef k8s fill:#9cf,stroke:#333,stroke-width:2px
    classDef argocd fill:#fcf,stroke:#333,stroke-width:2px
    classDef process fill:#cfc,stroke:#333,stroke-width:2px
    classDef security fill:#ffe0e0,stroke:#d32f2f,stroke-width:2px

    class GS,GW,DR github
    class ARS,ARC,PSC,APP k8s
    class AAPP,MANIFEST argocd
    class BA,ARGS,CI,ENV process
    class FILTER security
Loading

Plugin Configuration Architecture

graph LR
    subgraph "Plugin Files"
        PY[github-secrets-injector.py<br/>Python Script<br/>πŸ” Reads GITHUB_SECRET_*<br/>πŸ“± App secrets only]
        PC[plugin.yaml<br/>Plugin Config]
        EW[entrypoint-wrapper.sh<br/>Startup Script<br/>πŸš€ Zero file processing]
    end

    subgraph "ArgoCD Integration"
        CMP[ConfigManagementPlugin<br/>Server]
        DISC[Discovery Phase<br/>*.yaml files]
        GEN[Generation Phase<br/>Inject app secrets only]
    end

    subgraph "Kubernetes Manifests"
        ORIG[Original Manifest<br/>kind: Deployment]
        MOD[Modified Manifest<br/>+ env vars with app secrets<br/>🎯 No infrastructure secrets]
    end

    subgraph "Secret Filtering"
        ENV_ALL[All Environment Variables<br/>GITHUB_SECRET_*<br/>πŸ” Infrastructure filtered out]
        ENV_APP[Application Secrets<br/>GITHUB_SECRET_DB_PASSWORD<br/>GITHUB_SECRET_API_KEY<br/>etc.]
    end

    EW -->|1. Container starts with<br/>pre-filtered secrets| ENV_ALL
    ENV_ALL -->|2. Only app secrets available| ENV_APP
    PC -->|3. Configures| CMP
    CMP -->|4. Discovers| DISC
    DISC -->|5. Triggers| GEN
    ENV_APP -->|6. Provides app secrets to| PY
    PY -->|7. Processes| ORIG
    PY -->|8. Outputs with app secrets| MOD

    classDef files fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    classDef argocd fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    classDef k8s fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
    classDef process fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
    classDef security fill:#ffe0e0,stroke:#d32f2f,stroke-width:2px

    class PY,PC,EW files
    class CMP,DISC,GEN argocd
    class ORIG,MOD k8s
    class ENV_ALL,ENV_APP security
Loading

Sequence Diagram

End-to-End Secret Injection Flow

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub Repository
    participant GA as GitHub Actions
    participant REG as JFrog Artifactory
    participant K8S as Kubernetes Cluster
    participant ARGO as ArgoCD
    participant APP as Application

    Note over Dev,APP: Setup Phase
    Dev->>GH: 1. Push .argo-plugin/ files
    Dev->>GH: 2. Configure secrets in repository
    Dev->>GH: 3. Setup reusable workflow

    Note over Dev,APP: Build Phase
    Dev->>GH: 4. Push code changes
    GH->>GA: 5. Trigger workflow
    GA->>GA: 6. Load all repository secrets
    GA->>GA: 7. Auto-filter AF_DOCKER_* and user-excluded secrets
    GA->>GA: 8. Create build args with GITHUB_SECRET_* prefix (app secrets only)
    GA->>GA: 9. Generate Dockerfile with dynamic ARG/ENV statements
    GA->>GA: 10. Build Docker image with application secrets as build args
    GA->>REG: 11. Push container image (using AF_DOCKER_* for authentication)

    Note over Dev,APP: Deployment Phase
    GA->>K8S: 12. Update argocd-repo-server deployment (optional)
    K8S->>REG: 13. Pull new plugin sidecar image
    K8S->>K8S: 14. Start plugin sidecar container

    Note over Dev,APP: Runtime Phase
    ARGO->>ARGO: 15. Application sync triggered
    ARGO->>K8S: 16. Request manifest processing via plugin

    activate K8S
    K8S->>K8S: 17. entrypoint-wrapper.sh starts (app secrets already in env)
    K8S->>K8S: 18. github-secrets-injector.py processes manifests
    K8S->>K8S: 19. Inject application secrets as environment variables
    K8S-->>ARGO: 20. Return modified manifests
    deactivate K8S

    ARGO->>APP: 21. Deploy with injected application secrets

    Note over Dev,APP: Application Running
    APP->>APP: 22. Application uses secrets from environment variables

    Note over GA,REG: Infrastructure secrets transparent to developer
    Note over K8S,APP: Only application secrets exposed to containers
Loading

Reusable Workflow Interaction

sequenceDiagram
    participant REPO as Application Repository
    participant SHARED as Shared Workflows Repository
    participant GA as GitHub Actions Runner
    participant REG as JFrog Artifactory
    participant K8S as Kubernetes

    REPO->>SHARED: 1. calls: uses shared-workflows/.../build-argocd-plugin.yml
    REPO->>SHARED: 2. with: configuration inputs (no AF_DOCKER_* knowledge needed)
    REPO->>SHARED: 3. secrets: inherit (all secrets)

    activate SHARED
    SHARED->>GA: 4. Start reusable workflow
    GA->>GA: 5. Process inputs (registry auto-detected, excluded_secrets, etc.)
    GA->>GA: 6. Auto-filter AF_DOCKER_* and user-excluded secrets
    GA->>GA: 7. Transform remaining secrets to GITHUB_SECRET_* format
    GA->>GA: 8. Generate Dockerfile with dynamic ARG/ENV statements
    GA->>GA: 9. docker build with application secrets as build args
    GA->>REG: 10. Push container image (using AF_DOCKER_* internally)

    alt enable_k8s_deployment == true
        GA->>K8S: 11. Update deployment with new image
        K8S-->>GA: 12. Deployment status
    end

    SHARED-->>REPO: 13. Workflow completion
    deactivate SHARED

    Note over GA,REG: Infrastructure secrets (AF_DOCKER_*) handled transparently
    Note over GA,K8S: Only application secrets injected into containers
Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment