Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
persist-credentials: false

- name: Validate
shell: pwsh
run: ./scripts/validate.ps1

11 changes: 8 additions & 3 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@

## v2 Requirements

- [x] **INT-01**: Platform injects the reference product non-secret configuration contract.
- [x] **INT-02**: Platform configures reference product liveness and readiness probes.
- [x] **INT-03**: Foundry RBAC is optional and requires explicit project scope and role definition inputs.
- [x] **INT-04**: Contract tests prove workload identity, configuration, probes, ingress, and RBAC boundaries.
- **NET-01**: Operator can enable private networking and controlled egress.
- **GOV-01**: Operator can apply Azure Policy and compliance reporting.
- **DEL-01**: Authorized workload identity can promote reviewed infrastructure.
Expand All @@ -57,9 +61,10 @@
| SEC-01, SEC-02, SEC-03, SEC-04 | Phase 1 | Complete |
| OPS-01, OPS-02, OPS-03, OPS-04 | Phase 1 | Complete |
| QUAL-01, QUAL-02, QUAL-03, QUAL-04 | Phase 1 | Complete |
| INT-01, INT-02, INT-03, INT-04 | Phase 2 | Complete |
| NET-01, GOV-01 | Phase 3 | Pending landing-zone contract |

**Coverage:** 16 v1 requirements, 16 mapped, 0 unmapped.
**Coverage:** 16 v1 requirements and 4 integration requirements complete.

---
*Last updated: 2026-06-11 after v0.1 foundation*

*Last updated: 2026-06-12 after reference product integration*
15 changes: 10 additions & 5 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@ through OPS-04, QUAL-01 through QUAL-04.
3. Architecture and threat model accurately describe residual risk.
4. Repository can be created publicly and CI can validate the foundation.

## Phase 2: Network and Policy Guardrails
## Phase 2: Reference Product Integration

Implement the public reference product deployment contract: non-secret
configuration, health probes, system-assigned identity boundaries, and
optional project-scoped Foundry RBAC.

## Phase 3: Network and Policy Guardrails

Add private networking, controlled egress, Azure Policy, and compliance
reporting after target subscription architecture is agreed.
reporting only after target subscription architecture is agreed.

## Phase 3: Federated Delivery
## Phase 4: Federated Delivery

Add OIDC/managed-identity deployment workflows, approvals, evidence retention,
and safe promotion between environments.

## Phase 4: Production Operations
## Phase 5: Production Operations

Add alerts, SLOs, dashboards, incident routing, release attestations, and
recovery exercises.

4 changes: 3 additions & 1 deletion .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
See `.planning/PROJECT.md`.

**Core value:** An operator can confidently understand and validate a secure CAS Azure environment before deploying it.
**Current focus:** Phase 2 - Network and Policy Guardrails
**Current focus:** Phase 3 - Network and Policy Guardrails

## Status

- Phase 1 v0.1 foundation implemented and locally verified.
- Phase 2 reference product integration implemented and locally verified.
- No Azure resources deployed.
- Private networking and policy deferred until a landing-zone contract exists.
- Next planned phase: Network and Policy Guardrails.
24 changes: 24 additions & 0 deletions .planning/phases/02-reference-product-integration/02-01-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Phase 2 Plan: Reference Product Integration

## Goal

Implement the public `cas-reference-product` deployment interface without
deploying Azure resources or assuming a landing-zone topology.

## Tasks

1. Inject the required non-secret workload configuration from Bicep.
2. Configure port 8080, internal ingress, system-assigned identity, and health
probes matching the public workload interface.
3. Add optional Foundry RBAC gated by explicit project resource and role
definition resource IDs, scoped only to that project.
4. Add contract tests and documentation covering the integration boundary.
5. Build all Bicep and parameter files, run Pester, and retain non-deploying
what-if behavior.

## Guardrails

- Do not deploy Azure resources.
- Do not add private networking or Azure Policy without a target landing-zone
contract.
- Do not select a broad built-in role on the operator's behalf.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Phase 2 Summary: Reference Product Integration

Implemented the reference product configuration and health contract in the
Container App module. Application Insights configuration flows directly from
the observability module. Foundry role assignment is disabled by default and
can only be enabled with both an explicit Foundry project resource ID and an
explicit approved role definition resource ID; its scope is the project.

Private networking and Azure Policy were reviewed but not added because no
landing-zone topology, DNS, egress, or policy ownership contract exists.

No Azure resources were deployed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Phase 2 Verification

**Status:** Passed locally

## Evidence

