diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..74fe611 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto +*.sh text eol=lf +*.ps1 text eol=lf +*.bicep text eol=lf +*.json text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aee225a..b75645b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,32 +3,47 @@ name: CI on: pull_request: push: - branches: [ main ] + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Markdown lint - uses: avto-dev/markdown-lint@v1 + uses: avto-dev/markdown-lint@04687db2e9b72c18a4dfce687923a8daa3e4b543 # v1 with: args: 'docs/**/*.md README.md CONTRIBUTING.md SECURITY.md' - name: Link check - uses: lycheeverse/lychee-action@v1 + uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # v1 with: args: '--verbose --no-progress docs/**/*.md README.md' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Verify Mermaid blocks - run: | - grep -rl '```mermaid' docs README.md + run: grep -rl '```mermaid' docs README.md - name: Validate JSON formatting run: | - find impl -name '*.json' 2>/dev/null | while read f; do - jq empty "$f" - done + find impl -name '*.json' -print0 | + xargs -0 -n1 jq empty + + - name: Install Bicep CLI + run: az bicep install + + - name: Validate repository contracts + run: bash scripts/validate-repository.sh diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index f2c9e97..6e0792b 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -1,43 +1,38 @@ -# Simple workflow for deploying static content to GitHub Pages +# Deploy the public documentation surface only. name: Deploy static content to Pages on: - # Runs on pushes targeting the default branch push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab + branches: [main] workflow_dispatch: -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: - group: "pages" + group: pages cancel-in-progress: false jobs: - # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + - name: Upload documentation artifact + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: - # Upload entire repository - path: '.' + path: docs - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.planning/audits/20260611-enterprise-audit-fix-continuation.md b/.planning/audits/20260611-enterprise-audit-fix-continuation.md new file mode 100644 index 0000000..e0a18cb --- /dev/null +++ b/.planning/audits/20260611-enterprise-audit-fix-continuation.md @@ -0,0 +1,30 @@ +# Enterprise Audit-Fix Continuation + +**Date:** 2026-06-11 +**Source:** `gsd-debug` followed by `gsd-audit-fix --severity all` +**Branch:** `hardening/enterprise-audit-20260611` + +## Outcome + +The interrupted audit was resumed after scientifically reproducing and resolving the Azure Arc Bash CRLF failure. +All remaining defensible auto-fixable findings were processed sequentially and committed atomically. No validation +failure occurred, so the pipeline completed F-02 through F-08. + +## Atomic Results + +| ID | Finding | Commit | +| --- | --- | --- | +| F-02 | Enforce repository contracts and resolve Arc Bash CRLF failure | `062dfff` | +| F-03 | Replace Bicep stubs with a secure compilable reference baseline | `4c583c6` | +| F-04 | Replace fictional policy references and define safe rollout defaults | `8ef9375` | +| F-05 | Define fail-closed Azure Arc dry-run onboarding contracts | `64a4f1a` | +| F-06 | Connect assets, trust boundaries, threats, controls, and residual risk | `073fa1d` | +| F-07 | Define evidence ownership, integrity, collection, and failure handling | `03e79e2` | +| F-08 | Add bounded Sentinel KQL references and tuning metadata | `76076e6` | + +## Remaining Manual Work + +- Select and approve production identity, tenant hierarchy, and deployment identities. +- Validate controls against a representative live Azure and Azure Local estate. +- Obtain independent compliance and legal review before assurance claims. +- Choose the production SIEM automation approval model and incident containment authority. \ No newline at end of file diff --git a/.planning/debug/arc-onboard-crlf-bash-syntax.md b/.planning/debug/arc-onboard-crlf-bash-syntax.md new file mode 100644 index 0000000..159d149 --- /dev/null +++ b/.planning/debug/arc-onboard-crlf-bash-syntax.md @@ -0,0 +1,38 @@ +--- +status: resolved +trigger: arc-onboard.sh CRLF/bash syntax failure +created: 2026-06-11 +updated: 2026-06-11 +--- + +# Symptoms + +- Expected: `bash -n impl/hybrid/azure-arc/onboarding/arc-onboard.sh` exits successfully. +- Actual: Bash reports a syntax error at the closing brace on line 20. +- Reproduction: Run the command from the repository root on Windows or Linux Bash. + +# Current Focus + +- hypothesis: CRLF line endings leave a carriage return attached to the closing brace. +- test: Add a repository validator that rejects CRLF in shell files and runs `bash -n`. +- expecting: The validator fails before normalization and passes after LF normalization. +- next_action: Complete repository-wide validation and commit the fix. + +# Evidence + +- timestamp: 2026-06-11 + observation: Format-Hex shows `0D 0A` line endings throughout arc-onboard.sh. +- timestamp: 2026-06-11 + observation: Bash reports `syntax error near unexpected token '}'` and displays a carriage return after the tee command. + +# Eliminated + +- hypothesis: The brace-and-pipe Bash syntax is invalid. + reason: The syntax is valid when line endings are LF. + +# Resolution + +- root_cause: CRLF line endings attached a carriage return to the Bash closing brace. +- fix: Enforced LF through .gitattributes and normalized arc-onboard.sh. +- verification: Repository validator rejects CRLF and bash -n now passes. +- files_changed: .gitattributes, scripts/validate-repository.sh, arc-onboard.sh diff --git a/.planning/phases/01-enterprise-audit/01-UAT.md b/.planning/phases/01-enterprise-audit/01-UAT.md new file mode 100644 index 0000000..2be3cd1 --- /dev/null +++ b/.planning/phases/01-enterprise-audit/01-UAT.md @@ -0,0 +1,33 @@ +# Enterprise Security Model Audit + +**Date:** 2026-06-11 +**Source:** `gsd-audit-fix --severity all --max 8` +**Scope:** Threat model, controls, Azure and hybrid design, Bicep, CI, evidence, and documentation + +## Classification + +| ID | Severity | Finding | Classification | Reason | +| --- | --- | --- | --- | --- | +| F-01 | High | CI uses mutable action tags and lacks explicit least-privilege defaults, timeouts, and concurrency controls. | Auto-fixable | Specific workflow files and objectively testable controls. | +| F-02 | High | CI validates formatting only; Bicep, onboarding scripts, Mermaid files, security examples, and repository contracts are not enforced. | Auto-fixable | A repository validator and CI invocation provide a bounded fix. | +| F-03 | High | Landing-zone Bicep modules return strings but deploy no resources or secure defaults. | Auto-fixable | Existing module boundaries support a compilable reference implementation. | +| F-04 | High | Policy-as-code examples reference fictional policy IDs and do not define rollout safety metadata. | Auto-fixable | Existing JSON examples can use documented built-in policy IDs and safe enforcement defaults. | +| F-05 | Medium | Azure Arc onboarding examples accept unvalidated input, write ambiguous logs, and lack an explicit dry-run contract. | Auto-fixable | Two bounded scripts have clear safety behavior and syntax validation. | +| F-06 | High | The service model has no formal threat model connecting assets, trust boundaries, threats, controls, and residual risk. | Auto-fixable | A public-safe architecture threat model can be added and cross-linked. | +| F-07 | High | Audit evidence guidance does not define ownership, integrity, access, collection automation, or evidence failure handling. | Auto-fixable | The evidence contract can be made explicit in the existing audit document. | +| F-08 | Medium | Sentinel analytic examples contain TODO queries, so detection claims are not testable or operationally credible. | Auto-fixable | Existing examples can contain safe, bounded KQL and tuning metadata. | + +## Manual-only Findings + +| ID | Severity | Finding | Reason | +| --- | --- | --- | --- | +| M-01 | High | Select the production identity, tenant hierarchy, and least-privilege deployment identities. | Requires organization-specific identity and management-group decisions. | +| M-02 | High | Validate controls against a live Azure and Azure Local estate. | Requires tenant access, representative workloads, and approved test windows. | +| M-03 | High | Obtain independent compliance and legal review before claiming certification or regulatory sufficiency. | Requires qualified external assurance and organization-specific obligations. | +| M-04 | Medium | Choose the production SIEM automation approval model and incident containment authority. | Requires risk appetite and operating-model decisions. | + +## Stop Conditions + +- Stop after the first failed validation and record unattempted findings. +- Do not deploy resources or mutate a live Azure tenant. +- Do not claim certification, compliance, or production readiness from reference artifacts alone. diff --git a/.planning/phases/01-enterprise-audit/01-VERIFICATION.md b/.planning/phases/01-enterprise-audit/01-VERIFICATION.md new file mode 100644 index 0000000..23c72c2 --- /dev/null +++ b/.planning/phases/01-enterprise-audit/01-VERIFICATION.md @@ -0,0 +1,48 @@ +# Enterprise Audit Verification + +**Date:** 2026-06-11 +**Branch:** `hardening/enterprise-audit-20260611` +**Pipeline status:** Complete for all auto-fixable findings + +## Result + +| ID | Finding | Status | Commit | +| --- | --- | --- | --- | +| F-01 | Harden workflow trust boundaries | Fixed and validated | `40c053a` | +| F-02 | Enforce repository contracts in CI | Fixed and validated | `062dfff` | +| F-03 | Deployable secure Bicep reference | Fixed and validated | `4c583c6` | +| F-04 | Credible policy-as-code examples | Fixed and validated | `8ef9375` | +| F-05 | Safe Azure Arc onboarding contract | Fixed and validated | `64a4f1a` | +| F-06 | Formal threat model | Fixed and validated | `073fa1d` | +| F-07 | Evidence integrity contract | Fixed and validated | `03e79e2` | +| F-08 | Testable Sentinel detections | Fixed and validated | `76076e6` | + +## CRLF Debug Resolution + +`bash -n impl/hybrid/azure-arc/onboarding/arc-onboard.sh` failed because the committed CRLF line endings attached a +carriage return to the closing brace. The repository now enforces LF for shell files, validates line endings and Bash +syntax, and records the resolved GSD debug session in `.planning/debug/arc-onboard-crlf-bash-syntax.md`. + +## Validation + +- Repository contract validator passed. +- Bash syntax and Azure Arc dry-run behavior passed. +- PowerShell onboarding script parsed successfully. +- Landing-zone Bicep compiled successfully. +- JSON policy and Sentinel examples parsed successfully. +- Workflow YAML parsed successfully. +- Markdown documentation checks passed. +- Third-party actions are pinned to immutable SHAs. +- `git diff --check` passed. + +## Manual-only Findings + +- Select production identity, tenant hierarchy, and deployment identities. +- Validate controls against a representative live Azure and Azure Local estate. +- Obtain independent compliance and legal review before making assurance claims. +- Choose production SIEM automation approval and containment authority. + +## Assurance Boundary + +No resources were deployed and no live Azure tenant was mutated. Reference artifacts require tenant-specific design, +approvals, testing, and independent assurance before production use. \ No newline at end of file diff --git a/README.md b/README.md index 43d99a3..6a07fe0 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ pretending to be those services. - Runbooks: [`docs/20-runbooks/README.md`](docs/20-runbooks/README.md) - Templates: [`docs/21-templates/README.md`](docs/21-templates/README.md) - Diagrams: [`docs/22-diagrams/README.md`](docs/22-diagrams/README.md) +- Threat model: [`docs/23-threat-model.md`](docs/23-threat-model.md) - Static site: [`docs/index.html`](docs/index.html) - [Wiki](https://github.com/Coding-Autopilot-System/cloud-security-service-model/wiki) - overview, service definition, architecture, metrics & compliance diff --git a/docs/04-reference-architecture.md b/docs/04-reference-architecture.md index 7a243f9..6fa428e 100644 --- a/docs/04-reference-architecture.md +++ b/docs/04-reference-architecture.md @@ -35,3 +35,4 @@ Shared responsibility overview: [`22-diagrams/shared-responsibility.mmd`](22-dia ## Related docs - Architecture principles: [`03-architecture-principles.md`](03-architecture-principles.md) - Hybrid/Azure Local: [`18-hybrid-azure-local.md`](18-hybrid-azure-local.md) +- Threat model: [`23-threat-model.md`](23-threat-model.md) diff --git a/docs/09-risk-management.md b/docs/09-risk-management.md index 711b34c..7947f2d 100644 --- a/docs/09-risk-management.md +++ b/docs/09-risk-management.md @@ -25,3 +25,4 @@ ## Related docs - Audit readiness: [`10-audit-readiness.md`](10-audit-readiness.md) - Exception handling: [`13-exception-handling.md`](13-exception-handling.md) +- Threat model: [`23-threat-model.md`](23-threat-model.md) diff --git a/docs/10-audit-readiness.md b/docs/10-audit-readiness.md index 6c41f0e..018b086 100644 --- a/docs/10-audit-readiness.md +++ b/docs/10-audit-readiness.md @@ -1,30 +1,82 @@ # Audit Readiness +## Assurance boundary + +This repository defines an evidence operating contract and reference controls. It does not prove certification, +regulatory compliance, or the effectiveness of a production tenant. Evidence must be validated against the applicable +scope, obligations, and approved assurance process. + +## Evidence contract + +Every evidence item must contain: + +- Control and requirement identifiers, scope, environment, and evidence owner +- Source system, collection method, collection identity, and collection timestamp +- Correlation values such as change ID, run ID, commit SHA, deployment ID, or incident ID +- Retention class, access classification, integrity method, and review status +- Collection result, validation result, exceptions, and remediation or escalation reference + +Evidence owners are accountable for collection health, access reviews, validation, retention, and timely remediation. +Control owners are accountable for deciding whether the evidence demonstrates the intended control outcome. + ## Evidence model -| Control area | Evidence | Source | Retention | -| --- | --- | --- | --- | -| Access control | PIM activation logs, role review reports | IAM/PIM | 1 year | -| Logging | SIEM ingestion reports, log retention policy | SIEM, Log Analytics | 1 year | -| Incident response | Incident records, postmortems | ITSM/SecOps | 2 years | -| Change management | Change tickets, approvals | ITSM | 2 years | -| Policy compliance | Compliance reports, policy assignments | Policy dashboard | 1 year | + +| Control area | Evidence | Source | Owner | Collection | Retention | +| --- | --- | --- | --- | --- | --- | +| Access control | PIM activations and access reviews | Entra ID / PIM | Identity owner | Daily export and quarterly review | 1 year | +| Logging | Ingestion health, retention, and analytic results | SIEM / Log Analytics | SecOps owner | Continuous health plus monthly sample | 1 year | +| Incident response | Incident record, timeline, and postmortem | ITSM / SecOps | Incident manager | Event-driven export at closure | 2 years | +| Change management | Change, approval, commit, and deployment correlation | ITSM / GitHub / Azure | Platform owner | Per deployment | 2 years | +| Policy compliance | Assignment, exemption, and compliance snapshots | Azure Policy | Policy owner | Daily snapshot and monthly review | 1 year | + +## Integrity and storage requirements + +- Store evidence in a dedicated protected location separate from the producing workload. +- Use least-privilege managed identities for automated collection and read-only roles for reviewers. +- Enable immutable or write-once retention where obligations require it; document approved deletion and legal hold. +- Preserve source timestamps and correlation identifiers. Generate and verify hashes for exported evidence packages. +- Log evidence reads, writes, exports, retention changes, and access-policy changes. +- Encrypt evidence in transit and at rest; prohibit secrets, access tokens, and unnecessary personal data. + +## Automated collection and validation + +1. Collect through scheduled or event-driven jobs using managed identity and bounded scopes. +2. Validate schema, source, expected time window, completeness, hash, and correlation identifiers. +3. Record collection and validation outcomes in a separate health log. +4. Alert the evidence owner and control owner on missing, late, malformed, or integrity-failed evidence. +5. Sample evidence monthly and test restoration and access at least quarterly. + +## Evidence failure handling + +Evidence failure is a control-operating issue, not an administrative warning. + +- Stop assurance claims and release gates that depend on missing or integrity-failed evidence. +- Open an incident or control exception with severity based on the affected control and duration. +- Preserve the failed artifact and collection logs; do not overwrite or silently regenerate them. +- Restore collection through the approved runbook, document the gap, and assess whether retrospective evidence exists. +- Require control-owner approval before closing the failure and resuming assurance reporting. ## ISO 27001:2022 mapping (high level) + | ISO domain | Control intent | Implementation examples | | --- | --- | --- | | Access control | Ensure least privilege and privileged access management | PIM, break-glass monitoring | | Asset management | Maintain inventory and classification | Resource tagging, inventory reports | -| Logging & monitoring | Detect events and maintain evidence | Centralized logging, SIEM analytics | +| Logging and monitoring | Detect events and maintain evidence | Centralized logging, SIEM analytics | | Incident management | Timely response and recovery | IR playbooks, evidence capture | -| Change management | Controlled changes to security posture | Change request workflow, CAB-lite | +| Change management | Controlled changes to security posture | Change request workflow, approvals | | Risk management | Identify and treat security risks | Risk register, exception handling | ## Audit readiness checklist -- Evidence sources documented for each control intent. -- Retention periods meet regulatory obligations. -- Evidence is reviewable and immutable. -- Exception register is current and signed off. + +- Evidence contracts and owners are approved for each control intent. +- Collection identities and reviewer access are least privilege and reviewed. +- Retention, immutability, integrity verification, and legal hold meet applicable obligations. +- Collection health, restoration, and failure escalation are tested. +- Exceptions and residual risks are current, time-bound, and approved. ## Related docs + - Risk management: [`09-risk-management.md`](09-risk-management.md) - Incident response: [`11-incident-response.md`](11-incident-response.md) +- Threat model: [`23-threat-model.md`](23-threat-model.md) diff --git a/docs/20-runbooks/rbk-005-azure-arc-onboarding.md b/docs/20-runbooks/rbk-005-azure-arc-onboarding.md index c8cd89b..24496f2 100644 --- a/docs/20-runbooks/rbk-005-azure-arc-onboarding.md +++ b/docs/20-runbooks/rbk-005-azure-arc-onboarding.md @@ -1,16 +1,32 @@ # RBK-005 Azure Arc Onboarding ## Purpose -Onboard hybrid servers to Azure Arc with consistent policy and logging. + +Onboard hybrid servers to Azure Arc with consistent policy and logging while preserving an approval and evidence trail. + +## Preconditions + +- Use an approved tenant-specific onboarding package; repository scripts are validation-only dry runs. +- Confirm change approval, owner, subscription, resource group, region, and rollback plan. +- Use managed identity or approved short-lived credentials. Never place secrets in commands or logs. ## Steps -1. Validate connectivity and prerequisites. -2. Run approved onboarding script (PowerShell or Bash). -3. Tag resources for ownership and environment. -4. Validate policy compliance and log forwarding. -5. Document onboarding evidence. + +1. Run the repository dry-run script to validate identifiers and produce a JSONL evidence record. +2. Review the dry-run output and obtain the required change approval. +3. Run the approved tenant-specific onboarding package in the approved maintenance window. +4. Validate Azure Arc connectivity, policy scope, and log forwarding. +5. Record outcome, approver, package version, and rollback status in the evidence store. + +## Failure handling + +- Stop on validation, connectivity, identity, policy, or logging failures. +- Do not retry with broader credentials or disabled controls. +- Roll back through the approved package and escalate according to the incident process. ## Evidence -- Onboarding logs -- Policy compliance snapshot -- Log ingestion validation + +- Dry-run JSONL record and approved change reference +- Approved package version and execution identity +- Azure Arc connectivity and policy compliance snapshots +- Log ingestion validation and rollback outcome diff --git a/docs/23-threat-model.md b/docs/23-threat-model.md new file mode 100644 index 0000000..3eac96e --- /dev/null +++ b/docs/23-threat-model.md @@ -0,0 +1,64 @@ +# Cloud Security Service Threat Model + +## Purpose and assurance boundary + +This threat model connects the public reference architecture to explicit threats, controls, evidence, and residual +risk. It is a design input, not proof of production security, certification, or tenant-specific compliance. Production +use requires a tenant-specific review, approved identities, live validation, and independent assurance where required. + +## Protected assets + +| Asset | Security objective | Evidence source | +| --- | --- | --- | +| Privileged identities and role assignments | Prevent unauthorized elevation and preserve accountability | Entra ID audit logs, PIM records, access reviews | +| Policy definitions and assignments | Prevent unauthorized or unsafe control changes | Git history, approvals, Azure Activity Log, policy snapshots | +| Security telemetry and evidence | Preserve confidentiality, integrity, availability, and retention | Log Analytics configuration, immutable evidence store, collection health | +| Hybrid onboarding packages and identities | Prevent supply-chain compromise and unauthorized enrollment | Signed package provenance, change record, Azure Arc inventory | +| Landing-zone network and key boundaries | Prevent unintended public exposure and secret access | IaC plan, deployment record, network and Key Vault configuration | +| Incident and exception records | Preserve investigation integrity and risk ownership | ITSM records, approvals, retention and access logs | + +## Trust boundaries + +```mermaid +flowchart LR + Engineer[Platform engineer] -->|approved pull request| Pipeline[CI and deployment pipeline] + Pipeline -->|managed identity| Azure[Azure control plane] + Azure --> Hybrid[Azure Arc connected estate] + Azure --> Telemetry[Log Analytics and SIEM] + Telemetry --> Evidence[Protected evidence store] + SecOps[Security operations] --> Telemetry + Auditor[Authorized auditor] --> Evidence +``` + +1. Human-to-source boundary: contributors propose changes; reviews and required checks authorize them. +2. Source-to-pipeline boundary: workflow definitions and dependencies determine trusted execution. +3. Pipeline-to-Azure boundary: deployment identity limits mutation scope. +4. Azure-to-hybrid boundary: onboarding packages and Arc identities cross into externally operated hosts. +5. Telemetry-to-evidence boundary: collection, transformation, retention, and access can affect evidence integrity. + +## Threat register + +| ID | STRIDE | Threat and boundary | Preventive and detective controls | Required evidence | Residual risk and owner | +| --- | --- | --- | --- | --- | --- | +| TM-01 | Spoofing / elevation | Compromised contributor or pipeline identity changes controls | MFA, PIM, branch protection, least-privilege managed identity, signed artifacts | Access review, workflow run, deployment identity log | Identity compromise remains possible; identity owner reviews quarterly | +| TM-02 | Tampering | Policy, IaC, or evidence is altered outside the approved flow | Pull-request review, immutable action pins, audit-mode rollout, protected evidence storage | Git and Azure Activity logs, evidence integrity check | Privileged emergency changes require reconciliation; platform owner | +| TM-03 | Repudiation | Operator actions cannot be correlated to approval and deployment | Correlation IDs, change tickets, structured logs, retained activity logs | Approval, run ID, commit SHA, deployment record | External system outages may interrupt correlation; service owner | +| TM-04 | Information disclosure | Logs, secrets, or evidence are exposed through public endpoints or broad access | Managed identity, private endpoints, RBAC, secret-free logs, deny-by-default network rules | Access logs, network config, secret scanning results | Misclassification and downstream exports remain; data owner | +| TM-05 | Denial of service | Control plane, telemetry, or evidence collection becomes unavailable | Quotas, health monitoring, retry limits, break-glass runbook, tested recovery | Availability metrics, alerts, recovery test | Regional/provider outage remains; service continuity owner | +| TM-06 | Supply-chain tampering | Mutable dependencies or onboarding packages execute malicious code | SHA-pinned actions, reproducible packages, SBOM, attestations, isolated runners | Provenance, SBOM, signature verification, runner logs | Upstream trusted-source compromise remains; supply-chain owner | +| TM-07 | Boundary bypass | Hybrid host is enrolled into the wrong tenant, subscription, or policy scope | Validated identifiers, dry-run approval, scoped identity, post-onboarding checks | Dry-run JSONL, package version, Arc inventory, policy snapshot | Host administrator can alter local state; hybrid service owner | +| TM-08 | Detection failure | Missing or manipulated telemetry prevents response | Collection health alerts, analytic rule tests, retention controls, failure escalation | Ingestion health, rule results, evidence failure record | Novel attacks and blind spots remain; SecOps owner | + +## Validation and review + +- Review this model after architecture, identity, trust-boundary, or data-flow changes and at least quarterly. +- Validate controls through repository checks, deployment what-if, adversarial exercises, and evidence sampling. +- Record accepted residual risks with owner, expiry, treatment, and approval. +- Stop releases when high-severity threats lack an approved treatment or required evidence collection fails. + +## Related documents + +- Reference architecture: [`04-reference-architecture.md`](04-reference-architecture.md) +- Risk management: [`09-risk-management.md`](09-risk-management.md) +- Audit readiness: [`10-audit-readiness.md`](10-audit-readiness.md) +- Incident response: [`11-incident-response.md`](11-incident-response.md) diff --git a/impl/azure/landing-zone/bicep/main.bicep b/impl/azure/landing-zone/bicep/main.bicep index db8c33f..2ff0fd2 100644 --- a/impl/azure/landing-zone/bicep/main.bicep +++ b/impl/azure/landing-zone/bicep/main.bicep @@ -1,10 +1,20 @@ -// Entry point for landing zone deployment (stub) +targetScope = 'resourceGroup' + +@description('Azure region for the reference security baseline.') param location string = 'eastus' +@description('Short environment name used in resource names.') +@minLength(2) +@maxLength(12) +param environmentName string = 'demo' + +var suffix = uniqueString(subscription().subscriptionId, resourceGroup().id) + module identity 'modules/identity.bicep' = { name: 'identity' params: { location: location + name: 'id-csm-${environmentName}-${suffix}' } } @@ -12,6 +22,7 @@ module network 'modules/network-hubspoke.bicep' = { name: 'network' params: { location: location + name: 'vnet-csm-${environmentName}-${suffix}' } } @@ -19,6 +30,7 @@ module logging 'modules/logging-siem.bicep' = { name: 'logging' params: { location: location + name: 'log-csm-${environmentName}-${suffix}' } } @@ -26,6 +38,7 @@ module keyvault 'modules/keyvault.bicep' = { name: 'keyvault' params: { location: location + name: take('kvcsm${environmentName}${suffix}', 24) } } @@ -35,3 +48,9 @@ module policies 'modules/policy-assignments.bicep' = { location: location } } + +output managedIdentityId string = identity.outputs.resourceId +output virtualNetworkId string = network.outputs.resourceId +output logAnalyticsWorkspaceId string = logging.outputs.resourceId +output keyVaultId string = keyvault.outputs.resourceId +output policyAssignmentId string = policies.outputs.resourceId \ No newline at end of file diff --git a/impl/azure/landing-zone/bicep/modules/identity.bicep b/impl/azure/landing-zone/bicep/modules/identity.bicep index 987121b..26a26ef 100644 --- a/impl/azure/landing-zone/bicep/modules/identity.bicep +++ b/impl/azure/landing-zone/bicep/modules/identity.bicep @@ -1,5 +1,14 @@ param location string +param name string -// TODO: Define identity resources (managed identities, role assignments) -// Notes: Use PIM for privileged roles and enforce least privilege. -output identityBaseline string = 'identity-baseline-stub' +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: name + location: location + tags: { + purpose: 'cloud-security-service-model' + privilegeModel: 'no-default-role-assignments' + } +} + +output resourceId string = identity.id +output principalId string = identity.properties.principalId \ No newline at end of file diff --git a/impl/azure/landing-zone/bicep/modules/keyvault.bicep b/impl/azure/landing-zone/bicep/modules/keyvault.bicep index 0dbd595..34001e9 100644 --- a/impl/azure/landing-zone/bicep/modules/keyvault.bicep +++ b/impl/azure/landing-zone/bicep/modules/keyvault.bicep @@ -1,4 +1,25 @@ param location string +param name string -// TODO: Define Key Vault with RBAC model. -output keyVaultBaseline string = 'keyvault-stub' +resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' = { + name: name + location: location + properties: { + tenantId: tenant().tenantId + sku: { + family: 'A' + name: 'standard' + } + enableRbacAuthorization: true + enablePurgeProtection: true + enableSoftDelete: true + publicNetworkAccess: 'Disabled' + networkAcls: { + bypass: 'None' + defaultAction: 'Deny' + } + } +} + +output resourceId string = keyVault.id +output vaultUri string = keyVault.properties.vaultUri \ No newline at end of file diff --git a/impl/azure/landing-zone/bicep/modules/logging-siem.bicep b/impl/azure/landing-zone/bicep/modules/logging-siem.bicep index 136d169..f210409 100644 --- a/impl/azure/landing-zone/bicep/modules/logging-siem.bicep +++ b/impl/azure/landing-zone/bicep/modules/logging-siem.bicep @@ -1,4 +1,21 @@ param location string +param name string -// TODO: Define Log Analytics workspace and diagnostic settings placeholders. -output loggingBaseline string = 'logging-siem-stub' +resource workspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { + name: name + location: location + properties: { + retentionInDays: 90 + publicNetworkAccessForIngestion: 'Disabled' + publicNetworkAccessForQuery: 'Disabled' + features: { + enableLogAccessUsingOnlyResourcePermissions: true + } + workspaceCapping: { + dailyQuotaGb: 1 + } + } +} + +output resourceId string = workspace.id +output customerId string = workspace.properties.customerId \ No newline at end of file diff --git a/impl/azure/landing-zone/bicep/modules/network-hubspoke.bicep b/impl/azure/landing-zone/bicep/modules/network-hubspoke.bicep index 64e4dd9..8b4ebae 100644 --- a/impl/azure/landing-zone/bicep/modules/network-hubspoke.bicep +++ b/impl/azure/landing-zone/bicep/modules/network-hubspoke.bicep @@ -1,4 +1,51 @@ param location string +param name string -// TODO: Define hub VNet, spokes, firewall placeholder, private DNS zones. -output networkBaseline string = 'network-hubspoke-stub' +resource nsg 'Microsoft.Network/networkSecurityGroups@2024-05-01' = { + name: '${name}-nsg' + location: location + properties: { + securityRules: [ + { + name: 'DenyInternetInbound' + properties: { + priority: 4096 + access: 'Deny' + direction: 'Inbound' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = { + name: name + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.20.0.0/16' + ] + } + subnets: [ + { + name: 'workload' + properties: { + addressPrefix: '10.20.1.0/24' + networkSecurityGroup: { + id: nsg.id + } + privateEndpointNetworkPolicies: 'Disabled' + } + } + ] + } +} + +output resourceId string = virtualNetwork.id +output workloadSubnetId string = virtualNetwork.properties.subnets[0].id \ No newline at end of file diff --git a/impl/azure/landing-zone/bicep/modules/policy-assignments.bicep b/impl/azure/landing-zone/bicep/modules/policy-assignments.bicep index 26ae5b5..635ce08 100644 --- a/impl/azure/landing-zone/bicep/modules/policy-assignments.bicep +++ b/impl/azure/landing-zone/bicep/modules/policy-assignments.bicep @@ -1,4 +1,24 @@ param location string -// TODO: Define policy assignment placeholders at management group and subscription levels. -output policyAssignments string = 'policy-assignments-stub' +resource allowedLocations 'Microsoft.Authorization/policyAssignments@2024-04-01' = { + name: guid(resourceGroup().id, 'allowed-locations') + properties: { + displayName: 'Allowed locations for the reference resource group' + description: 'Restricts new resources to the approved reference deployment region.' + policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'e56962a6-4747-49cd-b67b-bf8b01975c4c') + enforcementMode: 'DoNotEnforce' + metadata: { + category: 'Security' + rolloutState: 'audit' + } + parameters: { + listOfAllowedLocations: { + value: [ + location + ] + } + } + } +} + +output resourceId string = allowedLocations.id \ No newline at end of file diff --git a/impl/azure/policy-as-code/assignments/mgmt-group-assignment.example.json b/impl/azure/policy-as-code/assignments/mgmt-group-assignment.example.json index fe82731..5d0da84 100644 --- a/impl/azure/policy-as-code/assignments/mgmt-group-assignment.example.json +++ b/impl/azure/policy-as-code/assignments/mgmt-group-assignment.example.json @@ -1,9 +1,22 @@ { - "name": "mgmt-group-baseline-assignment", + "name": "mcsb-audit-assignment", "properties": { - "displayName": "Management Group Baseline Assignment", - "policyDefinitionId": "/providers/Microsoft.Authorization/policySetDefinitions/cloud-security-baseline-initiative", - "scope": "/providers/Microsoft.Management/managementGroups/contoso-mg", - "parameters": {} + "displayName": "Microsoft cloud security benchmark - audit rollout", + "description": "Reference assignment for the Microsoft cloud security benchmark built-in initiative.", + "policyDefinitionId": "/providers/Microsoft.Authorization/policySetDefinitions/1f3afdf9-d0c9-4c3d-847f-89da613e70a8", + "scope": "/providers/Microsoft.Management/managementGroups/replace-with-management-group-id", + "enforcementMode": "DoNotEnforce", + "metadata": { + "assignedBy": "cloud-security-platform-team", + "rolloutStage": "audit", + "changeTicket": "replace-with-approved-change-id", + "reviewAfterDays": 30 + }, + "parameters": {}, + "nonComplianceMessages": [ + { + "message": "Review the benchmark finding with the cloud security platform team before remediation." + } + ] } -} +} \ No newline at end of file diff --git a/impl/azure/policy-as-code/assignments/subscription-assignment.example.json b/impl/azure/policy-as-code/assignments/subscription-assignment.example.json index c8a1e1e..6c547f5 100644 --- a/impl/azure/policy-as-code/assignments/subscription-assignment.example.json +++ b/impl/azure/policy-as-code/assignments/subscription-assignment.example.json @@ -1,9 +1,22 @@ { - "name": "subscription-baseline-assignment", + "name": "iso27001-audit-assignment", "properties": { - "displayName": "Subscription Baseline Assignment", - "policyDefinitionId": "/providers/Microsoft.Authorization/policySetDefinitions/iso27001-aligned-initiative", + "displayName": "ISO 27001:2013 built-in initiative - audit rollout", + "description": "Reference assignment only; it does not establish certification or compliance.", + "policyDefinitionId": "/providers/Microsoft.Authorization/policySetDefinitions/89c6cddc-1c73-4ac1-b19c-54d1a15a42f2", "scope": "/subscriptions/00000000-0000-0000-0000-000000000000", - "parameters": {} + "enforcementMode": "DoNotEnforce", + "metadata": { + "assignedBy": "cloud-security-platform-team", + "rolloutStage": "audit", + "changeTicket": "replace-with-approved-change-id", + "reviewAfterDays": 30 + }, + "parameters": {}, + "nonComplianceMessages": [ + { + "message": "Treat this result as control evidence input, not proof of ISO 27001 certification." + } + ] } -} +} \ No newline at end of file diff --git a/impl/azure/policy-as-code/initiatives/cloud-security-baseline-initiative.json b/impl/azure/policy-as-code/initiatives/cloud-security-baseline-initiative.json index 05ddf67..7dbea49 100644 --- a/impl/azure/policy-as-code/initiatives/cloud-security-baseline-initiative.json +++ b/impl/azure/policy-as-code/initiatives/cloud-security-baseline-initiative.json @@ -1,33 +1,26 @@ { - "name": "cloud-security-baseline-initiative", + "name": "cloud-security-baseline-reference", "properties": { - "displayName": "Cloud Security Baseline", - "description": "Baseline controls for encryption, diagnostics, and network exposure.", + "displayName": "Cloud security baseline reference", + "description": "Small reference initiative using verifiable Azure built-in policy definitions.", "metadata": { - "category": "Security" + "category": "Security", + "version": "1.0.0", + "rolloutDefault": "DoNotEnforce", + "owner": "cloud-security-platform-team" }, "parameters": {}, "policyDefinitions": [ { - "policyDefinitionReferenceId": "encryption-required", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/encryption-required-stub", + "policyDefinitionReferenceId": "secure-transfer-storage", + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9", "parameters": {} }, { - "policyDefinitionReferenceId": "no-public-ip", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/no-public-ip-stub", - "parameters": {} - }, - { - "policyDefinitionReferenceId": "diagnostics-enabled", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/diagnostics-enabled-stub", - "parameters": {} - }, - { - "policyDefinitionReferenceId": "required-tags", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/required-tags-stub", + "policyDefinitionReferenceId": "managed-disks-vm", + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d", "parameters": {} } ] } -} +} \ No newline at end of file diff --git a/impl/azure/policy-as-code/initiatives/iso27001-aligned-initiative.json b/impl/azure/policy-as-code/initiatives/iso27001-aligned-initiative.json index 8af733a..6d1da60 100644 --- a/impl/azure/policy-as-code/initiatives/iso27001-aligned-initiative.json +++ b/impl/azure/policy-as-code/initiatives/iso27001-aligned-initiative.json @@ -1,28 +1,27 @@ { - "name": "iso27001-aligned-initiative", + "name": "iso27001-supporting-controls-reference", "properties": { - "displayName": "ISO 27001 Aligned Security Baseline", - "description": "High-level alignment to ISO 27001 control intent (summary only).", + "displayName": "ISO 27001 supporting controls reference", + "description": "Selected built-in technical controls that may support an ISO 27001 program; this example does not establish certification.", "metadata": { - "category": "Security" + "category": "Security", + "version": "1.0.0", + "rolloutDefault": "DoNotEnforce", + "owner": "cloud-security-platform-team", + "assuranceBoundary": "Requires independent compliance and legal review" }, "parameters": {}, "policyDefinitions": [ { - "policyDefinitionReferenceId": "access-control", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/access-control-stub", + "policyDefinitionReferenceId": "secure-transfer-storage", + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9", "parameters": {} }, { - "policyDefinitionReferenceId": "logging-monitoring", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/logging-monitoring-stub", - "parameters": {} - }, - { - "policyDefinitionReferenceId": "change-management", - "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/change-management-stub", + "policyDefinitionReferenceId": "managed-disks-vm", + "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d", "parameters": {} } ] } -} +} \ No newline at end of file diff --git a/impl/azure/sentinel/README.md b/impl/azure/sentinel/README.md index 545dece..cd0766d 100644 --- a/impl/azure/sentinel/README.md +++ b/impl/azure/sentinel/README.md @@ -1,17 +1,28 @@ -# Sentinel (Stub) +# Microsoft Sentinel Detection References + +## Assurance boundary + +The analytic rules are disabled reference examples. Validate schemas and KQL against a test workspace, establish a +baseline, review exclusions, and obtain approval before enabling them. They are not production detection guarantees. ## Detection engineering workflow -1. Identify detection requirement. -2. Author analytic rule in a test workspace. -3. Peer review and validate logic. -4. Deploy to production via pipeline. -5. Review alert quality monthly. -## Tuning cadence -- Weekly review of false positives. -- Monthly review of detection coverage. -- Quarterly review aligned to threat landscape updates. +1. Define the threat, data dependency, owner, and expected response. +2. Validate query syntax and representative positive and negative cases in a test workspace. +3. Measure false-positive rate, coverage, query cost, and execution duration. +4. Peer review KQL, thresholds, entity mappings, exclusions, and response guidance. +5. Deploy disabled, observe results, then enable through an approved change. +6. Review alert quality monthly and after source-schema or threat changes. + +## Required evidence + +- Query version and commit SHA +- Test dataset or replay window and expected results +- False-positive analysis and approved exclusions +- Query duration and estimated ingestion/query cost +- Owner approval, deployment record, and rollback outcome ## Contents -- Analytic rules in `analytic-rules/` -- Playbook stubs in `playbooks/` + +- Analytic rule references in `analytic-rules/` +- Playbook stubs in `playbooks/` \ No newline at end of file diff --git a/impl/azure/sentinel/analytic-rules/impossible-travel.example.json b/impl/azure/sentinel/analytic-rules/impossible-travel.example.json index f7bce68..31b5c86 100644 --- a/impl/azure/sentinel/analytic-rules/impossible-travel.example.json +++ b/impl/azure/sentinel/analytic-rules/impossible-travel.example.json @@ -1,9 +1,40 @@ { - "name": "Impossible Travel", + "name": "Impossible Travel Reference", "properties": { - "description": "Detects sign-ins from geographically impossible locations.", - "query": "// TODO: add KQL query", + "description": "Flags successful sign-ins for one identity whose calculated travel speed exceeds the review threshold.", + "query": "let lookback = 1h;\nlet maxTravelSpeedKph = 900.0;\nSigninLogs\n| where TimeGenerated > ago(lookback)\n| where ResultType == 0\n| where isnotempty(LocationDetails.geoCoordinates.latitude) and isnotempty(LocationDetails.geoCoordinates.longitude)\n| project UserPrincipalName, TimeGenerated, IPAddress, Country=tostring(LocationDetails.countryOrRegion), Latitude=todouble(LocationDetails.geoCoordinates.latitude), Longitude=todouble(LocationDetails.geoCoordinates.longitude)\n| sort by UserPrincipalName asc, TimeGenerated asc\n| serialize\n| extend PreviousUser=prev(UserPrincipalName), PreviousTime=prev(TimeGenerated), PreviousCountry=prev(Country), PreviousLatitude=prev(Latitude), PreviousLongitude=prev(Longitude)\n| where UserPrincipalName == PreviousUser and TimeGenerated > PreviousTime and Country != PreviousCountry\n| extend DistanceKm=geo_distance_2points(Longitude, Latitude, PreviousLongitude, PreviousLatitude) / 1000.0, Hours=datetime_diff('second', TimeGenerated, PreviousTime) / 3600.0\n| extend SpeedKph=DistanceKm / Hours\n| where SpeedKph > maxTravelSpeedKph\n| project TimeGenerated, UserPrincipalName, IPAddress, Country, PreviousCountry, DistanceKm, Hours, SpeedKph", "severity": "Medium", - "enabled": true + "enabled": false, + "queryFrequency": "PT15M", + "queryPeriod": "PT1H", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT1H", + "suppressionEnabled": true, + "tactics": [ + "InitialAccess", + "CredentialAccess" + ], + "techniques": [ + "T1078" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "FullName", + "columnName": "UserPrincipalName" + } + ] + } + ], + "tuning": { + "owner": "security-operations", + "validationMode": "test-workspace-first", + "reviewCadence": "monthly", + "knownLimitations": "VPN, mobile carrier, and trusted egress changes can create false positives.", + "requiredExclusions": "Approved VPN and trusted egress ranges must be reviewed before enabling." + } } -} +} \ No newline at end of file diff --git a/impl/azure/sentinel/analytic-rules/suspicious-admin-activity.example.json b/impl/azure/sentinel/analytic-rules/suspicious-admin-activity.example.json index 4528e9f..4549e1b 100644 --- a/impl/azure/sentinel/analytic-rules/suspicious-admin-activity.example.json +++ b/impl/azure/sentinel/analytic-rules/suspicious-admin-activity.example.json @@ -1,9 +1,51 @@ { - "name": "Suspicious Admin Activity", + "name": "Suspicious Admin Activity Reference", "properties": { - "description": "Detects unusual privileged actions.", - "query": "// TODO: add KQL query", + "description": "Flags bursts of successful high-impact directory administration operations by one actor.", + "query": "let lookback = 1h;\nlet operationThreshold = 5;\nAuditLogs\n| where TimeGenerated > ago(lookback)\n| where Result =~ 'success'\n| where OperationName in~ ('Add member to role', 'Add eligible member to role', 'Update conditional access policy', 'Delete conditional access policy', 'Reset user password')\n| extend Actor=tostring(InitiatedBy.user.userPrincipalName), ActorIp=tostring(InitiatedBy.user.ipAddress)\n| where isnotempty(Actor)\n| summarize OperationCount=count(), Operations=make_set(OperationName, 20), Targets=make_set(tostring(TargetResources[0].displayName), 20) by Actor, ActorIp, bin(TimeGenerated, 15m)\n| where OperationCount >= operationThreshold", "severity": "High", - "enabled": true + "enabled": false, + "queryFrequency": "PT15M", + "queryPeriod": "PT1H", + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "suppressionDuration": "PT1H", + "suppressionEnabled": true, + "tactics": [ + "Persistence", + "PrivilegeEscalation", + "DefenseEvasion" + ], + "techniques": [ + "T1098", + "T1078" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "identifier": "FullName", + "columnName": "Actor" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "identifier": "Address", + "columnName": "ActorIp" + } + ] + } + ], + "tuning": { + "owner": "security-operations", + "validationMode": "test-workspace-first", + "reviewCadence": "monthly", + "knownLimitations": "Approved bulk administration and emergency response can create false positives.", + "requiredExclusions": "Approved automation identities and emergency changes require time-bound exclusions." + } } -} +} \ No newline at end of file diff --git a/impl/hybrid/azure-arc/README.md b/impl/hybrid/azure-arc/README.md index c41de6b..943f007 100644 --- a/impl/hybrid/azure-arc/README.md +++ b/impl/hybrid/azure-arc/README.md @@ -1,9 +1,25 @@ # Azure Arc (Hybrid) ## Overview -Azure Arc onboarding enables consistent policy and logging for hybrid servers. Scripts provided here are safe -placeholders and must be updated with actual commands. + +Azure Arc onboarding enables consistent policy and logging for hybrid servers. The scripts in `onboarding/` are +safe validation-only references. They never mutate Azure or a server, and they reject `--execute` / `-Execute`. +Production onboarding must use an approved tenant-specific package with managed identity or short-lived credentials. + +## Dry-run contract + +Both scripts validate the resource group, region, and subscription ID, then append one JSONL evidence record to a +configurable log path. Use a temporary or protected evidence location and retain it according to the audit policy. + +```bash +./onboarding/arc-onboard.sh rg-security northeurope 00000000-0000-0000-0000-000000000000 +``` + +```powershell +./onboarding/arc-onboard.ps1 -ResourceGroup rg-security -Region northeurope -SubscriptionId 00000000-0000-0000-0000-000000000000 +``` ## Contents -- Onboarding scripts: `onboarding/` -- Policy scope guidance: `policy-scope/arc-policy-scope.md` + +- Onboarding validation scripts: `onboarding/` +- Policy scope guidance: `policy-scope/arc-policy-scope.md` \ No newline at end of file diff --git a/impl/hybrid/azure-arc/onboarding/arc-onboard.ps1 b/impl/hybrid/azure-arc/onboarding/arc-onboard.ps1 index b59c186..b2481f3 100644 --- a/impl/hybrid/azure-arc/onboarding/arc-onboard.ps1 +++ b/impl/hybrid/azure-arc/onboarding/arc-onboard.ps1 @@ -1,13 +1,43 @@ -Param( - [Parameter(Mandatory=$true)][string]$ResourceGroup, - [Parameter(Mandatory=$true)][string]$Region, - [Parameter(Mandatory=$true)][string]$SubscriptionId +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidatePattern('^[A-Za-z0-9._()/-]{1,90}$')] + [string]$ResourceGroup, + + [Parameter(Mandatory = $true)] + [ValidatePattern('^[a-z0-9-]{2,32}$')] + [string]$Region, + + [Parameter(Mandatory = $true)] + [ValidatePattern('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')] + [string]$SubscriptionId, + + [string]$LogFile = (Join-Path ([System.IO.Path]::GetTempPath()) 'arc-onboard-dry-run.jsonl'), + + [switch]$Execute ) -$logFile = "arc-onboard.log" -"Starting Azure Arc onboarding stub" | Tee-Object -FilePath $logFile -Append -"ResourceGroup: $ResourceGroup" | Tee-Object -FilePath $logFile -Append -"Region: $Region" | Tee-Object -FilePath $logFile -Append -"SubscriptionId: $SubscriptionId" | Tee-Object -FilePath $logFile -Append +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if ($Execute) { + throw 'Execution is intentionally unavailable in this public reference. Use an approved tenant-specific onboarding package.' +} + +$parent = Split-Path -Parent $LogFile +if ($parent) { + New-Item -ItemType Directory -Path $parent -Force | Out-Null +} + +$record = [ordered]@{ + timestamp = [DateTimeOffset]::UtcNow.ToString('o') + operation = 'azure-arc-onboard' + mode = 'dry-run' + resourceGroup = $ResourceGroup + region = $Region + subscriptionId = $SubscriptionId + result = 'validated' +} -Write-Output "TODO: add actual Azure Arc onboarding commands" +$line = $record | ConvertTo-Json -Compress +$line | Tee-Object -FilePath $LogFile -Append \ No newline at end of file diff --git a/impl/hybrid/azure-arc/onboarding/arc-onboard.sh b/impl/hybrid/azure-arc/onboarding/arc-onboard.sh old mode 100644 new mode 100755 index ef9637a..0c33c0d --- a/impl/hybrid/azure-arc/onboarding/arc-onboard.sh +++ b/impl/hybrid/azure-arc/onboarding/arc-onboard.sh @@ -1,20 +1,45 @@ #!/usr/bin/env bash set -euo pipefail +usage() { + echo "Usage: ./arc-onboard.sh [--log-file ] [--execute]" +} + RESOURCE_GROUP="${1:-}" REGION="${2:-}" SUBSCRIPTION_ID="${3:-}" +shift "$(( $# >= 3 ? 3 : $# ))" +LOG_FILE="${TMPDIR:-/tmp}/arc-onboard-dry-run.jsonl" +EXECUTE=false + +while (( $# > 0 )); do + case "$1" in + --log-file) + [[ $# -ge 2 ]] || { echo "--log-file requires a path" >&2; exit 2; } + LOG_FILE="$2" + shift 2 + ;; + --execute) + EXECUTE=true + shift + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +[[ "$RESOURCE_GROUP" =~ ^[A-Za-z0-9._()/-]{1,90}$ ]] || { echo "Invalid resource group name" >&2; exit 2; } +[[ "$REGION" =~ ^[a-z0-9-]{2,32}$ ]] || { echo "Invalid Azure region" >&2; exit 2; } +[[ "$SUBSCRIPTION_ID" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]] || { echo "Invalid subscription ID" >&2; exit 2; } -if [[ -z "$RESOURCE_GROUP" || -z "$REGION" || -z "$SUBSCRIPTION_ID" ]]; then - echo "Usage: ./arc-onboard.sh " - exit 1 +if [[ "$EXECUTE" == true ]]; then + echo "Execution is intentionally unavailable in this public reference. Use an approved tenant-specific onboarding package." >&2 + exit 3 fi -LOG_FILE="arc-onboard.log" -{ - echo "Starting Azure Arc onboarding stub" - echo "ResourceGroup: $RESOURCE_GROUP" - echo "Region: $REGION" - echo "SubscriptionId: $SUBSCRIPTION_ID" - echo "TODO: add actual Azure Arc onboarding commands" -} | tee -a "$LOG_FILE" +mkdir -p "$(dirname "$LOG_FILE")" +printf '{"timestamp":"%s","operation":"azure-arc-onboard","mode":"dry-run","resourceGroup":"%s","region":"%s","subscriptionId":"%s","result":"validated"}\n' \ + "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$RESOURCE_GROUP" "$REGION" "$SUBSCRIPTION_ID" | tee -a "$LOG_FILE" \ No newline at end of file diff --git a/scripts/validate-repository.sh b/scripts/validate-repository.sh new file mode 100755 index 0000000..96dc803 --- /dev/null +++ b/scripts/validate-repository.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +failures=0 + +report_failure() { + printf 'ERROR: %s\n' "$1" >&2 + failures=$((failures + 1)) +} + +while IFS= read -r -d '' script; do + if LC_ALL=C grep -q $'\r' "$script"; then + report_failure "$script contains CRLF line endings; shell scripts must use LF." + fi + + if ! bash -n "$script"; then + report_failure "$script failed bash syntax validation." + fi +done < <(find impl scripts -type f -name '*.sh' -print0) + +while IFS= read -r -d '' json_file; do + if command -v jq >/dev/null 2>&1; then + json_validator=(jq empty) + elif command -v python >/dev/null 2>&1; then + json_validator=(python -m json.tool) + elif command -v python.exe >/dev/null 2>&1; then + json_validator=(python.exe -m json.tool) + else + report_failure "jq or Python is required for JSON validation." + break + fi + + if ! "${json_validator[@]}" "$json_file" >/dev/null; then + report_failure "$json_file failed JSON parsing." + fi +done < <(find impl -type f -name '*.json' -print0) + +if command -v az >/dev/null 2>&1; then + if ! az bicep build --file impl/azure/landing-zone/bicep/main.bicep --stdout >/dev/null; then + report_failure "Landing-zone Bicep failed compilation." + fi +else + report_failure "Azure CLI with Bicep is required for repository validation." +fi + +if grep -R -n --include='*.json' -- '-stub"' impl/azure/policy-as-code; then + report_failure "Policy examples must not reference fictional stub identifiers." +fi + +while IFS= read -r -d '' assignment; do + if ! grep -q '"enforcementMode": "DoNotEnforce"' "$assignment"; then + report_failure "$assignment must default to audit-only DoNotEnforce rollout." + fi +done < <(find impl/azure/policy-as-code/assignments -type f -name '*.json' -print0) +arc_log="$(mktemp)" +if ! bash impl/hybrid/azure-arc/onboarding/arc-onboard.sh rg-security northeurope 00000000-0000-0000-0000-000000000000 --log-file "$arc_log" >/dev/null; then + report_failure "Azure Arc Bash dry-run contract failed." +fi +if bash impl/hybrid/azure-arc/onboarding/arc-onboard.sh rg-security northeurope 00000000-0000-0000-0000-000000000000 --execute >/dev/null 2>&1; then + report_failure "Azure Arc Bash reference must reject execution." +fi +if ! grep -q '"mode":"dry-run"' "$arc_log"; then + report_failure "Azure Arc Bash dry run did not produce structured evidence." +fi +rm -f "$arc_log" + +if ! grep -q "\[switch\]\$Execute" impl/hybrid/azure-arc/onboarding/arc-onboard.ps1 || + ! grep -q "ConvertTo-Json -Compress" impl/hybrid/azure-arc/onboarding/arc-onboard.ps1; then + report_failure "Azure Arc PowerShell reference must expose dry-run evidence and fail-closed execution." +fi +if grep -R -n --include='*.json' 'TODO: add KQL query' impl/azure/sentinel/analytic-rules; then + report_failure "Sentinel analytic rules must contain bounded testable KQL." +fi + +while IFS= read -r -d '' rule; do + for field in queryFrequency queryPeriod tuning; do + if ! grep -q "\"$field\"" "$rule"; then + report_failure "$rule is missing required Sentinel tuning field: $field." + fi + done +done < <(find impl/azure/sentinel/analytic-rules -type f -name '*.json' -print0) +if (( failures > 0 )); then + printf 'Repository validation failed with %d error(s).\n' "$failures" >&2 + exit 1 +fi + +printf 'Repository validation passed.\n'