diff --git a/skills/sg-create-connector/SKILL.md b/skills/sg-create-connector/SKILL.md new file mode 100644 index 0000000..5d95aff --- /dev/null +++ b/skills/sg-create-connector/SKILL.md @@ -0,0 +1,273 @@ +--- +name: sg-create-connector +description: Guided flow for creating a StackGuardian connector (integration) with the create_connector tool — a cloud connector that workflows deploy through (AWS OIDC or IAM role with external id, Azure service principal with federated credentials or managed identity, GCP Workload Identity Federation, or static keys / client secret / service-account key) or a version-control connector that clones private repos (GitLab token, Bitbucket API or access token, Azure DevOps PAT or service principal, custom GitHub App / GitHub Enterprise). Walks the user through the cloud-side trust setup FIRST (OIDC provider, trust policy, federated credential, workload identity pool) with exact per-region values, then creates and tests the connector and hands back the reference for create_workflow / create_stack / update_workflow. Use when the user wants to connect a cloud account, subscription or project, set up AWS/Azure/GCP credentials, OIDC or a role for StackGuardian, connect GitLab/Bitbucket/Azure DevOps/a GitHub App, or when create_workflow / create_stack / create_policy reports that no suitable connector exists. +--- + +# Create Connector — Guided Flow + +A connector is an org-wide, stored credential: a **cloud** connector gives +workflow runs access to an AWS account, Azure subscription or GCP project; a +**version-control** connector lets StackGuardian clone private repositories. +Follow the steps IN ORDER. Never surface parameter names like `roleArn`, +`armClientId`, `gcpConfigFileContent` or `validateConnection`; speak of "the +role ARN", "the client id", "the configuration file", "testing the +connection". Fetch ids with tools; never ask the user to recall them. + +## Step 0 — Reuse beats create + +1. List what exists: + `search_orchestrator_resources(resourceTypes=['INTEGRATION.CLOUD'])` or + `['INTEGRATION.VCS']`, with `searchQuery=`. +2. If one fits, inspect it with `get_connector(connectorId=)` (kind, auth + method, role ARN / ids, last authentication) and offer it. If it may be + stale, `authenticate_integration(integrationId=)` tests it. A working + existing connector ends this skill: hand back its `referenceAs` (Step 8). +3. Create only when nothing fits, or the user explicitly wants a new one. + +## Step 1 — Provider and auth method + +Ask which provider, then offer its methods, recommended first. Say why in one +line: the recommended methods pass **nothing secret through this chat**. + +| Provider | Method (kind) | One-line trade-off | +|---|---|---| +| AWS | OIDC (`AWS_OIDC`) — recommended | Short-lived tokens, nothing to rotate; needs an IAM OIDC provider + role. | +| AWS | IAM role + external id (`AWS_RBAC`) | No stored key; StackGuardian's AWS accounts assume your role. | +| AWS | Access keys (`AWS_STATIC`) | Long-lived key passes through this chat; rotate it yourself. | +| Azure | Service principal, federated (`AZURE_OIDC`) — recommended | No secret; federated credential on an app registration. | +| Azure | Managed identity (`AZURE_MANAGED_ID_OIDC`) | No secret; federated credential on a user-assigned identity. | +| Azure | Service principal, client secret (`AZURE_STATIC`) | Secret passes through this chat and expires. | +| GCP | Workload Identity Federation (`GCP_OIDC`) — recommended | No key; the config file holds no secret. | +| GCP | Service-account key (`GCP_STATIC`) | Private key passes through this chat. | +| GitLab | Token (`GITLAB_COM`) | Personal, group or project access token. | +| Bitbucket | API token or workspace access token (`BITBUCKET_ORG`) | App passwords are refused (deprecated by Atlassian). | +| Azure DevOps | Service principal (`AZURE_DEVOPS_SP`) or PAT (`AZURE_DEVOPS`) | Federated SP needs no secret; a PAT is tied to one user. | +| GitHub Enterprise / own App | Custom GitHub App (`GITHUB_APP_CUSTOM`) | Several secrets (client secret, private key, webhook secret). | +| GitHub.com | — | Dashboard only (see Out of scope). | + +## Step 2 — Values every OIDC setup needs + +Ask which StackGuardian region the user logs into, and their org name (it is +in the dashboard URL after `/orgs/`, or in any full `/orgs//...` id a tool +returns). Then use: + +| | EU (`app.stackguardian.io`) | US (`us.stackguardian.io`) | +|---|---|---| +| Issuer = audience (``) | `https://api.app.stackguardian.io` | `https://api.us.stackguardian.io` | +| Subject | `/orgs/` | `/orgs/` | + +The subject is the same for every connector in the org. The run container +reads the token from `/mnt/sg_workspace/user/stackguardian.oidc`. + +## Step 3 — Cloud-side setup FIRST + +Walk the user through their branch and wait until they confirm it is done +(except AWS role without an external id — see 3b). Permissions are the +user's choice: recommend least privilege (read-only for discovery, scoped +write for deployments), never admin by default. The dashboard also offers a +CloudFormation quick setup for AWS (Connectors → Cloud providers); mention it, +but do not paste a CloudFormation link yourself. + +### 3a. AWS OIDC +1. IAM → Identity providers → Add provider → OpenID Connect; provider URL and + audience both `` + (`aws iam create-open-id-connect-provider --url --client-id-list `). +2. Create a role with this trust policy (`` = `` without + `https://`), attach the permissions policy the user chooses, and copy the + role ARN: + ```json + { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Federated": "arn:aws:iam:::oidc-provider/"}, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": {"StringEquals": { + ":aud": "", + ":sub": "/orgs/" + }} + }] + } + ``` + +### 3b. AWS role + external id +The role trusts StackGuardian's AWS accounts and requires an external id that +contains the org name: +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"AWS": ["arn:aws:iam::476299211833:root", "arn:aws:iam::163602625436:root"]}, + "Action": "sts:AssumeRole", + "Condition": {"StringEquals": {"sts:ExternalId": ""}} + }] +} +``` +Two orders — ask which the user prefers: +- **User picks the external id** (must contain the org name, e.g. + `:`): they create the role with it first, then Step 5 as + normal, passing it. +- **Let StackGuardian generate it**: agree the role name now (the ARN is + predictable: `arn:aws:iam:::role/`), create the connector in + Step 5 with connection testing OFF and no external id, take + `generated.externalId` from the response, have the user create the role + with it in the trust policy, then test with `authenticate_integration`. + +Session length defaults to 1 hour; ask only if the user needs longer runs +(15 min – 12 h). + +### 3c. Azure — service principal (federated) or managed identity +1. Service principal: Entra ID → App registrations → New registration + (single tenant). Managed identity: create a user-assigned managed identity. +2. Add a federated credential, scenario "Other issuer": issuer ``, + subject `/orgs/`, audience ``, name e.g. `StackGuardian-Trust`. + (`az ad app federated-credential create --id --parameters '{"name":"StackGuardian-Trust","issuer":"","subject":"/orgs/","audiences":[""]}'`, + or `az identity federated-credential create --identity-name -g --name StackGuardian-Trust --issuer --subject /orgs/ --audiences `.) +3. Role assignment on the subscription: Reader for discovery, Contributor or a + narrower custom role for deployments. +4. Collect tenant id, subscription id and client id (the app's application id, + or the managed identity's client id) — all GUIDs. + +### 3d. GCP Workload Identity Federation +1. Create a workload identity pool, then an OIDC provider in it: issuer + ``, allowed audience ``, attribute mapping + `google.subject = assertion.sub`. +2. Grant access via service-account impersonation to a least-privilege + service account, for principals with subject `/orgs/` + (member `principal://iam.googleapis.com/projects//locations/global/workloadIdentityPools//subject//orgs/`, + role `roles/iam.workloadIdentityUser`). +3. Download the client library config with OIDC token path + `/mnt/sg_workspace/user/stackguardian.oidc` (or + `gcloud iam workload-identity-pools create-cred-config projects//locations/global/workloadIdentityPools//providers/ --service-account= --credential-source-file=/mnt/sg_workspace/user/stackguardian.oidc --output-file=sg-gcp.json`). + It must have `"type": "external_account"`, `audience` = + `//iam.googleapis.com/projects//locations/global/workloadIdentityPools//providers/`, + `token_url` = `https://sts.googleapis.com/v1/token`, and + `credential_source.file` = the token path with no `format` or + `{"type": "text"}` (the token file is a bare JWT, so a `json` format fails + every run). It holds no key, so it is safe to paste. + +For `GCP_STATIC`, use the service-account key file exactly as Google issued +it: it must keep `token_uri` (`https://oauth2.googleapis.com/token`), which +runs need even though the connection test passes without it. + +## Step 4 — Static credentials and VCS tokens + +### Handling credentials safely +Applies to AWS access keys, Azure client secrets, GCP service-account keys, +every VCS token and the GitHub App secrets. Before asking, tell the user: +- Prefer a recommended method from Step 1 when one fits. +- Use a **dedicated, least-privilege, ideally short-lived** credential made + for StackGuardian — never a personal, root or admin key. +- The value becomes part of this AI conversation (and any transcript or log + of it). If that is not acceptable, create the connector in the StackGuardian + dashboard instead. Rotate the credential if the conversation is shared. +- No tool ever returns it; it cannot be read back, only replaced. + +Then ask them to paste it **once**, pass it straight to the tool, and never +repeat, summarise or store it elsewhere in the conversation. Relay the +response's `credentialHandling` note. + +### What each VCS provider needs +- **GitLab**: personal token with scope `api` (needs the GitLab username), or + a group/project token with role Reporter or higher and scopes `api` + + `read_repository`. Self-managed or Dedicated: also the web URL + (`https://gitlab.example.com`) and API URL (`.../api/v4`). +- **Bitbucket**: an **API token** (needs username + Atlassian email) with + Read on Account, Me, Project, Pull request, Repository, User, Workspace; or + a **workspace access token** with Read on Account, Projects, Repositories. + Repository/project access tokens can't list repositories — steer away. +- **Azure DevOps service principal**: the app (Step 3c, federated, or with a + client secret — fixed at creation) added as a user to the DevOps org with + Basic access and at least Reader on the projects; collect tenant, + subscription and client ids. **PAT**: a token with at least Code (Read). +- **Custom GitHub App / GitHub Enterprise**: create the App with repository + permissions Read on contents, metadata, webhooks and Read & write on checks, + code scanning alerts, commit statuses, pull requests; organization Read on + webhooks; subscribe to the push / pull request / create / delete / check + events the user needs. Webhook URL: + `/api/v1/webhooks/github/?integrationId=/orgs//integrations/` + (exactly what the dashboard shows — the platform does not read the query + value, so don't append the connector id); + webhook secret: a random string the user generates and also sets on the + App. Collect App id, client id, client secret, installation id (from the URL + after installing), private key (PEM as-is is fine). Enterprise: web URL + `https://` and API URL `https:///api/v3`. + +A plain git host with no provider connector uses a token stored as a secret +instead — switch to the `sg-manage-secrets` skill. + +## Step 5 — Name, confirm, create + +1. Ask for a connector name; the id defaults to its slug (lower-case letters, + digits, `_`, `-`). Keep AWS OIDC ids short — the tool warns when the + AWS session name would exceed 64 characters. Optional description and tags. +2. Show a summary: provider, method, name/id, the non-secret values (role + ARN, tenant/subscription/client ids, project from the GCP file, VCS URLs), + "a credential is set" (never the value), whether the connection will be + tested, and discovery (off unless asked, Step 7). Wait for a yes. +3. Call `create_connector(name=..., config={"kind": , ...fields}, + connectorId=..., description=..., tags=...)`. Testing the connection is on + by default; pass `validateConnection=false` only for the AWS + generated-external-id order (3b) or when the trust is knowingly not ready. + +## Step 6 — Interpret the result + +- **Success** → note `authentication`, relay `warnings`, continue to Step 8. +- **`authenticationFailed`** → the provider rejected the probe. Walk the + checklist, fix, retry: + - AWS OIDC: provider URL and audience exactly `` for the right region; + trust `aud`/`sub` conditions spelled as in 3a; correct account id in the + provider ARN; role ARN copied right. + - AWS role: both principals trusted; external id identical in trust and + connector; role ARN right. + - Azure: federated credential issuer/subject/audience exact (no trailing + slash); role assignment on that subscription; client id is the app's or + identity's, not the object id. + - GCP: pool provider issuer/audience; `google.subject = assertion.sub` + mapping; impersonation granted to subject `/orgs/`; file is the + downloaded `external_account` config. + - Static/VCS: key or token expired, revoked, or missing scopes above; + self-hosted URL reachable from the internet. + If the user wants to save first and fix later, re-call with testing off — + except GCP workload identity federation: the platform must reach Google to + resolve the project, so it can't be saved until the trust works (the tool + refuses and says why). + After a failed create that carried a secret, retry under a **new** + connector id — a retry on the same id can silently keep the first value. +- **`idTaken`** → show `existingConnector` (name, kind, auth method) and ask: + reuse it (go to Step 8) or pick another id. +- **`warnings`** → relay in plain words: a URL host that doesn't resolve + publicly (StackGuardian may not reach a private host; private runners may + still work), the AWS session-name length, a GCP token path that isn't the + StackGuardian one. + +## Step 7 — Optional discovery (only if the user asks) + +Only when the user wants cloud inventory / compliance scanning on this +connector: pass `discovery={"enabled": true, "regions": [...], +"intervalMinutes": 1440 | 10080}` (daily or weekly). AWS needs at least one +region code (e.g. `eu-central-1`); Azure and GCP use `["all"]`. Say it runs +scans in the background. It can also be added later with +`update_connector(connectorId=..., discovery=...)`. + +## Step 8 — Hand back + +1. If the connection wasn't tested at creation, or the trust was set up + afterwards, run `authenticate_integration(integrationId=)` and + relay its `diagnosis` when it fails. +2. Give the user the response's `referenceAs` and continue where they came + from: + - cloud → the deployment connector in `sg-create-workflow`, `sg-create-stack` or + `sg-update-workflow` (`deployment={"connectorId": "/integrations/"}`); + - VCS → the private repo's git source credential in `sg-create-workflow` or + `sg-create-stack` (`gitAuthRef="/integrations/"`). +3. Later edits or rotation use `update_connector` (send only the new secret + value; never send back a masked one). + +## Out of scope — send the user to the dashboard + +- Connector groups: multi-account AWS Organizations and Azure + multi-subscription setups. +- GitHub.com via the StackGuardian GitHub App (browser install) and Azure + DevOps OAuth (browser consent). The tool refuses both kinds. diff --git a/skills/sg-create-policy/SKILL.md b/skills/sg-create-policy/SKILL.md index 154f9e9..c0b6e25 100644 --- a/skills/sg-create-policy/SKILL.md +++ b/skills/sg-create-policy/SKILL.md @@ -40,7 +40,9 @@ Ask what the user wants to enforce. Map it to a rule source: `tirith.json` (`kind: SG_POLICY_FRAMEWORK`). Ask whether the repo is private; if so, the credential is a connector or secret POINTER — offer the org's VCS connectors (the tool returns `availableVcsConnectors` - on a miss). Never put a raw token in `auth`. + on a miss). No suitable connector? Continue with the `sg-create-connector` + skill, then come back with its connector id. Never put a raw token in + `auth`. Rule `name` is a slug (letters, digits, `_`, `-`). diff --git a/skills/sg-create-stack/SKILL.md b/skills/sg-create-stack/SKILL.md index 2f76198..02d9e00 100644 --- a/skills/sg-create-stack/SKILL.md +++ b/skills/sg-create-stack/SKILL.md @@ -87,7 +87,8 @@ The dashboard offers these **stack-wide** (applied to every workflow) or `connectorId` is required; the kind is resolved live. A per-workflow connector goes in that workflow's entry as `workflows[].deployment` and overrides the stack-wide one for that workflow only. Omitting both keeps - whatever each workflow template declares. + whatever each workflow template declares. No suitable connector? Continue + with the `sg-create-connector` skill, then come back with its connector id. 2. Environment variables: collect each as `{varName, textValue}` or `{varName, secretId}` (`/secrets/`), exactly one of the two. Stack-wide ones go in `environmentVariables`; per-workflow ones in diff --git a/skills/sg-create-workflow/SKILL.md b/skills/sg-create-workflow/SKILL.md index 49a69d3..36f0d51 100644 --- a/skills/sg-create-workflow/SKILL.md +++ b/skills/sg-create-workflow/SKILL.md @@ -66,6 +66,8 @@ into the other: `source.gitAuthRef="/integrations/"` (use the full `/integrationgroups//integrations/` form if the connector is group-managed or the tool reports an ambiguity with `candidates`). + No suitable connector? Continue with the `sg-create-connector` skill, + then come back with its connector id. Setting `gitAuthRef` implies private — no need to also send `isPrivateRepo`. For a plain git host with no connector, a `/secrets/` token is the alternative (GIT_OTHER hosts accept @@ -121,6 +123,8 @@ into the other: `deployment.connectorKind` unless a returned error asks for it. Find connectors via `search_orchestrator_resources(resourceTypes=['INTEGRATION.CLOUD'])`. + No suitable connector? Continue with the `sg-create-connector` skill, then + come back with its connector id. 2. Ask whether to add environment variables. Collect each as `{varName, textValue}` (plain text) or `{varName, secretId}` where `secretId` is a `/secrets/` vault reference — exactly one of the @@ -295,7 +299,16 @@ parameter entirely otherwise (its defaults still apply omitted): - `githubComSync`: advanced GitHub PR-trigger behavior, e.g. `{"pull_request_opened": {"createWfRun": {"enabled": true}}}`. Free-form and NOT validated by the platform, so a wrong key fails silently — most - workflows omit it entirely. + workflows omit it entirely. For push/PR/tag triggers use `vcsTriggers`. +- `vcsTriggers`: git events that start runs, e.g. `{"onPush": true, + "pullRequests": "trackedBranch", "trackedBranch": "main", + "approvalPreApply": true}`. **Git source only**, and the repo must be private + with `gitAuthRef` set (on GitLab, Bitbucket and Azure DevOps, a VCS + connector rather than a stored token). PR runs only plan; push and tag runs + apply unless `planOnly` or `approvalPreApply` is set. `fileFilters` (fnmatch + globs such as `["stacks/vpc/*"]`) limits runs to matching changes. The triggers are + registered right after the create; if that fails, the workflow still exists + and the response's `warnings` says how to retry. Confirm a plain-language summary of everything collected with the user, then call `create_workflow` with all parameters. diff --git a/skills/sg-manage-connectors/SKILL.md b/skills/sg-manage-connectors/SKILL.md new file mode 100644 index 0000000..1e664bd --- /dev/null +++ b/skills/sg-manage-connectors/SKILL.md @@ -0,0 +1,216 @@ +--- +name: sg-manage-connectors +description: Guided flow for managing an existing StackGuardian connector (integration) with get_connector, authenticate_integration, update_connector, trigger_connector_discovery_scan and delete_connector. Covers inspecting a connector (auth method, which credentials are set, last test result, discovery), testing it, editing it (rename, description, tags, context tags, role ARN, tenant / subscription / client id, session duration, GitLab / Azure DevOps / GitHub Enterprise URLs), rotating a credential (AWS access key, Azure client secret, GCP service-account key, GitLab / Bitbucket / Azure DevOps token, GitHub App private key / client secret / webhook secret), enabling, disabling or re-scoping cloud inventory discovery and starting a discovery scan, and deleting a connector after an impact report of the workflows, stacks, runner groups, templates and policies that use it. Use when the user wants to edit, rename, retag, rotate, re-key, update, test, check or delete a connector or integration, switch its role ARN or tenant, turn discovery on or off, scan a cloud account now, or asks "what uses this connector". +--- + +# Manage Connectors — Guided Flow + +A connector is an org-wide stored credential: **cloud** (AWS, Azure, GCP — +a workflow's deployment connector) or **version control** (GitLab, +Bitbucket, Azure DevOps, custom GitHub App — clones private repos). Creating +one is the `sg-create-connector` skill; a connector that fails, or a run that +failed because of one, is the `sg-troubleshoot-connector` skill. This skill +covers everything in between. Follow the steps IN ORDER, skipping those the +user didn't ask for, but always start with Step A. + +Never surface parameter or field names (`connectorId`, `config`, +`secretFieldsSet`, `awsSecretAccessKey`, `validateConnection`, +`confirmDelete`...) to the user. Speak of "the connector", "the role ARN", +"the client secret", "testing the connection", "confirming the delete". +Fetch ids with tools; never ask the user to recall them. + +## Step A — Find and inspect + +1. Find it: `search_orchestrator_resources(resourceTypes=['INTEGRATION.CLOUD', + 'INTEGRATION.VCS'], searchQuery=)`. A + connector created a moment ago may not be indexed yet; if the user knows + the id, go straight to step 2. +2. `get_connector(connectorId='/integrations/')` (add + `includeScanInfo=true` when discovery matters, e.g. Step E). Tell the user, + in plain words: + - **What it is**: name, provider, and the auth method (OIDC / role with + external id / access keys / token / GitHub App / service principal, + federated or with a secret / managed identity). + - **Its settings**: the non-secret values — role ARN, tenant / + subscription / client ids, GCP project, region, session duration, VCS + URLs, description, tags. + - **Which credentials are set** — by name only ("an access key secret is + stored"). Values are never readable; nothing can show them, only replace + them. + - **Last test**: passed / failed and when, or never tested. + - **Discovery** (cloud only): on or off, regions, daily or weekly, last + scan. +3. The id in the response is permanent. Keep it for every later call. + +A connector **group** (multi-account AWS Organizations or Azure +multi-subscription) can be read here but not changed — see Out of scope. + +## Step B — Test + +1. `authenticate_integration(integrationId='/integrations/')`. It is a + live check at the provider that also updates the stored status: once per + ask, never in a loop. +2. Read `diagnosis` first. Passed with no diagnosis → say so and stop. +3. Not every failure is a credential verdict: + - **Rate-limited (429)** or **not responding** → no verdict; retry later, + and for not-responding check a self-hosted host is reachable from the + internet. Never tell the user to rotate on these. + - **Private** → StackGuardian deliberately doesn't probe this git host + (only private runners reach it). No verdict either way. +4. A real failure (rejected token, missing scope, cloud rejected the trust) + → switch to the `sg-troubleshoot-connector` skill, which has the + cause-by-cause table. Don't guess fixes here. + +## Step C — Edit settings + +For name, description, tags, context tags, and non-secret config: role ARN, +session duration, tenant / subscription / client ids, region, VCS URLs. +An Azure DevOps connector's URLs can't be changed here (the platform drops +them on edit) — create a new connector for a different host. + +1. Confirm the change in plain words, then call + `update_connector(connectorId=..., )`: + - name / description: `name=`, `description=`; + - tags: `tags=[...]` replaces the whole list — start from the current + tags when the user adds or removes one; + - context tags: `contextTags={"key": "value"}` merges; `{"key": null}` + removes that key; + - config: `config={}`, e.g. + `{"roleArn": ...}`, `{"armTenantId": ...}`, `{"durationSeconds": 7200}`. + The tool keeps every stored key you don't send, secrets included. +2. Testing the connection is on by default whenever the config changes, and + a failed test means nothing was saved. Leave it on. Turn it off only when + the user is knowingly changing the cloud side afterwards (e.g. a new role + ARN whose trust is created next), and test with Step B once it's done. +3. What can't change: + - The connector's id: workflows reference it, so it is fixed. A rename + changes only the display name. + - Its kind (e.g. access keys → OIDC, PAT → service principal): create a + new connector (`sg-create-connector`), repoint each workflow to it with the + `sg-update-workflow` skill (Step F's report lists them), then delete the old + one (Step F). + - An Azure DevOps service principal's mode (federated vs client secret). +4. Relay `warnings` in plain words. Notably: an edit resets a connector + that was limited to some workflow groups back to **org-wide** (a platform + behaviour) — tell the user, and that they can re-limit it in the dashboard. +5. Report back from the response: what changed, and the test result. + +## Step D — Rotate a credential + +### Handling the credential +Before asking for the new value, tell the user: +- Use a **dedicated, least-privilege, ideally short-lived** credential made + for StackGuardian — never a personal, root or admin key. +- The value becomes part of this AI conversation (and any transcript or log + of it). If that is not acceptable, rotate it in the StackGuardian dashboard + instead. Rotate it again if this conversation is ever shared. +- No tool ever returns it; it cannot be read back, only replaced. + +Then ask them to paste it **once**, pass it straight to the tool, and never +repeat, summarise or store it elsewhere in the conversation. Relay the +response's `credentialHandling` note. + +### Rotating +1. Send **only** the new secret field in `config`, e.g. + `{"awsSecretAccessKey": ...}` (plus `"awsAccessKeyId"` when the key pair + changes), `{"armClientSecret": ...}`, `{"gcpConfigFileContent": }`, `{"token": ...}`, `{"azureCreds": ...}`, + `{"githubAppPemFileContent": ...}`, `{"githubAppClientSecret": ...}`, + `{"githubAppWebhookSecret": ...}`. Field names per kind are in the + `create_connector` tool description. +2. **Never send back anything `get_connector` showed as hidden or + `[REDACTED]`** — that is a mask, not the secret, and sending it would + overwrite the real value. The tool refuses it; don't try to work around + that. Omitted secrets stay as stored. +3. Per-provider notes: + - **AWS access keys**: suggest moving to OIDC or a role with external id + instead — nothing secret passes through the chat and nothing expires. + That is a new connector (Step C, "what can't change"). + - **GitLab**: the token type (personal / group / project) and hosting + (gitlab.com / self-managed / dedicated) are fixed at creation; a + different type means a new connector. A personal token's username + travels with the token — send both when either changes. + - **Bitbucket**: re-validates its credential on **every** settings + change, so any Bitbucket config edit (not just a rotation) must include + the token — the current one or a new one — and, for an API token, the + username and Atlassian email if they changed. + - **GitHub App**: rotate each secret separately as GitHub issues it; a new + webhook secret must also be set on the App itself. +4. Testing stays on: a rejected new value is not saved. On failure → + `sg-troubleshoot-connector`. +5. Tell the user: workflows use the new credential from their next run; + runs already in progress keep the old one. Once the next run works, they + should revoke the old key or token at the provider. + +## Step E — Discovery (cloud connectors only) + +Discovery runs scheduled cloud inventory scans. It is off unless the user +asks. + +1. To turn it on, change regions or interval, or pause it: + `update_connector(connectorId=..., discovery={"enabled": true | false, + "regions": [...], "intervalMinutes": 1440 | 10080})` — daily or weekly + (default weekly). Send only what changes — anything left out keeps its + current value, so pausing is just `discovery={"enabled": false}`. + - **AWS**: one or more region codes (`eu-central-1`, `us-east-1`...) when + turning it on for the first time or changing regions. + - **Azure / GCP**: always every region — omit regions or pass `["all"]`. +2. To scan **now**, only when the user explicitly asks: it starts scan runs + in their account. Call `trigger_connector_discovery_scan(connectorId=...)` + and report how many scans started and how many were skipped. It refuses + when discovery isn't configured — set it up in step 1 first. Results show + up in the cloud inventory once the scans finish; they are not instant. + +## Step F — Delete + +1. Suggest the safer order first: repoint every dependent to another + connector (`sg-update-workflow`), then delete. +2. Call `delete_connector(connectorId=...)` **without** confirming the + delete. Nothing is deleted; the response is an impact report. +3. Show the user what still references it — workflows, stacks, runner + groups (including their storage backends), templates (one row per + template, with the `revisions` whose git source uses it), and policies — + both those attached to the connector (`via: "enforcedOn"`) and those + whose rules pull their policy code from git through it + (`via: "ruleGitAuth"`, naming the `rules`) — and the coverage limits from + `sweepCoverage`: the report can't see Terraform state backends configured + in code, `SG_CONNECTOR_NAME` environment variables, the git auth of + IAC_POLICY / WORKFLOW_STEP templates, or users of a shared template in + other orgs. Ask them to check those themselves. + - **Impact unknown**: if `total` is `null` (`sweepFailed` names the part + that failed or timed out), say so plainly — the list may be incomplete, + never "nothing uses it". Retry the call first. A confirmed delete is + refused while the impact is unknown; the only way past it is + `ignoreSweepFailures=true`, and pass that **only** after the user + explicitly says to delete without knowing what still uses the + connector — never on your own. +4. Say plainly: + - Deleting is permanent. Every dependent still pointing at it **fails at + its next run**; the platform does not repoint or block anything. + - Deleting **never touches the cloud or git side**: the IAM role and OIDC + provider, Azure app registration / federated credential / managed + identity, GCP workload identity pool or service account, access keys, + tokens and GitHub App all stay. Tell the user to remove or revoke them + themselves if they are no longer needed. +5. Only after an explicit yes, call `delete_connector(connectorId=..., + confirmDelete=true)` (plus `ignoreSweepFailures=true` only in the + impact-unknown case above, with that explicit acceptance). If it reports + the connector was already gone, say so — nothing was deleted by this call. + +## "What uses this connector?" + +Without deleting: `cross_reference(targetKind='cloud_integration' | +'git_integration', targetId='/integrations/')` lists the workflows, +stacks and runner groups (cloud) or workflows, stacks and templates (version +control) that reference it; `count=true` gives just the number. Policies +attached to the connector or authenticating their rules' git through it, and +the gaps above, are only covered by Step F's report. + +## Out of scope — send the user to the dashboard + +- Connector groups (multi-account AWS Organizations, Azure + multi-subscription): read-only here; edit, rotate or delete them, and their + child connectors, in the dashboard. +- Re-authorising GitHub.com (StackGuardian GitHub App) and Azure DevOps + OAuth connectors: their credentials come from a browser flow. +- Limiting a connector to specific workflow groups. diff --git a/skills/sg-troubleshoot-connector/SKILL.md b/skills/sg-troubleshoot-connector/SKILL.md new file mode 100644 index 0000000..8630cac --- /dev/null +++ b/skills/sg-troubleshoot-connector/SKILL.md @@ -0,0 +1,203 @@ +--- +name: sg-troubleshoot-connector +description: Guided diagnosis for a StackGuardian connector (integration) that is not working, starting from a failed workflow run or from a named connector. Reads the run's error and logs, identifies the cloud or version-control connector the run actually used, tests it with authenticate_integration, matches the exact platform error text to a cause, and walks the fix (cloud-side trust, rotating or editing the connector with update_connector, workflow wiring), then re-tests. Use when a run failed with a connector, credential, authentication, permission or git clone error; "connector not working", "credentials expired", "token expired/revoked"; AssumeRole / AssumeRoleWithWebIdentity / OIDC / federated credential / workload identity errors; AWS_INTEGRATION_ERROR, GIT_ERROR, "is not correctly configured", "Connector ... does not exist", "Failed to authenticate against role"; a private repo that won't clone; or authenticate_integration / a connector test reports a failure. +--- + +# Troubleshoot Connector — Guided Flow + +A connector is an org-wide stored credential: **cloud** (AWS, Azure, GCP — +used as a workflow's deployment connector) or **version control** (GitLab, +Bitbucket, Azure DevOps, GitHub App — used to clone a private repo). Follow +the steps IN ORDER and stop as soon as the cause is clear. + +Rules for the whole flow: +- Never surface parameter or field names (`deploymentPlatformConfig`, + `gitAuthRef`, `roleArn`, `validateConnection`, `AdminTaskError`...) to the + user. Say "the workflow's cloud connector", "the repo credential", "the role + ARN", "testing the connection", "the run's start error". +- Never ask for, print or summarise a credential value. Values shown as + `[REDACTED]` are masks, not the real secret — never send one back. +- Never start a run yourself. When a re-run is the next step, say so and name + `create_workflow_run` as the action the user can approve. +- `authenticate_integration` is a live check that also rewrites the stored + status. Call it once per diagnosis and once after a fix — never in a loop. + +## Step 1 — Get the error + +**From a failed run.** +1. Find it: `search_workflow_runs(searchQuery=, + wfrunStatuses=['PLAN_FAILED','APPLY_FAILED','DESTROY_FAILED'])`, or + `parentId=` for one workflow's history (drop the status + filter if nothing comes back). Take `WfRunFullId`. +2. `get_workflow_run(workflowRunId=)` — the live status (the search index + can lag). In the raw `run`, read: + - `AdminTaskError`: set when the run failed **before** a runner started + (connector lookup, credential fetch). Logs may then be empty. + - `SGInternals.RuntimeErrors[].name/message`: a named code + (`AWS_INTEGRATION_ERROR`, `GIT_ERROR`, `ADMIN_JOB_ERROR`, + `SECRET_RESOLUTION_ERROR`). Only private runners record these today; + on shared runners the same failure shows only as an `[SG_ERROR]` line. +3. `get_workflow_run_logs(workflowRunId=, errorsOnly=true)` — the + `[SG_ERROR]` lines. If they are generic ("Something unexpected happened", + "Runtime Exception"), re-read with `errorsOnly=false, tail=true, + maxLines=300`: the provider's own message (AWS STS, Entra `AADSTS…`, Google + IAM, git) sits in the step output just above. + +**From a named connector** ("my AWS connector stopped working"): find it with +`search_orchestrator_resources(resourceTypes=['INTEGRATION.CLOUD', +'INTEGRATION.VCS'], searchQuery=)` and go to Step 3. To find a failing +run that used it, `cross_reference(targetKind='cloud_integration' | +'git_integration', targetId='/integrations/')` lists the workflows, then +`search_workflow_runs(parentId=)` their recent runs. + +## Step 2 — Which connector did the run use? + +Read `run.RuntimeParameters` from Step 1 (it includes per-run overrides): +- **Cloud**: `deploymentPlatformConfig` — **only the first entry is used** + (more than one cloud connector on a workflow is a platform defect, see + Step 6). Its `config.integrationId` is the connector. With no entry, a + `SG_CONNECTOR_NAME` environment variable may name one instead. +- **Version control**: `vcsConfig.iacVCSConfig.customSource` — + `sourceConfigDestKind` is the provider, `config.isPrivate` whether a + credential is needed, `config.auth` the credential: `/integrations/` (a + connector) or `/secrets/` (a vault secret → `sg-manage-secrets` skill). + A workflow built from a library template clones with the **template + owner's** credential, which the user may not control. +- Other places a connector hides: a runner group's storage backend, and a + policy's git source. + +If the run record is unavailable, read the workflow: +`get_orchestrator_resource_by_id(parentId=, resourceId='/wfs/')` +(`DeploymentPlatformConfig`, `VCSConfig`). + +## Step 3 — Read and test the connector + +1. `get_connector(connectorId='/integrations/')` → `kind`, `authMethod`, + non-secret `config` (role ARN, tenant/subscription/client ids, GCP project, + VCS URLs, session duration), `secretFieldsSet` (names only) and the last + stored `authentication`. A not-found error here means the connector was + deleted → the "does not exist" row in Step 4. +2. `authenticate_integration(integrationId='/integrations/')` and read + `diagnosis` first, then `status`, `statusCode`, `reason`, `msg`: + - **`authenticated: true`, no diagnosis** → the credential and trust work + from StackGuardian. The fault is elsewhere: the run used a different + connector (Step 2), the cloud identity lacks a *permission* for what the + run did (AccessDenied / AuthorizationFailed / 403 on a specific API in + the logs — grant it; that is not a connector problem), or a + runner-specific difference (Step 4). + - **401 / DISCONNECTED** → the token or key was rejected: revoked, + expired, or mis-entered → rotate it (Step 5). + - **403** → it authenticates but lacks a scope; `reason` names it. + - **429 (rate-limited) and NOT_RESPONDING (unreachable)** are **not + credential verdicts**. Never tell the user to rotate on these. Retry + later; for NOT_RESPONDING check that a self-hosted host is reachable + from the internet (firewall, IP allow list). + - **PRIVATE** → StackGuardian deliberately does not probe this connector + (a git server only private runners can reach). No verdict either way; + the only real test is a run on a private runner that can reach the host. + - **Cloud `authenticated: false`** → the cloud rejected the stored + credential or the trust; match the kind's rows in Step 4. + +## Step 4 — Symptom → cause → fix + +Match error text as a **substring**; `<…>` parts vary. Rows marked *(provider)* +quote AWS/Microsoft/Google/git wording, which can vary by tool version. + +### Any connector + +| Error text / symptom | Cause | Fix | +|---|---|---| +| `Connector does not exist` | The workflow points at a deleted connector (deletes don't check references). | Pick a replacement or recreate it (`sg-create-connector`), then repoint the workflow (`sg-update-workflow`). `cross_reference` on the old id lists every other workflow, stack and runner still pointing at it. | +| `Failed to fetch Integration from SG_CONNECTOR_NAME environment variable` | That variable names a connector that doesn't exist (format `` or `/`). | Fix the variable, or set a deployment connector on the workflow. | +| Two or more cloud connectors on one workflow; the run uses the wrong account | Only the first is used (platform defect). | Keep one cloud connector per workflow, or put the intended one first (`sg-update-workflow`). | +| `Failed to generate AWS credentials for Storage Backend (S3)` | The runner group's storage-backend role can't be assumed. | Fix that role's trust / external id — it is the runner group's connector, not the workflow's. | + +### AWS + +| Error text / symptom | Cause | Fix | +|---|---|---| +| `AWS Integration Error. Failed to authenticate against role configured in AWS Integration.` (`AWS_INTEGRATION_ERROR`) | Role + external id connector: the role's trust doesn't allow StackGuardian (principals `arn:aws:iam::476299211833:root` and `arn:aws:iam::163602625436:root`), the external id differs between trust and connector, or the role ARN is wrong. | Compare the trust policy with `sg-create-connector` §3b; the external id there must equal the connector's. Also check the session duration below. | +| Session duration missing or empty in `get_connector` config, run fails at start or with `AWS_INTEGRATION_ERROR` | Platform defect: no default is applied. | `update_connector(config={"durationSeconds": 3600})`. | +| *(provider)* `InvalidIdentityToken` / `No OpenIDConnect provider found` | OIDC: no identity provider in that AWS account for the region's issuer. | Create it with URL and audience = the issuer (`sg-create-connector` §2/§3a). | +| *(provider)* `AccessDenied` on `AssumeRoleWithWebIdentity` | OIDC trust conditions don't match: `:aud` must be the issuer URL, `:sub` must be `/orgs/`; provider ARN must be in the role's account. | Correct the trust policy (`sg-create-connector` §3a). | +| OIDC fails for one region's org only, or after an org move | Wrong issuer: EU is `https://api.app.stackguardian.io`, US is `https://api.us.stackguardian.io`. Ask which dashboard the user logs into. | Add the right provider and fix the trust keys (`` = issuer without `https://`). | +| Connection test fails only for a long connector id; trust looks right | Older platform builds sent a session name over AWS's 64-character cap. Current builds shorten it. | Recreate with a shorter id, or report it as a platform defect (Step 6). | +| `AWS Integration '' is not correctly configured.` | Access-key connector: the stored key could not be read. | Re-enter the keys (Step 5); prefer moving to OIDC. | +| Works on shared runners, fails on a private runner (role + external id) | The role is assumed by StackGuardian when the run starts; if that fails, a private runner falls back to its **own host's** instance role, which the trust must then allow. | Trust StackGuardian's principals with the external id, or also trust the runner host's role. | + +### Azure + +| Error text / symptom | Cause | Fix | +|---|---|---| +| `Azure Integration '' is not correctly configured.` | The connector's stored ids/secret could not be read. | Re-enter them with `update_connector` (Step 5). | +| *(provider)* `AADSTS70021` (no matching federated identity record) | Federated credential mismatch: issuer and audience must be the region's issuer URL, subject `/orgs/` — exact, no trailing slash, scenario "Other issuer". | Fix the federated credential on the app or managed identity (`sg-create-connector` §3c). | +| *(provider)* `AADSTS7000222` / `AADSTS7000215` (client secret expired / invalid) | Client-secret connector: the secret expired or was mis-entered. | New secret in Entra ID → rotate (Step 5); consider federated instead. | +| *(provider)* `AuthorizationFailed` | Authenticated, but no role assignment on that subscription (or the subscription id is wrong). | Assign Reader/Contributor/custom role on the subscription. | +| Client id rejected | The object id was used instead of the application (client) id. | `update_connector(config={"armClientId": })`. | +| `Failed to authenticate to Azure Key Vault ''. Deployment platform is not Azure` | A Key Vault secret reference needs an Azure deployment connector on the workflow. | Set an Azure cloud connector (`sg-update-workflow`). | + +### GCP + +| Error text / symptom | Cause | Fix | +|---|---|---| +| `GCP Integration '' is not correctly configured.` | The stored configuration file could not be read. | Re-upload it with `update_connector` (Step 5). | +| *(provider)* audience mismatch / `invalid_grant` on token exchange | Workload Identity provider: issuer or allowed audience isn't the region's issuer URL, or the file's `audience` names another pool/provider. | Fix the provider, or regenerate the configuration file (`sg-create-connector` §3d). | +| *(provider)* `iam.serviceAccounts.getAccessToken` denied | Impersonation not granted to subject `/orgs/`, or mapping isn't `google.subject = assertion.sub`. | Grant Workload Identity User on the service account to that principal. | +| Terraform works, but `gcloud`/SDK steps can't find credentials on a **private runner** | Platform defect: private runners only get `GOOGLE_CREDENTIALS`, not `GOOGLE_APPLICATION_CREDENTIALS`. | In a custom step write `$GOOGLE_CREDENTIALS` to a file and export `GOOGLE_APPLICATION_CREDENTIALS`, or use a shared runner. | + +### Version control (`GIT_ERROR`, `ADMIN_JOB_ERROR`, start errors) + +| Error text / symptom | Cause | Fix | +|---|---|---| +| *(git)* `Authentication failed`, `could not read Username`, `Repository not found` (`GIT_ERROR`) | Token revoked/expired, missing scope, or no access to that repo. | `authenticate_integration`, then rotate or re-scope (scopes: `sg-create-connector` Step 4). | +| `No authentication method provided to authenticate against private repo` | Repo marked private with no usable credential. When it ends "...specified in the IaC template." the template owner's credential is missing. | Set the repo credential (`sg-update-workflow`); for a library template, the template owner must fix it. | +| `No authentication method provided to authenticate against git ssh repo.` | SSH repo URL with no key. | Store the key as a secret and reference it (`sg-manage-secrets`), or use an https URL with a connector. | +| ` does not exist` (may start `[SG_DEBUG]`) | The repo credential points at a deleted connector or secret. | Repoint it (`sg-update-workflow`) or recreate. | +| `Unsupported 'auth' for private repo of '' sourceConfigDestKind` | The credential is the wrong type for that provider (e.g. a secret where a connector is required). | Use a connector of that provider (`sg-create-connector`). | +| ` not configured. Please configure it under Integrations` | GitHub.com: the StackGuardian GitHub App isn't installed on the repo's owner. | Install it from the dashboard (Connectors → GitHub). | +| `Github App Custom integration () is not configured properly` / `GitHub Enterprise Edition (EE) integration being utilized is not configured correctly.` / `Failed calling GitHub API with error` | Custom GitHub App: App id or private key missing/wrong, installation removed, or the GitHub API host unreachable from shared runners. | Re-enter App id / installation id / private key (Step 5); for an internal GitHub Enterprise use a private runner. | +| `bitbucketEmail is required for API_TOKEN auth` | Bitbucket API-token connector without the Atlassian account email. | Add the email (Step 5). | +| `Failed to get Bitbucket VCS credentials:` (other text) | Token rejected or wrong auth type. | Test, then rotate (Step 5). | +| `Azure DevOps SP integration is missing required ARM credentials` / `...is not configured properly - missing required ARM credentials` | Tenant, subscription or client id missing on the connector. | Fill them in (Step 5). | +| *(provider)* `AADSTS…` during Azure DevOps clone | Federated: issuer/subject/audience mismatch on the app; or the app isn't a user in the DevOps org with at least Reader on the project. | Fix as in the Azure table; add the app to the DevOps org. | +| `OIDC token file not found at` | The run's OIDC token wasn't mounted (Azure DevOps federated or Key Vault). | Re-run once; if it repeats, it is a platform defect (Step 6). | + +## Step 5 — Fix, re-test, hand back + +1. **Cloud-side fixes** (trust policy, OIDC provider, federated credential, + role assignment, token scopes) are done by the user in their cloud or git + provider. Give exact values (issuer, subject, external id, role ARN) from + `get_connector` and `sg-create-connector`, then wait for "done". +2. **Connector-side fixes** use `update_connector(connectorId=..., + config={})`. Connection testing stays on by + default; leave it on. The tool keeps stored secrets you don't send. + - Rotation: send only the new secret field. Before asking for it, follow + the credential-handling guidance in `sg-create-connector` ("Handling + credentials safely", also in `sg-manage-connectors`): dedicated, + least-privilege credential, pasted once, never repeated; or rotate in + the dashboard if it must not pass through this chat. + - The kind can't change (e.g. access keys → OIDC): create a new connector + (`sg-create-connector`), then repoint workflows (`sg-update-workflow`). + - Relay the response's `warnings` — notably that an edit resets a + connector limited to some workflow groups back to org-wide. +3. `authenticate_integration` once more and relay the result. +4. Tell the user the fix applies to the next run. Offer a re-run and name + `create_workflow_run` as the action they can approve — only start it on + their explicit yes. + +## Step 6 — When it's the platform, say so + +If the cause is a known platform defect, tell the user plainly, give the +workaround, and stop iterating on their configuration. Known items +(`docs/connector_api_upstream_bugs.md`): +- **#12** only the first cloud connector on a workflow is used. +- **#13** role + external id connector without a session duration fails. +- **#14** private runners give GCP steps `GOOGLE_CREDENTIALS` only. +- **#2** a retry after a failed connector create can keep the old secret — + create under a new id instead. +- **#3** deleting a connector leaves workflows pointing at it. +- Session names over 64 characters on older platform builds (Step 4, AWS). + +If nothing matches after Steps 1–4, summarise what was checked (connector, +test result, exact error line, runner type) so the user can send it to +StackGuardian support. diff --git a/skills/sg-update-workflow/SKILL.md b/skills/sg-update-workflow/SKILL.md index b9f0f5b..d44bf37 100644 --- a/skills/sg-update-workflow/SKILL.md +++ b/skills/sg-update-workflow/SKILL.md @@ -82,7 +82,11 @@ Ask the user what they want to change, then build ONLY those parameters: Find connectors via `search_orchestrator_resources(resourceTypes=['INTEGRATION.CLOUD'])`. The connector's kind is resolved live; the workflow's existing deploy-profile name - is kept unless you pass `deployment.profileName`. + is kept unless you pass `deployment.profileName`. No suitable connector? + Continue with the `sg-create-connector` skill, then come back with its + connector id. If the change is meant to fix runs failing on connector + authentication, diagnose with the `sg-troubleshoot-connector` skill first — + swapping the connector may not be the fix. - **Environment variables** (`environmentVariables`) — a list of `{varName, textValue}` or `{varName, secretId}` entries. These are UPSERTED by name: entries you pass add or replace matching vars; every existing var you @@ -195,7 +199,14 @@ Ask the user what they want to change, then build ONLY those parameters: template upgrade. - **GitHub PR triggers** (`githubComSync`) — REPLACES wholesale. Free-form and not validated by the platform, so confirm the exact shape with the user - rather than guessing keys. + rather than guessing keys. For push/PR/tag triggers use `vcsTriggers`. +- **Git triggers** (`vcsTriggers`) — MERGES over the stored triggers, so pass + only what changes (`{"planOnly": true}`); turn one off with `false` or + `"pullRequests": "off"` (which also turns off the PR switches in a stored + `githubComSync`). Git source only (private repo with `gitAuthRef`), + and not together with a template upgrade. Sent after any other change in + the same call: if it fails, the other changes stay applied and `warnings` + says so. ## Step C — Confirm and update diff --git a/skills/sg-writing-stackguardian-workflow-terraform/references/execution.md b/skills/sg-writing-stackguardian-workflow-terraform/references/execution.md index f9e167d..d1d744b 100644 --- a/skills/sg-writing-stackguardian-workflow-terraform/references/execution.md +++ b/skills/sg-writing-stackguardian-workflow-terraform/references/execution.md @@ -59,7 +59,7 @@ workflow-step-templates source. | `run_pre_init/pre_plan/post_plan_hooks_on_drift` | run those hooks during drift runs too (default false) | | `wf_step_template_revision_id` | `workflow_from_template` only — custom runtime image | -Not settable: `tfvarsFilePath`, `terraformApplyOptions`. For a tfvars file in the repo use +Not settable: `terraformApplyOptions`. For a tfvars file in the repo use `terraform_plan_options = "-var-file=env/prod.tfvars"` (only plan takes var files). `terraform_config` is replaced **whole** on every update, which the provider handles by always sending the full object.