- `az bicep build --file infra/main.bicep --stdout`: passed.
- All dev, test, and prod Bicep parameter builds: passed.
- `scripts/validate.ps1`: passed.
- Pester infrastructure contract tests: 10 passed, 0 failed.
- `scripts/what-if.ps1 -Environment dev -Location northeurope`: succeeded
with only expected creates and no deployment. The preview proved internal
ingress, port 8080, system-assigned identity, required environment settings,
both health probes, and no default Foundry role assignment.
- Tests prove required environment injection, probes, port 8080, internal
ingress default, system-assigned identity, principal output, optional RBAC
gating, project scope, and non-deploying what-if commands.
- No Azure resources deployed.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Production-oriented Azure infrastructure foundation for the Coding Autopilot
System (CAS). It provides environment-isolated Container Apps hosting,
workspace-based observability, system-assigned managed identity, budgets, and
safe validation tooling without storing secrets.
safe validation tooling without storing secrets. The workload module implements
the public `cas-reference-product` deployment interface.

## v0.1 foundation

Expand All @@ -13,6 +14,8 @@ safe validation tooling without storing secrets.
- Log Analytics, Application Insights, diagnostic settings, tags, and budgets.
- Dev, test, and production parameter sets.
- Local and CI validation plus a non-deploying Azure `what-if` script.
- Reference-product configuration injection and liveness/readiness probes.
- Optional Foundry project RBAC requiring an explicit project scope and role.

## Validate locally

Expand Down Expand Up @@ -40,6 +43,18 @@ az login
The script only invokes `az deployment sub what-if`. It never invokes a create
or deploy command.

## Reference product configuration

The Container App injects `ENVIRONMENT`, `WORKFLOW_BACKEND`,
`FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_AGENT_NAME`, and
`APPLICATIONINSIGHTS_CONNECTION_STRING`. Local mode is the safe default.
Foundry mode requires a project endpoint and Next Gen agent name.

Foundry RBAC is disabled unless both `foundryProjectResourceId` and
`foundryRoleDefinitionResourceId` are explicitly supplied. The assignment is
created only at that Foundry project resource. Select and approve the minimum
role externally; the template does not assume a broad built-in role.

## Architecture

See [architecture](docs/architecture.md), [threat model](docs/threat-model.md),
Expand All @@ -48,6 +63,7 @@ kept under `.planning/`.

## Security

Public ingress is disabled by default. No secrets, credentials, connection
strings, or access keys are accepted by the templates. Runtime access to future
dependencies must use managed identity with narrowly scoped RBAC.
Public ingress is disabled by default. No secrets, credentials, or access keys
are accepted by the templates. Runtime access to dependencies uses managed
identity with narrowly scoped RBAC. Private networking and Azure Policy remain
deferred until a target landing-zone contract defines topology and ownership.
21 changes: 15 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,24 @@ environment, identity boundary, and budget.

## Identity and networking

The Container App receives a system-assigned managed identity. v0.1 has no
dependent data plane resources, so it deliberately creates no role
assignments. Future modules must grant the identity only the minimum data-plane
roles at the narrowest resource scope.
The Container App receives a system-assigned managed identity. Foundry access
is optional and creates no role assignment by default. When both an explicit
Foundry project resource ID and an approved role definition resource ID are
provided, the platform assigns that role only at the project resource scope.
Subscription-wide workload roles are not supported.

External ingress defaults to disabled. Enabling it is an explicit parameter
decision and requires a threat-model review. The v0.1 baseline does not claim
private networking; that is planned as a later hardened topology.
private networking. That topology remains deferred until a target landing-zone
contract identifies required subnets, DNS, firewall, and egress ownership.

## Reference product contract

The workload module follows the public `cas-reference-product` deployment
interface: Linux container image, port 8080, internal ingress by default,
system-assigned identity, `/health/live` and `/health/ready` probes, and
non-secret workload configuration. Application Insights configuration is
injected directly from the observability module.

## Observability

Expand All @@ -50,4 +60,3 @@ are output.
Normal changes flow through build, lint, contract tests, and subscription
`what-if`. Resource naming is deterministic. Renaming workload, environment, or
location can replace resource boundaries and must be treated as a migration.

12 changes: 11 additions & 1 deletion docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ sets. Production deployment is intentionally not implemented in v0.1. Add it
only after workload identity, approvals, policy, and rollback ownership are
defined.

## Reference product modes

Parameter files default to `WORKFLOW_BACKEND=local`. To validate Foundry mode,
provide the non-secret project endpoint and Next Gen agent name. RBAC remains
disabled unless the operator also supplies both the exact Foundry project
resource ID and an approved role definition resource ID. Review the resulting
project-scoped role assignment in what-if before any deployment authorization.

Health probes call `/health/live` and `/health/ready` on port 8080. Readiness
checks configuration only; it does not invoke Foundry.

## Rollback

