commit 3dd8ad4c1f0114a7211a8319a82da35f5db79294 Author: Aydent Date: Sun Jan 25 11:33:39 2026 +0100 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..912d132 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Infisical Secrets Fetcher for Gitea Actions + +This composite Gitea Action fetches secrets from a self-hosted [Infisical](https://infisical.com) instance and injects them into the Gitea Actions environment. + +## Features + +- **DNS Resolution Fix**: Automatically resolves the Infisical domain using Cloudflare DNS (1.1.1.1) and updates `/etc/hosts` to prevent DNS timeouts on runners. +- **Universal Auth**: Supports Machine Identity authentication. +- **Secure Injection**: Injects secrets directly into `$GITEA_ENV` and masks values. + +## Usage + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Fetch Secrets + uses: actions/infisical-secrets-fetcher@main + with: + client_id: ${{ secrets.INFISICAL_CLIENT_ID }} + client_secret: ${{ secrets.INFISICAL_CLIENT_SECRET }} + project_id: ${{ secrets.INFISICAL_PROJECT_ID }} + environment: 'prod' # Optional, default: prod + secret_path: '/MyHelper' # Optional, default: / + domain: 'https://infisical.lemarechal.eu' # Optional, default provided +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `client_id` | Machine Identity Client ID | Yes | - | +| `client_secret` | Machine Identity Client Secret | Yes | - | +| `project_id` | Infisical Project ID (Workspace ID) | Yes | - | +| `environment` | Environment slug (dev, staging, prod) | No | `prod` | +| `secret_path` | Path to secrets folder | No | `/` | +| `domain` | URL of the Infisical instance | No | `https://infisical.lemarechal.eu` | + +## License + +MIT diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..7e8c8ee --- /dev/null +++ b/action.yml @@ -0,0 +1,94 @@ +name: 'Infisical Secrets Fetcher' +description: 'Fetch and inject secrets from self-hosted Infisical into Gitea Actions' +inputs: + client_id: + description: 'Machine Identity Client ID' + required: true + client_secret: + description: 'Machine Identity Client Secret' + required: true + project_id: + description: 'Infisical Project ID (Workspace ID)' + required: true + environment: + description: 'Target Environment (dev, staging, prod)' + default: 'prod' + required: false + secret_path: + description: 'Path/Folder to secrets (e.g. /Discord_bot). Root is /' + default: '/' + required: false + domain: + description: 'Infisical Instance URL' + default: 'https://infisical.lemarechal.eu' + required: false + +runs: + using: "composite" + steps: + - name: 'Install dependencies' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y dnsutils jq curl + + - name: 'Execute Fetch' + shell: bash + run: | + # 1. DNS Fix + echo "Resolving IP for ${{ inputs.domain }}..." + DOMAIN_CLEAN=$(echo "${{ inputs.domain }}" | sed 's|https://||g' | sed 's|http://||g' | cut -d'/' -f1) + PUBLIC_IP=$(dig +short @1.1.1.1 $DOMAIN_CLEAN | tail -n1) + + if [ -z "$PUBLIC_IP" ]; then + echo "::error::Failed to resolve IP for $DOMAIN_CLEAN" + exit 1 + fi + + echo "Resolved $DOMAIN_CLEAN to $PUBLIC_IP. Updating /etc/hosts..." + echo "$PUBLIC_IP $DOMAIN_CLEAN" | sudo tee -a /etc/hosts + + # 2. Login (Universal Auth) + echo "Authenticating with Infisical..." + LOGIN_RESPONSE=$(curl -s -X POST "${{ inputs.domain }}/api/v1/auth/universal-auth/login" \ + -H "Content-Type: application/json" \ + -d "{\"clientId\": \"${{ inputs.client_id }}\", \"clientSecret\": \"${{ inputs.client_secret }}\"}") + + ACCESS_TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.accessToken') + + if [ "$ACCESS_TOKEN" == "null" ] || [ -z "$ACCESS_TOKEN" ]; then + echo "::error::Authentication failed. Response: $LOGIN_RESPONSE" + exit 1 + fi + + # 3. Fetch Raw Secrets + echo "Fetching secrets from path: ${{ inputs.secret_path }} (Env: ${{ inputs.environment }})..." + SECRETS_RESPONSE=$(curl -s -X GET "${{ inputs.domain }}/api/v3/secrets/raw?workspaceId=${{ inputs.project_id }}&environment=${{ inputs.environment }}&secretPath=${{ inputs.secret_path }}" \ + -H "Authorization: Bearer $ACCESS_TOKEN") + + # Check for errors in response (Infisical usually returns JSON, check if it's an object with 'secrets' or just the raw dictionary if using /raw endpoint? + # The prompt says /api/v3/secrets/raw. + # CAUTION: /api/v3/secrets/raw typically returns a dictionary of key-value pairs directly: { "KEY": "VALUE" } + # Let's verify we got valid JSON and not an error message. + + if echo "$SECRETS_RESPONSE" | jq -e . >/dev/null 2>&1; then + # Valid JSON + : + else + echo "::error::API returned invalid JSON: $SECRETS_RESPONSE" + exit 1 + fi + + # 4. Injection + echo "Injecting secrets into Gitea Environment..." + echo "$SECRETS_RESPONSE" | jq -r 'to_entries | .[] | "\(.key)=\(.value)"' | while read -r line; do + # Securely append to GITEA_ENV (using the environment file pattern if available, or simpler export approach) + # Gitea Actions uses $GITHUB_ENV / $GITEA_ENV file pattern. + echo "$line" >> $GITEA_ENV + + # Mask the value in logs to be safe (optional but recommended) + val=$(echo "$line" | cut -d'=' -f2-) + echo "::add-mask::$val" + done + + echo "Secrets injected successfully."