Skip to main content

citadel-spoke-onboarding

Onboard a Foundry project as a spoke into an AI Citadel Governance Hub. Covers Access Contracts, APIM connections, product policies, JWT auth. USE FOR: citadel spoke onboarding, access contract, connect foundry to citadel, APIM connection, bring your own ai gateway, govern agent, citadel JWT auth, citadel product policy, unified ai api, citadel AGT, vnet-isolated citadel spoke, foundry-vnet-deploy spoke onboarding. DO NOT USE FOR: deploying the Citadel hub itself, APIM infrastructure, hub networking, hub provisioning, hub sizing, llm backend onboarding, deploying model backends, apim backend pools, hub policy fragment deployment, spoke-side VNet/peering creation (use foundry-vnet-deploy).

Jump to install

Source facts

Repository
aiappsgbb/awesome-gbb
Last source activity
September 17, 2026 at 09:59
Detected SKILL.md language
English
Stars
6
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
5 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
citadel-spoke-onboarding
description
Onboard a Foundry project as a spoke into an AI Citadel Governance Hub. Covers Access Contracts, APIM connections, product policies, JWT auth. USE FOR: citadel spoke onboarding, access contract, connect foundry to citadel, APIM connection, bring your own ai gateway, govern agent, citadel JWT auth, citadel product policy, unified ai api, citadel AGT, vnet-isolated citadel spoke, foundry-vnet-deploy spoke onboarding. DO NOT USE FOR: deploying the Citadel hub itself, APIM infrastructure, hub networking, hub provisioning, hub sizing, llm backend onboarding, deploying model backends, apim backend pools, hub policy fragment deployment, spoke-side VNet/peering creation (use foundry-vnet-deploy).
metadata
{"version":"2.0.0"}
# Citadel Spoke Onboarding — Reference Guide How to connect a GenAI application or Microsoft Foundry project to an **existing** AI Citadel Governance Hub. Controls cover the traffic actually routed through the gateway, not every agent action or a compliance certification. > **Threadlight integration**: This skill is the **opt-in Phase 7** of > `threadlight-deploy`. It runs ONLY when SPEC § 11b sets > `governance_hub.required: yes` (the SPEC field is generic; the AI > Citadel hub is one reference implementation). The base agent deploy > (Phase 5 + 6) lands in the customer's tenant first; this skill > onboards it as a hub spoke afterwards as an additive step. Read > SPEC § 11b for the per-process governance posture (hub endpoint, > access contracts, JWT requirements, secret wiring). > > **Threadlight pilots MUST use Option B (Foundry Connection)** — see > `Consuming the Gateway from Your App` below. Option A (Key Vault > secret pull) violates the keyless-by-mandate posture: it requires > the agent to hold an APIM subscription key and read it from KV at > runtime. Option B threads the call through a Foundry APIM connection > so the agent does not retrieve the subscription key. The pinned connection > uses `authType: ApiKey`; this is **not end-to-end keyless**. Caller-to-Foundry > Entra authentication, connection-managed secret custody and downstream APIM > authentication are separate boundaries. JWT requires its own approved setup > and positive/negative runtime evidence; no MI-to-APIM JWT flow is implied. > If a customer insists on > Option A for a non-threadlight reason, document the deviation in > SPEC § 11b explicitly. > **Source:** [exact hub/spoke revision](https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator/tree/63f0f812474e713916dc909494d655246783a1d9). > The [pin record](references/upstream-pin.md) separates source comparison from > live compatibility. `citadel-v1` is a freshness signal, not a deployment input. > **Quick link:** <https://aka.ms/ai-hub-gateway> --- ## Key Concepts | Term | Meaning | |------|---------| | **Citadel Governance Hub** | Central control plane with Azure API Management (APIM) acting as the unified AI gateway. Already deployed — not your concern here. | | **Spoke** | An isolated workload environment (Foundry project, Container App, Function, etc.) that consumes AI services **through** the hub gateway. | | **Access Contract** | A Bicep parameter file (`.bicepparam`) + optional policy XML declaring what AI services a spoke needs, with what policies. Deployed as IaC. | | **Foundry Connection** | An APIM-type connection inside an Azure AI Foundry project that routes model calls through the Citadel gateway. | | **Service Code** | Short acronym mapping a category of AI services to APIM API IDs (e.g. `LLM`, `DOC`, `SRCH`, `OAIRT`). | --- ## What Gets Created Per Access Contract | Resource | Naming Pattern | Description | |----------|----------------|-------------| | **APIM Product** | `{code}-{BU}-{UseCase}-{ENV}` | One per service code, with attached APIs and policies | | **APIM Subscription** | `{product}-SUB-01` | Subscription with API key | | **Key Vault Secrets** (optional) | `{secretName}` | Endpoint URL + API key stored in KV | | **Foundry Connection** (optional) | `{prefix}-{code}` | APIM connection for Foundry agents | --- ## Prerequisites (Spoke Side) | Requirement | Details | |-------------|---------| | Running Citadel Hub | APIM deployed with published APIs matching your `apiNameMapping` | | Azure CLI + Bicep | Latest version with `az deployment sub create` support | | Permissions | `API Management Service Contributor` on APIM RG, `Key Vault Secrets Officer` on target KV (if used), `Contributor` on Foundry RG (if using Foundry connections) | | Foundry Project | Must exist if you want APIM connections inside Foundry | Before any operator Azure call, apply [`azure-tenant-isolation`](../azure-tenant-isolation/SKILL.md): paired isolated CLI/AZD context and the approved exact tenant/subscription. Assert the target immediately before writes. Read-only inventory grants no repair authority. --- ## Step-by-Step: Create an Access Contract ### 1. Scaffold the Contract Folder Materialize the exact reviewed hub revision, then follow `contracts/<businessunit-usecasename>/<environment>/`. Do not overwrite an existing checkout, deployed contract or policy to align a pin. ```bash PINNED_SHA="63f0f812474e713916dc909494d655246783a1d9" git clone --filter=blob:none --no-checkout https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator.git cd ai-hub-gateway-solution-accelerator git fetch --depth 1 origin "$PINNED_SHA" git checkout --detach "$PINNED_SHA" test "$(git rev-parse HEAD)" = "$PINNED_SHA" cd bicep/infra/citadel-access-contracts # Create contract folder mkdir -p contracts/myteam-myagent/dev cd contracts/myteam-myagent/dev # Copy templates cp ../../../main.bicepparam main.bicepparam cp ../../../policies/default-ai-product-policy.xml ai-product-policy.xml ``` > 📂 Full contract folder structure and module reference: > [citadel-access-contracts/](https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator/tree/citadel-v1/bicep/infra/citadel-access-contracts) > > ⚠️ Sample contracts were removed from the repo. Use `main.bicepparam` as your template base. > Review `allowedModels` explicitly: this revision's default LLM policy allows > `gpt-4.1,gpt-5.4-mini`, unlike the older policy's broader list. Existing > approved policies are not replaced automatically. Leave optional key rotation > and additional-gateway settings disabled unless separately approved. ### 2. Configure the Parameter File Edit `main.bicepparam`: ```bicep using '../../../main.bicep' // ── Hub coordinates (get these from your platform team) ── param apim = { subscriptionId: '<HUB-SUBSCRIPTION-ID>' resourceGroupName: '<HUB-APIM-RG>' name: '<HUB-APIM-NAME>' } // ── Secret storage ── param useTargetAzureKeyVault = true // false → credentials in deployment output param keyVault = { subscriptionId: '<SPOKE-SUBSCRIPTION-ID>' resourceGroupName: '<SPOKE-KV-RG>' name: '<SPOKE-KV-NAME>' } // ── Use-case identity ── param useCase = { businessUnit: 'MyTeam' useCaseName: 'MyAgent' environment: 'DEV' // DEV | TEST | PROD } // ── Map service codes → APIM API IDs ── // ⚠️ Order matters: endpoint secret stores the gateway URL for the FIRST API. // Put the API matching your SDK first (e.g. azure-openai-api for AzureOpenAI SDK). param apiNameMapping = { LLM: ['azure-openai-api', 'universal-llm-api', 'unified-ai-api'] } // ── Services to onboard ── param services = [ { code: 'LLM' endpointSecretName: 'MYAGENT-LLM-ENDPOINT' apiKeySecretName: 'MYAGENT-LLM-KEY' policyXml: loadTextContent('ai-product-policy.xml') // '' → use default } ] // ── Foundry integration (optional) ── param useTargetFoundry = true // false if not using Foundry agents param foundry = { subscriptionId: '<FOUNDRY-SUBSCRIPTION-ID>' resourceGroupName: '<FOUNDRY-RG>' accountName: '<FOUNDRY-ACCOUNT>' projectName: '<FOUNDRY-PROJECT>' } param foundryConfig = { connectionNamePrefix: '' // empty → auto from useCase naming deploymentInPath: 'false' // model name in request body isSharedToAll: false inferenceAPIVersion: '' // empty → APIM defaults deploymentAPIVersion: '' staticModels: [] listModelsEndpoint: '' getModelEndpoint: '' deploymentProvider: '' customHeaders: {} authConfig: {} } ``` ### 3. Customise the Product Policy (Optional) The [default policy](https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator/blob/citadel-v1/bicep/infra/citadel-access-contracts/policies/default-ai-product-policy.xml) includes model restrictions, token limits, and content safety. For custom policies, edit `ai-product-policy.xml`. Full policy reference: [citadel-access-contracts-policy.md](https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator/blob/citadel-v1/bicep/infra/citadel-access-contracts/citadel-access-contracts-policy.md) **Recommended policy ordering in `<inbound>`:** ```xml <inbound> <base /> <!-- 1. JWT Authentication (optional) --> <set-variable name="jwtRequired" value="true" /> <!-- 2. App Role Authorization (optional, requires JWT) --> <set-variable name="requiredRoles" value="Models.Read" /> <!-- 3. Model extraction and access control --> <include-fragment fragment-id="set-llm-requested-model" /> <set-variable name="allowedModels" value="gpt-5.4-mini,gpt-5.4-nano" /> <include-fragment fragment-id="validate-model-access" /> <!-- 4. Capacity management (subscription level) --> <!-- ⚠️ For Foundry agents with MCP tools, use ≥100K TPM. A single CI query with 10-20 tool calls consumes 50-80K tokens. 10K TPM causes server_error after the first tool call completes. --> <llm-token-limit counter-key="@(context.Subscription.Id)" tokens-per-minute="5000" estimate-prompt-tokens="false" tokens-consumed-header-name="consumed-tokens" remaining-tokens-header-name="remaining-tokens" token-quota="100000" token-quota-period="Monthly" retry-after-header-name="retry-after" /> <!-- 5. Usage attribution (optional) --> <set-variable name="appId" value="@(context.Request.Headers.GetValueOrDefault("x-app-id", context.Subscription?.Id ?? "Portal-Admin"))" /> <set-variable name="customDimension1" value="@(context.Request.Headers.GetValueOrDefault("x-sub-agent-id", "general-agent"))" /> <set-variable name="customDimension2" value="@(context.Request.Headers.GetValueOrDefault("x-enduser-id", "anonymous-enduser"))" /> <!-- 6. PII Anonymization (optional) --> <set-variable name="piiAnonymizationEnabled" value="true" /> <!-- 7. Content Safety (optional) --> <llm-content-safety backend-id="content-safety-backend" shield-prompt="true"> <categories output-type="EightSeverityLevels"> <category name="Hate" threshold="3" /> <category name="Violence" threshold="3" /> </categories> </llm-content-safety> <!-- 8. Response debug headers (dev/test only) --> <set-variable name="enableResponseHeaders" value="@(true)" /> </inbound> ``` **Per-model capacity limits** (instead of flat subscription-level): ```xml <include-fragment fragment-id="set-llm-requested-model" /> <choose> <when condition="@((string)context.Variables["requestedModel"] == "gpt-5.4-mini")"> <llm-token-limit counter-key="@(context.Subscription.Id + "-gpt-5.4-mini")" tokens-per-minute="10000" token-quota="100000" token-quota-period="Monthly" estimate-prompt-tokens="false" /> </when> <when condition="@((string)context.Variables["requestedModel"] == "DeepSeek-R1")"> <llm-token-limit counter-key="@(context.Subscription.Id + "-DeepSeek-R1")" tokens-per-minute="2000" token-quota="10000" token-quota-period="Weekly" estimate-prompt-tokens="false" /> </when> <otherwise> <llm-token-limit counter-key="@(context.Subscription.Id + "-default")" tokens-per-minute="1000" token-quota="5000" token-quota-period="Monthly" estimate-prompt-tokens="false" /> </otherwise> </choose> ``` **Throttling alerts** (in `<on-error>` section): ```xml <on-error> <base /> <set-variable name="productName" value="@(context.Product?.Name?.ToString() ?? "Portal-Admin")" /> <set-variable name="deploymentName" value="@((string)context.Variables.GetValueOrDefault<string>("requestedModel", "DefaultModel"))" /> <set-variable name="appId" value="@((string)context.Variables.GetValueOrDefault<string>("appId", context.Subscription?.Id ?? "Portal-Admin-Sub"))" /> <include-fragment fragment-id="raise-throttling-events" /> </on-error> ``` ### 4. Validate and Deploy ```powershell # Preview (what-if) az deployment sub what-if ` --location <REGION> ` --template-file ../../../main.bicep ` --parameters main.bicepparam # Deploy az deployment sub create `
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub