-
Notifications
You must be signed in to change notification settings - Fork 2
Add CF log bridge, workflows and drain infra #424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| name: CF log bridge deploy to STACKIT Cloud Foundry | ||
|
|
||
| on: | ||
| push: | ||
| branches: [staging, main] | ||
| paths: ["infra/cf-log-bridge/**", ".github/workflows/cf-log-bridge-deploy.yml"] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: read | ||
|
|
||
| env: | ||
| IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/baergpt-log-bridge | ||
| # Suffix must match the CF space names and the drain URL built in | ||
| # infra/terraform/cloud-foundry/log-drain.tf. | ||
| APP_NAME: baergpt-log-bridge-${{ github.ref_name == 'main' && 'prod' || 'staging' }} | ||
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| concurrency: | ||
| group: cf-log-bridge-deploy-${{ github.ref }} | ||
| cancel-in-progress: false | ||
| # workflow_dispatch has no branches filter, so guard the ref here. | ||
| if: ${{ github.ref_name == 'staging' || github.ref_name == 'main' }} | ||
| environment: ${{ github.ref_name == 'main' && 'Production' || 'Staging' }} | ||
|
|
||
| steps: | ||
| - name: Checkout code ⬇️ | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0, pinned to commit sha to avoid sha-hulud like attacks | ||
| with: | ||
| persist-credentials: "false" | ||
|
|
||
| - name: Setup environment ⚙️ | ||
| uses: ./.github/actions/setup | ||
| with: | ||
| install-1password: "true" | ||
|
|
||
| # Same formula as cf-log-bridge-image.yml, so it resolves to the same tag. | ||
| - name: Compute image tag 🏷️ | ||
| id: tag | ||
| run: | | ||
| set -euo pipefail | ||
| echo "tag=content-sha-${{ hashFiles('infra/cf-log-bridge/vector.yaml', 'infra/cf-log-bridge/Dockerfile') }}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Set 1Password item IDs based on branch 🌿 | ||
| id: env-config | ||
| run: | | ||
| set -euo pipefail | ||
| if [[ "${{ github.ref_name }}" == "main" ]]; then | ||
| echo "cf_item_id=${{ secrets.OP_CF_DEPLOY_PRODUCTION_ENV_ITEM_ID }}" >> "$GITHUB_OUTPUT" | ||
| echo "app_item_id=${{ secrets.OP_LOG_BRIDGE_PRODUCTION_ENV_ITEM_ID }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "cf_item_id=${{ secrets.OP_CF_DEPLOY_STAGING_ENV_ITEM_ID }}" >> "$GITHUB_OUTPUT" | ||
| echo "app_item_id=${{ secrets.OP_LOG_BRIDGE_STAGING_ENV_ITEM_ID }}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Fetch STACKIT Cloud Foundry credentials from 1Password 🔑 | ||
| run: | | ||
| set -euo pipefail | ||
| op read "op://${{ secrets.OP_VAULT_ID }}/${{ steps.env-config.outputs.cf_item_id }}/notesPlain" > cf.env | ||
| while IFS= read -r line || [ -n "$line" ]; do | ||
| [[ -z "$line" || "$line" == \#* ]] && continue | ||
| key="${line%%=*}" | ||
| value="${line#*=}" | ||
| echo "::add-mask::$value" | ||
| echo "$key=$value" >> "$GITHUB_ENV" | ||
| done < cf.env | ||
| rm cf.env | ||
| env: | ||
| OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | ||
|
|
||
| # Both workflows fire on the same push, so the build may still be running. | ||
| - name: Wait for image to exist in GHCR 🔍 | ||
| run: | | ||
| set -euo pipefail | ||
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | ||
| image="${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" | ||
| timeout=600 | ||
| counter=0 | ||
| until docker manifest inspect "$image" >/dev/null 2>&1; do | ||
| if [ $counter -ge $timeout ]; then | ||
| echo "::error::Image $image not found in GHCR after ${timeout}s." \ | ||
| "Ensure 'CF log bridge image build and push' ran for this commit." | ||
| exit 1 | ||
| fi | ||
| echo "Waiting for $image to be available on GHCR... (${counter}s)" | ||
| sleep 10 | ||
| counter=$((counter + 10)) | ||
| done | ||
| echo "Image $image found." | ||
|
|
||
| - name: Install cf CLI ⚙️ | ||
| run: | | ||
| set -euo pipefail | ||
| curl -sL "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=v8&source=github" \ | ||
| | tar -zx | ||
| sudo mv cf8 /usr/local/bin/cf | ||
|
|
||
| - name: Target STACKIT Cloud Foundry 🎯 | ||
| run: | | ||
| set -euo pipefail | ||
| cf api "$CF_API_ENDPOINT" | ||
| cf auth | ||
| cf target -o "$CF_ORG" -s "$CF_SPACE" | ||
|
|
||
| # KEY=VALUE lines in the item's notes. See infra/cf-log-bridge/README.md. | ||
| - name: Fetch bridge env vars from 1Password 🔑 | ||
| run: | | ||
| set -euo pipefail | ||
| op read "op://${{ secrets.OP_VAULT_ID }}/${{ steps.env-config.outputs.app_item_id }}/notesPlain" > bridge.env | ||
| : > infra/cf-log-bridge/app.env | ||
| while IFS= read -r line || [ -n "$line" ]; do | ||
| [[ -z "$line" || "$line" == \#* ]] && continue | ||
| key="${line%%=*}" | ||
| value="${line#*=}" | ||
| echo "::add-mask::$value" | ||
| echo "$key=$value" >> infra/cf-log-bridge/app.env | ||
| done < bridge.env | ||
| rm bridge.env | ||
| env: | ||
| OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | ||
|
|
||
| # cf set-env needs the app to exist, so the first deploy creates it with | ||
| # --no-start. Unsetting first means keys removed from 1Password do not linger. | ||
| - name: Sync bridge env vars 🔄 | ||
| id: sync | ||
| working-directory: infra/cf-log-bridge | ||
| env: | ||
| CF_DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| set -euo pipefail | ||
| if ! cf app "${{ env.APP_NAME }}" >/dev/null 2>&1; then | ||
| echo "bootstrapped=true" >> "$GITHUB_OUTPUT" | ||
| cf push \ | ||
| -f manifest.yml \ | ||
| --no-start \ | ||
| --var app_name="${{ env.APP_NAME }}" \ | ||
| --var image="${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" \ | ||
| --var docker_username="${{ github.actor }}" | ||
| fi | ||
|
|
||
| guid=$(cf app "${{ env.APP_NAME }}" --guid) | ||
| # Assigned first: a substitution inside `for ... in` discards its exit status, so | ||
| # a failed cf curl would silently skip the unset and leave stale vars behind. | ||
| keys=$(cf curl "/v3/apps/$guid/environment_variables" | jq -r '.var | keys[]') | ||
| for key in $keys; do | ||
| cf unset-env "${{ env.APP_NAME }}" "$key" >/dev/null | ||
| done | ||
|
|
||
| while IFS= read -r line || [ -n "$line" ]; do | ||
| key="${line%%=*}" | ||
| value="${line#*=}" | ||
| cf set-env "${{ env.APP_NAME }}" "$key" "$value" >/dev/null | ||
| done < app.env | ||
|
|
||
| rm -f app.env | ||
|
|
||
| # Rolling keeps the old version serving the drain until the new one is up. The | ||
| # first deploy has nothing to roll over, hence the flag from the sync step. | ||
| - name: Push image to Cloud Foundry 🚀 | ||
| working-directory: infra/cf-log-bridge | ||
| env: | ||
| CF_DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| set -euo pipefail | ||
| cf push \ | ||
| -f manifest.yml \ | ||
| ${{ steps.sync.outputs.bootstrapped != 'true' && '--strategy rolling' || '' }} \ | ||
| --var app_name="${{ env.APP_NAME }}" \ | ||
| --var image="${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" \ | ||
| --var docker_username="${{ github.actor }}" \ | ||
| || { cf cancel-deployment "${{ env.APP_NAME }}" 2>/dev/null || true; exit 1; } | ||
|
|
||
| - name: Dump recent app logs on failure 🪵 | ||
| if: ${{ failure() && !cancelled() }} | ||
| run: cf logs "${{ env.APP_NAME }}" --recent | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| name: CF log bridge image build and push to registry | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [staging, main] | ||
| paths: ["infra/cf-log-bridge/**", ".github/workflows/cf-log-bridge-image.yml"] | ||
| push: | ||
| branches: [staging, main] | ||
| paths: ["infra/cf-log-bridge/**", ".github/workflows/cf-log-bridge-image.yml"] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
Comment on lines
+3
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow file =="
if [ -f .github/workflows/cf-log-bridge-image.yml ]; then
nl -ba .github/workflows/cf-log-bridge-image.yml | sed -n '1,140p'
else
echo "missing .github/workflows/cf-log-bridge-image.yml"
git ls-files .github/workflows | sed -n '1,120p'
fi
echo
echo "== related references =="
rg -n "cf-log-bridge|docker/build-push-action|GITHUB_TOKEN|GHCR|packages: write|pull_request|workflow_dispatch" .github/workflows -S || trueRepository: technologiestiftung/baergpt Length of output: 225 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow file =="
if [ -f .github/workflows/cf-log-bridge-image.yml ]; then
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/cf-log-bridge-image.yml | sed -n '1,140p'
else
echo "missing .github/workflows/cf-log-bridge-image.yml"
git ls-files .github/workflows | sed -n '1,120p'
fi
echo
echo "== related references =="
rg -n "cf-log-bridge|docker/build-push-action|GITHUB_TOKEN|GHCR|packages: write|pull_request|workflow_dispatch" .github/workflows -S || trueRepository: technologiestiftung/baergpt Length of output: 9147 🌐 Web query:
💡 Result: When a workflow is triggered by a pull request from a public fork, GitHub security policies enforce a read-only GITHUB_TOKEN, regardless of the permissions configured in your workflow file [1][2][3]. This restriction is a security boundary designed to prevent untrusted code submitted via forks from executing with write access to your repository [4][2]. Key details regarding this behavior include: 1. Restriction on Write Access: For pull requests from forks using the standard pull_request event, all requested write permissions (such as packages: write or contents: write) are automatically downgraded to read-only [1][5][6]. You cannot override this downgrade using the permissions key in your YAML file [2][3]. 2. Exception for pull_request_target: The pull_request_target event is an exception to this rule [6][7]. Because it runs in the context of the base repository's default branch rather than the fork's merge commit, it is granted the repository's standard permissions, which can include write access [4][2]. Important Security Warning: While pull_request_target allows for write permissions, it should be used with caution [3]. Because it executes code from the base repository's default branch, you must ensure you are not checking out or executing untrusted code from the pull request itself in a way that could lead to security vulnerabilities [4][2][3]. 3. Recommended Patterns: To handle tasks like publishing packages or commenting on pull requests from forks, the industry-standard approach is often to use a two-workflow split [2]: - An untrusted pull_request workflow that performs builds and uploads artifacts. - A separate, trusted workflow_run or pull_request_target job that consumes those artifacts and performs the authorized write actions [2]. For further configuration, repository administrators can control whether fork pull requests are allowed to send write tokens to workflows at all via the repository's Actions settings under "Workflow permissions" [8][5], though this still respects the fundamental security downgrade for the pull_request event [5]. Citations:
🌐 Web query:
💡 Result: To use docker/build-push-action v7 to push images to the GitHub Container Registry (GHCR) without manually running a separate docker/login-action, you must configure the workflow to authenticate using the GitHub Actions GITHUB_TOKEN and ensure the appropriate permissions are set [1]. Authentication Setup: You can authenticate to GHCR using the GITHUB_TOKEN provided by the workflow environment [1][2]. The action and the underlying Buildx setup will use this token to authenticate against ghcr.io if configured correctly [1]. Permissions Required: Your workflow must have explicit write permissions for the packages scope [1][3][4]. This is configured in the permissions block of your workflow file: permissions: contents: read packages: write Implementation: Because docker/build-push-action relies on Docker Buildx, you must ensure Buildx is set up before the build step using docker/setup-buildx-action [5][4]. While docker/login-action is commonly used, you can authenticate against GHCR by passing the GITHUB_TOKEN to the login step: - name: Login to GHCR uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} Although you asked to push without login, the docker/build-push-action requires an active Docker daemon authentication to push to a private registry [5][6]. Using docker/login-action with the GITHUB_TOKEN is the standard, secure way to provide this authentication without needing a long-lived Personal Access Token (PAT) [1]. Ensuring your workflow has packages: write is the critical security requirement for this token to successfully push images to your GHCR repository [1][4]. Citations:
Do not publish images during
[low_effort And_high_reward] 🤖 Prompt for AI Agents |
||
|
|
||
| env: | ||
| IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/baergpt-log-bridge | ||
|
|
||
| jobs: | ||
| build-and-push: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code ⬇️ | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| fetch-depth: "2" | ||
|
|
||
| # The image is the upstream Vector release plus this directory's config, so the | ||
| # config alone determines whether a rebuild is needed. Fixtures and the local | ||
| # harness are excluded: they never enter the image. | ||
| - name: Compute image tag 🏷️ | ||
| id: tag | ||
| run: | | ||
| echo "tag=content-sha-${{ hashFiles('infra/cf-log-bridge/vector.yaml', 'infra/cf-log-bridge/Dockerfile') }}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Log in to GitHub Container Registry 🔑 | ||
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| # Runner's default docker driver can't export cache (type=gha); switches | ||
| # to the docker-container driver, which supports --cache-to. | ||
| - name: Set up Docker Buildx ⚙️ | ||
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 | ||
|
|
||
| - name: Check if image already exists in GHCR 🔍 | ||
| id: check | ||
| run: | | ||
| if docker manifest inspect ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} >/dev/null 2>&1; then | ||
| echo "exists=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "exists=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Build and push image 🐳 | ||
| if: steps.check.outputs.exists != 'true' | ||
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 | ||
| with: | ||
| context: infra/cf-log-bridge | ||
| file: infra/cf-log-bridge/Dockerfile | ||
| platforms: linux/amd64 | ||
| push: true | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| tags: | | ||
| ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} | ||
| ${{ env.IMAGE_NAME }}:commit-${{ github.sha }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| FROM --platform=linux/amd64 timberio/vector:0.57.0-debian | ||
|
Check warning on line 1 in infra/cf-log-bridge/Dockerfile
|
||
|
|
||
| COPY vector.yaml /etc/vector/vector.yaml | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| # Vector does not substitute env vars into its config unless this flag is set: an | ||
| # endpoint written as ${SOME_URL} would stay that literal text instead of becoming the | ||
| # value. Baked in here rather than set as an app env var so a deploy cannot omit it. | ||
| CMD ["--config", "/etc/vector/vector.yaml", "--dangerously-allow-env-var-interpolation"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # CF log bridge | ||
|
|
||
| Cloud Foundry can forward an app's logs to a URL you nominate, as syslog over HTTPS. | ||
| STACKIT Observability does not accept syslog, so this Vector app sits in between: | ||
| it receives the drain, and pushes logs to Loki and container metrics to Prometheus | ||
| remote-write. | ||
|
|
||
| One instance runs per CF space, named `baergpt-log-bridge-<space>`. `default-route: true` | ||
| turns that name into the route, and a route belongs to one space only, so the suffix is | ||
| what keeps staging and prod apart. The deploy workflow and | ||
| `infra/terraform/cloud-foundry/log-drain.tf` build the name separately and must agree. | ||
|
|
||
| The drain itself is a `stackit-drain` service defined in | ||
| `infra/terraform/cloud-foundry/log-drain.tf`, and apps opt in by naming it in their own | ||
| manifest. | ||
|
|
||
| **Never bind this app to `stackit-drain`.** Its own output would be drained back into | ||
| itself, and the resulting amplification trips the platform's limit of 1000 log-lines per | ||
| second per instance, after which lines are dropped for every app on that cell. | ||
|
|
||
| Design notes: `docs/superpowers/specs/2026-07-30-cloud-foundry-observability-design.md`. | ||
|
|
||
| ## Environment | ||
|
|
||
| Set by `.github/workflows/cf-log-bridge-deploy.yml` from a 1Password item, which is the | ||
| single source of truth: the workflow unsets every existing variable before re-setting. | ||
|
|
||
| | Variable | Notes | | ||
| | ------------------------------------------------ | ------------------------------------------------------------------------------- | | ||
| | `BAERGPT_ENV` | `staging` or `production`. Becomes the `source` label as `cf-<env>` | | ||
| | `DRAIN_USERNAME`, `DRAIN_PASSWORD` | Basic auth on the inbound endpoint. Must match the credentials in the drain URL | | ||
| | `STACKIT_OBSERVABILITY_USERNAME`, `..._PASSWORD` | From the Observability instance's Credentials tab | | ||
| | `STACKIT_OBSERVABILITY_LOGS_ENDPOINT` | Base URL only. Vector appends `/loki/api/v1/push` | | ||
| | `STACKIT_OBSERVABILITY_METRICS_ENDPOINT` | Used verbatim, including `/api/v1/receive` | | ||
| | `VECTOR_LOG` | `warn` in deployed environments | | ||
|
|
||
| The two endpoints are handled in opposite ways. Copying the portal's logs URL verbatim | ||
| gives a doubled path and a 404. | ||
|
|
||
| ## Output | ||
|
|
||
| Loki labels: `source`, `app_name`, `space_name`, `source_type`, `instance_id`, `level`. | ||
|
|
||
| `process_instance_id` and the cell `index` stay on the event and are deliberately not | ||
| promoted. They are per-container and per-cell, and unbounded label values break Loki. | ||
| Same reasoning as `container.id` in `infra/supabase/MONITORING.md`. | ||
|
|
||
| `source` is load-bearing. Alert rules select on it, and a rule matching no series applies | ||
| cleanly and never fires. | ||
|
|
||
| Metrics are named `cf_<name>`: `cf_cpu`, `cf_cpu_entitlement`, `cf_memory`, | ||
| `cf_memory_quota`, `cf_disk`, `cf_disk_quota`, `cf_rx_bytes`, `cf_tx_bytes`, | ||
| `cf_log_rate`, `cf_log_rate_limit`, `cf_container_age`. All are gauges. `rx_bytes` and | ||
| `tx_bytes` are cumulative, so `rate()` works, but a container restart appears as a drop | ||
| rather than a counter reset. | ||
|
|
||
| STACKIT's ingest adds its own `receive` and `tenant_id` labels. Do not select on them. | ||
|
|
||
| ## Frame shape | ||
|
|
||
| Useful when changing the remap in `vector.yaml`. Everything CF sends is an RFC5424 | ||
| syslog frame: | ||
|
|
||
| ``` | ||
| <14>1 2026-07-30T12:08:43Z baergpt.staging.log-emitter 6c5662d9-... [APP/PROC/WEB/0] - [tags@47450 ...] drain-probe | ||
| │ │ │ │ │ │ │ │ │ | ||
| │ │ │ │ │ │ │ │ └─ message | ||
| │ │ │ │ │ │ │ └─ structured data | ||
| │ │ │ │ │ │ └─ msgid: unused by CF | ||
| │ │ │ │ │ └─ procid: source type and instance | ||
| │ │ │ │ └─ app-name: the app GUID | ||
| │ │ │ └─ hostname: org.space.app | ||
| │ │ └─ timestamp | ||
| │ └─ version | ||
| └─ PRI: facility x 8 + severity | ||
| ``` | ||
|
|
||
| PRI packs two numbers into one: `14` is facility 1 (user) times 8 plus severity 6 (info). | ||
| Vector decodes it to a `severity` string, which becomes the `level` label — a line written | ||
| to stderr arrives as `<11>`, severity 3, and is labelled `error` with no work from the app. | ||
|
|
||
| Structured data is exposed under its full element id: `."tags@47450"`, `."gauge@47450"`, | ||
| `."counter@47450"`. Vector also sets its own top-level `source_type` (`http_server`), so | ||
| CF's is carried as `cf_source_type` and renamed in the Loki sink's label map. | ||
|
|
||
| Logs and metrics share one connection and differ in four fields: | ||
|
|
||
| | | Log frame | Metric frame | | ||
| | ------------------------------------- | ------------------------------------------- | ---------------------- | | ||
| | `message` | the line as written | empty | | ||
| | `procid` | source type (`[APP/PROC/WEB/0]`, `[RTR/5]`) | instance index (`[0]`) | | ||
| | `."tags@47450".source_type` | the clean source type | absent | | ||
| | `."gauge@47450"` / `."counter@47450"` | absent | the value, as a string | | ||
|
|
||
| ## Access control | ||
|
|
||
| CF authenticates using credentials in the drain URL's userinfo | ||
| (`https://user:pass@<bridge-route>/`). That is its only mechanism, so the same pair must | ||
| be set on this app and in the drain URL. Rotating means changing both together. | ||
|
|
||
| This is load-bearing rather than defence in depth: Loki labels are built from fields in | ||
| the received frame, so an open endpoint would let anyone forge structured data and mint | ||
| arbitrary label values in an instance shared by every environment. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # ((image)), ((docker_username)) and ((app_name)) are injected at push time via | ||
| # `cf push --var ...`. Runtime env is set by the deploy workflow from 1Password, not | ||
| # here. | ||
| applications: | ||
| - name: ((app_name)) | ||
| docker: | ||
| image: ((image)) | ||
| username: ((docker_username)) | ||
| instances: 1 | ||
| memory: 256M | ||
| disk_quota: 512M | ||
| # A port check is a TCP connect. An http check would send a request, get 401 from | ||
| # Vector's basic auth, and mark the app crashed. | ||
| health-check-type: port | ||
| default-route: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: technologiestiftung/baergpt
Length of output: 862
🏁 Script executed:
Repository: technologiestiftung/baergpt
Length of output: 9647
Other (CWE-494): Download of Code Without Integrity Check
Reachability: External
Verify the Cloud Foundry CLI artifact before execution.
The workflow stores Cloud Foundry credentials in
GITHUB_ENVbefore downloading the CLI, and the install step fetches a floating v8 archive viatar, then installscf8globally. A compromised or replayed archive can execute under the deployment identity. Pin an exactcfrelease and check its published checksum or signature before extraction/install.🤖 Prompt for AI Agents