What to configure in your GitHub repo before the workflows can run against your Azure tenant. Read this once you've finished
authentication-setup.md(App Registration
- permissions on the Azure side) and want to wire it up on the GitHub side.
This is the canonical reference for "which secret / variable / env /
federated credential goes where." If a workflow fails with a
secret … not set error, search this doc for the secret name and it
will tell you where it belongs.
- GitHub Actions Variables — public identifiers (App Reg client ID, tenant ID). Set once at repo level.
- GitHub Actions Secrets — sensitive payloads (tenant.yml, gitleaks license). Stdin form only.
- GitHub Environments —
production,integration,automation, optionallydev. Each gates one set of workflows and pairs with a federated credential on the App Reg. - Federated credentials on the App Reg — one per environment, subject-matched to the environment name.
End-to-end after this, contentops conformance L7 should pass.
Open the repo's Settings → Secrets and variables → Actions → Variables tab.
| Name | Value | Why a Variable, not a Secret |
|---|---|---|
AZURE_CLIENT_ID |
The App Registration's "Application (client) ID" GUID | A client ID by itself authenticates nothing — it just identifies the app. Treating it as a secret leaks the wrong threat model and breaks ${{ vars.AZURE_CLIENT_ID }} references in workflows. |
AZURE_TENANT_ID |
The Entra tenant GUID | Same — tenant IDs are public discovery information. Treat as a Variable. |
The workflows reference these as ${{ vars.AZURE_CLIENT_ID }} and
${{ vars.AZURE_TENANT_ID }} (see .github/actions/pipeline-setup/action.yml).
Open Settings → Secrets and variables → Actions → Secrets tab.
Always use the stdin form — never paste secret values into a
shell history or a --body flag.
| Name | What | When required |
|---|---|---|
TENANT_CONFIG_YAML |
Contents of config/tenant.yml (real GUIDs + workspace names). The composite pipeline-setup action materialises this onto disk at job start. |
Required for every workflow that touches Azure. Set this first. |
TENANT_CONFIG_INTEGRATION_YAML |
Contents of config/tenant.integration.yml if you have a separate integration tenant config. |
Required only for promote-to-integration.yml. Skip unless you have a separate integration tenant. |
GITLEAKS_LICENSE |
Your free org license key from https://gitleaks.io/ | Required for secret-scan.yml once your repo is org-owned. Adopter shortcut: skip the local pre-commit hook + wait for the license before pushing. |
PUBLIC_MIRROR_PAT |
Token with repo scope on the public mirror |
Only the operator needs this. Adopters don't run mirror sync. |
# tenant.yml — the most common one
Get-Content config\tenant.yml -Raw | gh secret set TENANT_CONFIG_YAML --repo <org>/<repo>
# gitleaks license — paste the key when prompted
gh secret set GITLEAKS_LICENSE --repo <org>/<repo>
# Verify (lists names + dates, never shows values)
gh secret list --repo <org>/<repo>The -Raw flag preserves CRLF/UTF-8 exactly; without it,
Get-Content line-by-line pipes can subtly corrupt YAML formatting.
cat config/tenant.yml | gh secret set TENANT_CONFIG_YAML --repo <org>/<repo>
gh secret set GITLEAKS_LICENSE --repo <org>/<repo>
gh secret list --repo <org>/<repo>Open Settings → Environments → New environment. Create the
environments below depending on which workflows you intend to run.
Each environment can carry its own protection rules (required
reviewers, wait timer, deployment branch policy) — those are
optional and orthogonal to the wiring.
| Environment | Required by | What runs there |
|---|---|---|
production |
deploy.yml, integration.yml (the live-tenant integration test) |
Apply detection content to production Sentinel workspaces; live-tenant integration test suite. The most-protected environment. |
integration |
integration-deploy.yml, promote-to-integration.yml |
Apply content to your integration workspace as a PR-time smoke test; copy prod state into integration for parity testing. Skip if you don't have a separate integration workspace. |
automation |
drift.yml, defender-graph-probe.yml, silent-rules.yml |
Read-only cron workflows (drift detection, Defender Graph endpoint probing, silent-rule reporting). Separated from production so deploy protections don't slow down nightly automation. |
dev (optional) |
prune.yml, retry-failed.yml (when invoked with --env dev) |
If you have a dev tenant workspace, scope dev-targeted runs here. Skip if you don't. |
These are recommendations, not requirements:
| Setting | production |
integration |
automation |
|---|---|---|---|
| Required reviewers | At least 1 (gates manual workflow_dispatch of deploy.yml) |
0 (PR-time smoke test, automated) | 0 (cron, read-only) |
| Wait timer | 0 | 0 | 0 |
| Deployment branch policy | Only main |
main + any branch (PRs need it too) |
Only main |
The OIDC token exchange that lets GitHub Actions auth as your App Reg requires a matching federated credential on the App Reg. One federated credential per environment.
In the Azure portal: Entra ID → App registrations → your App Reg → Certificates & secrets → Federated credentials → Add credential.
For each environment from §3, add one credential with these subject formats:
| Environment | Federated credential subject |
|---|---|
production |
repo:<org>/<repo>:environment:production |
integration |
repo:<org>/<repo>:environment:integration |
automation |
repo:<org>/<repo>:environment:automation |
dev |
repo:<org>/<repo>:environment:dev |
Other fields:
- Issuer:
https://token.actions.githubusercontent.com - Audience:
api://AzureADTokenExchange(the GitHub default) - Name: free-text; use something readable like
github-<repo>-environment-<env>.
The three names must agree: the GitHub Environment, the
environment: field in the workflow yaml, and the
environment:<name> segment of the federated credential subject.
If any differ by case or spelling, OIDC token exchange fails with
AADSTS700213.
Settings → Branches → Branch protection rules → Add rule (name
pattern: main). Required status checks (these are GitHub Actions
job names; spelling matters):
dco— Developer Certificate of Originspdx-headers— Apache 2.0 license headersbandit— Python static analysis (security)semgrep— Python static analysis (style + security)cli-smoke— CLI imports + smoke testspytest— full unit suitegitleaks— secret scanactionlint— workflow YAML lintcoverage— MITRE coverage report (informational, not a true gate)production-promotion-check— gates human-authored promotions
Plus:
- Require linear history
- Do not allow force pushes
- Do not allow deletions
- Require signed commits (recommended)
- Require pull request before merging (1+ reviewer)
After §1–§4 are wired:
# Conformance L7 reads GitHub repo settings via gh CLI.
$env:GITHUB_REPOSITORY = "<org>/<repo>"
python -m contentops conformance --scope L7Expected: all four secrets/variables checks pass, all environments listed exist, all federated credentials present, branch protection required checks include the eight names above.
If L7 reports any missing pieces, the failure message points at the
exact setting + the gh command to fix it.
For the full picture (L1–L7), drop the --scope flag.
| Symptom | Root cause | Fix |
|---|---|---|
Error: tenant-config-yaml input is empty and config/tenant.yml is missing. |
TENANT_CONFIG_YAML secret unset or set on the wrong repo |
Get-Content config\tenant.yml -Raw | gh secret set TENANT_CONFIG_YAML --repo <org>/<repo> |
AADSTS700213 from azure/login step |
Federated credential subject doesn't match the workflow's environment: value |
Compare the GitHub Environment name, workflow yaml environment:, and federated credential subject — all three must match exactly. |
| Workflow run says success but skipped everything | Workspace not configured (e.g. no integration workspace in tenant.yml) |
This is graceful skip, not failure — see the workflow's step summary. |
gitleaks fails with "missing license" on org repo |
GITLEAKS_LICENSE not set |
Either set the secret once the license email arrives, OR remove secret-scan.yml if your org has GitHub Advanced Security covering the same ground. |
| Workflow can't see secret defined at environment level | The workflow doesn't declare environment: matching that env |
Either move the secret to repo-level (visible to all workflows), or add environment: <env> to the workflow's job-level config. |
authentication-setup.md— the Azure side: App Reg creation, Graph permissions, Sentinel RBAC.tenant-config-modes.md— different tenant-config sourcing modes (committed vs. secret vs. vars+split).deployment-conformance.md— what L1–L7 conformance checks actually verify.docs/quickstart.md— adopter onboarding flow with locked-down-Windows recipes.