PS HarriJaakkonen :~/Blog/Posts> cat ./devsecops-for-iac-trust-boundaries-part-1.html

Beyond Microsoft’s DevSecOps for IaC: Trust Boundaries for GitHub, Copilot and AI Agents

Secure Infrastructure as Code with GitHub, Azure, Copilot and AI agents

The delivery chain is the boundary

Microsoft recently published a new Azure Architecture Center solution idea for DevSecOps with Infrastructure as Code. It is a strong reference architecture because it connects the GitHub, pipeline and Azure stages and places security, policy, monitoring and drift reconciliation into the same lifecycle.

I agree with the direction. The question I would ask is how to preserve a trustworthy path from a proposed change to an approved Azure deployment when the change may be authored by a developer, GitHub Copilot, or a more autonomous agent.

The object we are protecting is no longer only the infrastructure definition. It is the delivery chain: repo, workflow, identity, approvals, policy, deployment path, and runtime enforcement.

Microsoft’s starting point

The reference architecture builds a reasonable lifecycle:

  1. Developer creates infrastructure as code
  2. GitHub stores the source of truth
  3. GitHub Actions validates and deploys
  4. Security and policy checks run before or during deployment
  5. Azure enforces control plane and runtime policy
  6. Detection and drift feed back into GitHub as issues or remediation

That is good architecture. The key point is that GitHub becomes the trusted source of infrastructure state, and manual drift becomes the exception to be reconciled back into code. I agree with that model. But there is another layer we should be more explicit about:

Trust boundary: Git can be the source of truth, but the controls around who or what is allowed to change that source of truth become more important as AI assistants and agents participate in the workflow.

Copilot should create code, not security decisions

Copilot is a natural fit for IaC. It can generate Bicep, Terraform, Azure Policy, GitHub Actions, infrastructure tests, docs, deployment scripts, and remediation changes. Microsoft’s architecture clearly includes GitHub Copilot in this part of the lifecycle.

That is useful, but it requires a clear distinction between proposed code and approved infrastructure. A better flow is:

Developer + Copilot Proposed change Pull request Approval gate policy review Human review + deploy
Copilot can propose the change, but the approval path remains the trust boundary.

Copilot can speed up the first phase. It should not replace the controls underneath it. This is especially important as GitHub moves from AI-assisted coding toward agentic workflows where repositories, workflows and tasks can be driven from natural-language prompts.

The more autonomous the environment becomes, the more deterministic the security boundaries around it should become.

GitHub Actions is part of the infrastructure supply chain

One thing people often miss is that the infrastructure code is not the only dependency in the deployment path. The workflow itself matters.

steps:
  - uses: actions/checkout@v4
  - uses: azure/login@v2
  - uses: azure/arm-deploy@v2
  - uses: microsoft/security-devops-action@v1

Every action and reusable workflow becomes part of the production delivery chain. An attacker does not necessarily need to compromise your Terraform or Bicep if they can compromise something a privileged deployment workflow executes.

GitHub recommends pinning actions to a full-length commit SHA. That gives you an immutable reference instead of a moving tag or branch. GitHub also supports governing this through enterprise policies and workflows. In security-sensitive deployments, this should be part of the default design.

Keep pinned actions current

Dependabot is usually thought of as a package manager tool, but it can also monitor GitHub Actions dependencies. That is useful when actions are pinned to commit SHAs and you still want a safe way to receive updates.

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

The sequence is straightforward: pinned workflow dependency, update PR, review, and security validation. It avoids the bad alternatives of never updating actions or blindly executing whatever the latest tag references.

Identity is the first control

Another control I would emphasize much more strongly is identity. A modern Azure deployment pipeline should not rely on a long-lived client secret stored as a GitHub secret.

GitHub Actions supports OIDC, and Microsoft Entra ID supports workload identity federation. In practical terms, that means an Azure federated credential can trust a GitHub repo, branch or environment, and issue short-lived Azure credentials to that workflow instead of storing a long-lived Azure secret in GitHub.

GitHub Actions OIDC token Microsoft Entra ID Trust bound to repo + env
Short-lived identity scoped to the repository, environment and workflow.

That is one of the most important controls in the entire design. It reduces the chance of a leaked credential becoming a permanent backdoor into the Azure tenant. OIDC is useful here because Microsoft Entra ID can be configured with a federated credential for the exact GitHub workflow trust boundary. That should be the default pattern for production deployments.

Be precise about OIDC trust

Simply saying “we use OIDC” is not enough. The important question is what exactly Microsoft Entra ID trusts. A production deployment identity should not be valid for any GitHub workflow in the organization. It should be constrained to a specific repository, workflow, branch or environment, and the associated approval or protection rules.

GitHub’s OIDC docs and Entra federated credential setup both point to the same model: the subject claim should be scoped tightly, and the Azure app registration or managed identity should only be used by the approved deployment path. The trust boundary should look closer to this:

Approved Workflow Protected Required Production identity
The production identity should only be valid when the repo, workflow, branch and approval conditions all line up.

That is much stronger than a generic trust relationship that any workflow in the organization can use. It gives GitHub Actions a defined workload identity in Azure rather than an unscoped token.

Least privilege on the token and the workflow

Azure identity is only half the story. Every GitHub Actions job also receives a GitHub token and that token should be scoped as tightly as possible.

permissions:
  contents: read
  id-token: write

Then add more only where it is needed. A security workflow may need additional permissions for code scanning or security events, but a deployment workflow should not automatically inherit more repository access than it uses.

The principle is simple: treat workflow permissions like Azure RBAC. Review them, scope them, and remove what is not required.

GitHub environments should represent deployment trust boundaries

I would also make GitHub Environments a core design element. A production environment should be a label. It should enforce required reviewers, protected branches, environment-specific identity, and deployment protection rules.

Merge to main Security checks Environment approval Production deployment identity
Environments should represent a trust boundary, not a deployment label.

Notice the order here. The approval and environment boundary happen before the production identity becomes useful. That is a much stronger security model than embedding approval logic inside a shell script.

Reusable workflows give teams a controlled path

Microsoft’s architecture already talks about platform engineering teams providing reusable GitHub Actions workflows and approved deployment patterns. I would make that a major design principle.

Instead of letting every application team create its own deploy-prod.yaml file, central platform teams can provide standardized building blocks. Examples include:

  • iac-validation.yml
  • iac-security.yml
  • azure-plan.yml
  • azure-deploy.yml
  • production-deployment.yml

GitHub allows this through reusable workflows. That gives the platform team a strong opportunity to own the policy, identity, testing and deployment pattern while application teams focus on the infrastructure they need.

This is where reusable workflows reduce variation. Security teams should not review 150 slightly different YAML deployment pipelines. They should provide a single approved path, backed by the correct Entra federated credential and Azure RBAC model, that many repositories can consume.

The boundary I would enforce

Microsoft’s reference architecture gives a good foundation, but it is still mostly a lifecycle view. The gap I want to close is the trust boundary around who is allowed to change infrastructure and how that change is validated and authorized before Azure sees it.

For GitHub, Copilot and AI agent workflows, I would enforce this sequence:

  • Copilot proposes code
  • policy and security checks validate it
  • identity and environment policies authorize the deployment
  • human approval remains in place for production-impacting changes
Control rule: Copilot proposes the change; policy, identity and human approval decide whether it can affect production.