Bicep redeployment can restore configuration but does not restore deleted data
Expand All @@ -26,4 +37,3 @@ Never rely on changing a resource name as a rollback mechanism.
Query the environment Log Analytics workspace for platform and application
logs. Application Insights is available for workload instrumentation when the
application is configured without exposing credentials.

5 changes: 2 additions & 3 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
| Threat | v0.1 control | Residual risk |
|---|---|---|
| Credential disclosure | Templates accept no secrets; managed identity only | Operator and CI Azure identities remain external controls |
| Excessive runtime privilege | No role assignments until a dependency requires one | Future modules could grant overly broad roles |
| Excessive runtime privilege | Optional Foundry assignment requires explicit role and project scope inputs | Operator could still select an overly broad role definition |
| Accidental production change | Environment isolation and what-if-only validation script | A separate deployment workflow could bypass review |
| Unreviewed public exposure | External ingress disabled by default | Enabling ingress does not yet add WAF/private networking |
| Telemetry loss | Diagnostic settings route logs and metrics to Log Analytics | Alert rules and SLOs are deferred |
Expand All @@ -30,9 +30,8 @@

## Security decisions required before production

- Adopt private networking and controlled egress where workload needs justify it.
- Adopt private networking and controlled egress after landing-zone topology and ownership are defined.
- Define a federated, least-privilege deployment identity and approval workflow.
- Add Azure Policy assignments for allowed regions, required tags, and exposure.
- Add workload-specific RBAC only after data-plane dependencies are known.
- Define alerts, incident routing, retention, and evidence access controls.

35 changes: 34 additions & 1 deletion infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ param dataClassification string = 'internal'
@description('Container image for the foundation workload.')
param containerImage string

@description('Workflow backend selected by the reference product.')
@allowed([
'local'
'foundry'
])
param workflowBackend string = 'local'

@description('Foundry project endpoint. Required by the application when workflowBackend is foundry.')
param foundryProjectEndpoint string = ''

@description('Foundry Next Gen agent name. Required by the application when workflowBackend is foundry.')
param foundryAgentName string = ''
Comment on lines +41 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject incomplete Foundry backend configuration

When an operator selects workflowBackend = 'foundry' but leaves either Foundry endpoint or agent name empty, the template still validates and deploys because both required application inputs default to empty strings without any cross-parameter assertion. The resulting Container App receives an invalid Foundry configuration and, according to the documented readiness contract, cannot become ready; reject this combination before deployment rather than allowing an unusable revision.

Useful? React with 👍 / 👎.


@description('Optional explicit Foundry project resource identifier used as the narrow RBAC scope.')
param foundryProjectResourceId string = ''

@description('Optional explicit role definition resource identifier approved for the workload at the Foundry project scope.')
param foundryRoleDefinitionResourceId string = ''

@description('Whether the Container App accepts public network ingress.')
param enableExternalIngress bool = false

Expand All @@ -48,6 +67,7 @@ param monthlyBudget int = 0
param budgetContactEmails array = []

var suffix = take(uniqueString(subscription().subscriptionId, workloadName, environment, location), 6)
var enableFoundryRbac = !empty(foundryProjectResourceId) && !empty(foundryRoleDefinitionResourceId)
var resourceGroupName = 'rg-${workloadName}-${environment}-${suffix}'
var tags = {
application: workloadName
Expand Down Expand Up @@ -87,12 +107,26 @@ module compute './modules/container-apps.bicep' = {
location: location
suffix: suffix
containerImage: containerImage
workflowBackend: workflowBackend
foundryProjectEndpoint: foundryProjectEndpoint
foundryAgentName: foundryAgentName
applicationInsightsConnectionString: observability.outputs.applicationInsightsConnectionString
enableExternalIngress: enableExternalIngress
logAnalyticsWorkspaceId: observability.outputs.logAnalyticsWorkspaceId
tags: tags
}
}

module foundryRbac './modules/foundry-rbac.bicep' = if (enableFoundryRbac) {
name: 'foundry-rbac-${environment}'
scope: resourceGroup(split(foundryProjectResourceId, '/')[2], split(foundryProjectResourceId, '/')[4])
params: {
workloadPrincipalId: compute.outputs.workloadPrincipalId
foundryProjectResourceId: foundryProjectResourceId
foundryRoleDefinitionResourceId: foundryRoleDefinitionResourceId
}
}

module budget './modules/budget.bicep' = if (monthlyBudget > 0 && length(budgetContactEmails) > 0) {
name: 'budget-${environment}'
scope: environmentResourceGroup
Expand All @@ -117,4 +151,3 @@ output logAnalyticsWorkspaceId string = observability.outputs.logAnalyticsWorksp

@description('Application Insights resource identifier.')
output applicationInsightsId string = observability.outputs.applicationInsightsId

Loading
Loading