diff --git a/src/pages/docs/platform-hub/policies/best-practices.md b/src/pages/docs/platform-hub/policies/best-practices.md index e7f4eba8ef..805f302065 100644 --- a/src/pages/docs/platform-hub/policies/best-practices.md +++ b/src/pages/docs/platform-hub/policies/best-practices.md @@ -2,75 +2,160 @@ layout: src/layouts/Default.astro pubDate: 2025-09-11 modDate: 2025-09-25 -title: Policies best practices -subtitle: Best practices for creating policies within Platform Hub +title: Policy best practices +subtitle: Guidance on naming, rollout, and writing reliable policies icon: fa-solid fa-lock -navTitle: Best Practices +navTitle: Best practices navSection: Policies -description: Best practices for creating policies within Platform Hub +description: Best practices for creating and managing policies in Platform Hub. navOrder: 165 --- -## Policies administration +This page covers the practices that will save you time and prevent problems as you build and roll out policies across your organization. If you're writing your first policy, start with the [getting started guide](/docs/platform-hub/policies) first. -### Establish a naming standard +## Naming your policies -Use a [ Prefix ] - [ Policy Name ] that is easy for everyone to understand the policy's purpose. The [ Prefix ] should reflect when the policy will run. +A consistent naming standard makes it easy for everyone to understand what a policy does and when it runs, without having to open it. -For example: +Use the format **[Scope] - [Policy name]**, where the scope prefix reflects what type of execution the policy applies to: -- Deployments - [ Policy Name ] for policies designed to run during deployments only. -- Runbook Runs - [ Policy Name ] for policies designed to run during runbooks runs only. -- Deployments and Runbook Runs - [ Policy Name ] for policies for designed to run for deployments or runbooks runs. +| Scope prefix | Use when | +| --- | --- | +| `Deployments` | The policy only applies to deployments | +| `Runbook Runs` | The policy only applies to runbook runs | +| `Deployments and Runbook Runs` | The policy applies to both | -### Turn on SIEM audit log streaming +For example: `Deployments - Manual intervention required` or `Runbook Runs - Main branch only`. -All policy evaluations are logged to the audit log. Ensure [audit log streaming](/docs/security/users-and-teams/auditing/audit-stream) is enabled to log those evaluations to Splunk, SumoLogic, or an OpenTelemetry collector. SIEM tools can provide alerting and visualizations that you can customize to your requirements. +## Use warn before block -## Creating and Updating Policies +Every new policy should start with `"action": "warn"` in the default result. A warning lets the execution proceed but records the violation in the task log, dashboard, and audit log. This gives you a chance to verify the policy is evaluating the right executions before it starts blocking anyone. -### Start restrictive, then make generic +Once you've confirmed the policy is working as expected, switch to `"action": "block"`. -Consider a policy that will block the execution of deployments and runbook runs. By default, that policy applies to all deployments and runbook runs. +```rego +# Start here while testing +default result := {"allowed": false, "action": "warn"} -When creating a new policy, be as restrictive as possible by limiting it to: +# Switch to this once confirmed +default result := {"allowed": false, "action": "block"} +``` -- A specific hook - such a deployment or a runbook run (not both) -- A specific project +You can also use the `action` field in individual rules to block in production while warning elsewhere. See the [block in production, warn elsewhere](/docs/platform-hub/policies/examples#block-in-production-warn-elsewhere) example. -That will limit a policy's "blast radius." Once you are confident the policy is working as intended, extend the policy to cover more projects or tenants. When acceptable, switch the policy to project groups or spaces. +## Start narrow, then broaden -### Provide a verbose failure reason +When you create a new policy, limit its scope as tightly as possible: -A policy violation will be the first experience for must users with policies within Octopus Deploy. For example, when a policy blocks a deployment or runbook run. Provide a verbose failure reason to help the user self-service the solution. +1. **Start with a single project and execution type.** For example, scope to one project and deployments only. This limits the blast radius if the policy behaves unexpectedly. +2. **Extend to more projects or tenants** once you're confident the policy is correct. +3. **Extend to project groups or spaces** once you're satisfied with behaviour across multiple projects. + +This progression also gives teams time to fix violations before the policy scope reaches them, rather than discovering a blocked deployment with no warning. + +## Write a clear violation reason + +A policy violation is often the first time a user encounters the policies feature. The violation reason is the message they see when a deployment or runbook run fails. Make it specific enough for them to understand what's wrong and what to do about it. + +Avoid generic messages like "Policy violation" or "Deployment blocked". Instead, explain what was expected: :::figure -![An example of a verbose policy violation error message to help users self-service](/docs/img/platform-hub/policies/policy-violation-user-message.png) +![An example of a clear, actionable policy violation message](/docs/img/platform-hub/policies/policy-violation-user-message.png) ::: -### Check for both the existence of steps and if they’ve been skipped +You can set a default violation reason in the policy UI, and override it per rule using the `reason` property in your conditions Rego: + +```rego +result := {"allowed": false, "reason": "A manual intervention step is required and cannot be skipped in this environment"} if { + manual_intervention_skipped +} +``` -Policies can be written to check for the existence of specific steps within a deployment or runbook process. It's important to remember that in many cases those deployments and runbook processes have existed for years. Octopus Deploy has the capability to require a step and prevent it from being skipped. But it is unlikely that _all_ of those required steps in _all_ of your deployment and runbook processes have been configured to prevent them from being skipped. +## Check for both existence and skipping -It is not enough for a policy to simply check for the existence of a specific step. The policy must also ensure users don't elect to skip the required step (for whatever reason). +It's not enough to check that a required step exists in the process. Users can skip steps when scheduling a deployment or runbook run, even if the step is present. :::figure -![An example of a step that can be skipped before scheduling a deployment or runbook run](/docs/img/platform-hub/policies/a-step-that-can-be-skipped-violating-a-policy.png) +![An example of a step that can be skipped when scheduling a deployment](/docs/img/platform-hub/policies/a-step-that-can-be-skipped-violating-a-policy.png) ::: -The resulting policy will have two conditions. +Your policy conditions should check both that the step is present and that it hasn't been skipped: + +```rego +result := {"allowed": true} if { + some step in input.Steps + step.Source.SlugOrId == "" + not step.Id in input.SkippedSteps + step.Enabled == true +} +``` :::figure -![An example of a policy that has both the existence and that isn't skipped](/docs/img/platform-hub/policies/example-of-policy-with-two-conditions.png) +![An example of a policy checking both step existence and that it is not skipped](/docs/img/platform-hub/policies/example-of-policy-with-two-conditions.png) ::: -### Check for parallel execution +See the [steps and skipping examples](/docs/platform-hub/policies/examples#ensure-required-steps-are-present) for complete patterns. + +## Guard against conditional fields + +Three input fields are not always present in the input object: `Tenant`, `Release`, and `Runbook`. Referencing them without checking for their existence first will cause a policy evaluation error. + +| Field | When it's present | +| --- | --- | +| `Tenant` | Tenanted deployments only | +| `Release` | Deployments only | +| `Runbook` | Runbook runs only | + +Always guard against their absence in your scope or conditions: + +```rego +# Safe: check Runbook exists before accessing its properties +evaluate if { + input.Runbook + input.Runbook.Id == "" +} + +# Unsafe: will error if Runbook is absent +evaluate if { + input.Runbook.Id == "" +} +``` + +The simplest way to avoid this is to scope your policy to deployments only or runbook runs only when the policy is specific to one type. See the [scoping examples](/docs/platform-hub/policies/examples#scoping-examples). + +## Check for parallel execution + +Steps can be configured to run in parallel or sequentially. If your organization requires sequential execution for compliance or audit purposes, add a policy to enforce it. + +Each item in the `Execution` input field has a `StartTrigger` property with one of two values: + +- `StartAfterPrevious`: the step runs after the previous step completes +- `StartWithPrevious`: the step runs at the same time as the previous step + +To enforce sequential execution: + +```rego +result := {"allowed": true} if { + every execution in input.Execution { + execution.StartTrigger != "StartWithPrevious" + } +} +``` + +See the [prevent parallel execution](/docs/platform-hub/policies/examples#prevent-parallel-execution) example for the complete policy. + +## Stream evaluations to your SIEM + +All policy evaluations are recorded in the Octopus audit log. If your organization uses a SIEM tool such as Splunk, Sumo Logic, or an OpenTelemetry collector, set up [audit log streaming](/docs/security/users-and-teams/auditing/audit-stream) to forward those records automatically. + +This gives your security team visibility into policy violations across your entire Octopus instance, and lets you build dashboards and alerts that match your compliance requirements. -Steps can be configured to run in parallel or sequentially. If your organization requires sequential execution for compliance or troubleshooting purposes, create a policy to check the `Execution` array in the input schema. +## Testing your policy -Each execution phase has a `StartTrigger` property that indicates when it should run: +Before extending a policy's scope or switching from warn to block, verify it's evaluating correctly: -- `StartAfterPrevious` - Steps run sequentially -- `StartWithPrevious` - Steps run in parallel +1. Run a deployment or runbook run that should fail the policy. Confirm the violation appears in the task log and project dashboard. +2. Run a deployment or runbook run that should pass the policy. Confirm it proceeds without a violation. +3. Check the audit log under **Configuration** > **Audit**, filtered by **Compliance Policy Evaluated**, to see the full evaluation history. -To enforce sequential execution, check that no execution phases have `StartTrigger` set to `StartWithPrevious`. See the [examples page](/docs/platform-hub/policies/examples) for a sample policy. +To see the exact input object that was passed to the policy engine for a specific execution, turn on the verbose option in the task log. This is useful when a policy isn't evaluating as expected. See [Troubleshooting policies](/docs/platform-hub/policies/troubleshooting) for more detail. diff --git a/src/pages/docs/platform-hub/policies/examples.md b/src/pages/docs/platform-hub/policies/examples.md index 798a9c325f..fa0456776c 100644 --- a/src/pages/docs/platform-hub/policies/examples.md +++ b/src/pages/docs/platform-hub/policies/examples.md @@ -2,378 +2,498 @@ layout: src/layouts/Default.astro pubDate: 2025-09-11 modDate: 2025-11-25 -title: Policies examples -subtitle: Examples of policies for different deployment scenarios +title: Policy examples +subtitle: Ready-to-use Rego organized by what you want to enforce icon: fa-solid fa-lock navTitle: Examples navSection: Policies -description: Example code for enforcing policies +description: Example Rego for common policy scenarios, organized by enforcement goal. navOrder: 161 --- -There are many different deployment scenarios that you might have that need to be evaluated in order to meet policy conditions. You can use this page as a reference document to help you quickly get started with enforcing policies. +This page organizes policy examples by what you're trying to achieve, rather than by the underlying input fields they use. Find the scenario closest to your goal, copy the Rego, and adapt it to your environment. + +If you haven't written a policy before, start with the [getting started guide](/docs/platform-hub/policies). For the full list of input fields available in your Rego, see the [policy input schema](/docs/platform-hub/policies/schema). ## How to use these examples -You can create policies using the editor available when editing a policy in the Platform Hub or by writing OCL files directly in your Git repository. The examples below show the Rego code for both the scope and conditions sections that you'll need. +Each example shows Rego for the scope and conditions sections separately. You can apply them in two ways. -### Using the policy editor +**Using the policy editor:** Copy the scope Rego into the **Scope** editor and the conditions Rego into the **Conditions** editor, including the `package` declaration in each. The package name must match your policy's slug. -When creating a policy using the policy editor in Platform Hub: +**Using OCL files:** See [Writing policies as OCL files](#writing-policies-as-ocl-files) at the end of this page. -1. Enter the policy name, description, violation action and violation reason in the UI fields -2. Add the package name at the top of both the Scope and Conditions editors - this must match your policy's slug -3. Copy the scope Rego code into the Scope editor (including the package declaration) -4. Copy the conditions Rego code into the Conditions editor (including the package declaration) +:::hint +Start every new policy with `"action": "warn"` in your default result. This lets executions proceed while you verify the policy is evaluating correctly, without risking blocked deployments. Switch to `"action": "block"` once you're confident it's working as expected. +::: -For example, if your policy slug is `manual_intervention_required`, you need to include `package manual_intervention_required` at the top of both editors. +--- -### Using OCL files +## Ensure required steps are present -If you prefer to write policies as OCL files in your Git repository, see the [Writing policies as OCL files](#writing-policies-as-ocl-files) section at the end of this page for the complete format. +Use these examples when you want to guarantee that specific steps, such as approvals, security scans, or manual interventions, are always included in a deployment or runbook run and can't be removed or skipped. -## Scoping examples +### Require a manual intervention step -The following examples will cover various ways that you can scope your policies: +Blocks deployments that don't include a manual intervention step. Also blocks deployments where the step exists but has been skipped. -### Scope policy to a space or many spaces +**Scope** (apply to the environments where manual intervention is required): -```ruby -package scope_example +```rego +package manual_intervention_required default evaluate := false -evaluate if { - # input.Space.Name == "" - If you want to use Space Name - # input.Space.Id == "" - If you want to use Space Id - # input.Space.Slug in ["", ""] - If you want to check multiple Spaces - input.Space.Slug == "" +evaluate if { + input.Environment.Slug == "" + input.Project.Slug == "" } ``` -### Scope policy to an environment or many environments +**Conditions:** -```ruby -package scope_example +```rego +package manual_intervention_required -default evaluate := false +default result := {"allowed": false, "action": "warn"} -evaluate if { - # input.Environment.Name == "" - If you want to use Environment Name - # input.Environment.Id == "" - If you want to use Environment Id - # input.Environment.Slug in ["", ""] - If you want to check multiple Environments - input.Environment.Slug == "" +result := {"allowed": true} if { + some step in input.Steps + step.ActionType == "Octopus.Manual" + not manual_intervention_skipped } -``` - -### Scope policy to a project or many projects -```ruby -package scope_example - -default evaluate := false +result := {"allowed": false, "reason": "A manual intervention step is required and cannot be skipped"} if { + manual_intervention_skipped +} -evaluate if { - # input.Project.Name == "" - If you want to use Project Name - # input.Project.Id == "" - If you want to use Project Id - # input.Project.Slug in ["", ""] - If you want to check multiple Projects - input.Project.Slug == "" +manual_intervention_skipped if { + some step in input.Steps + step.Id in input.SkippedSteps + step.ActionType == "Octopus.Manual" } ``` -### Scope policy to all except a particular project +### Require a step template -```ruby -package scope_example +Blocks deployments where a specific step template is absent, disabled, or skipped. Replace `` with the ID of your step template. -default evaluate := true +**Conditions:** + +```rego +package step_template_required + +default result := {"allowed": false, "action": "warn"} -evaluate := false if { - # input.Project.Slug == "" - If you want to exclude one project - # input.Project.Slug in ["", ""] - If you want to exclude multiple projects - input.Project.Slug == "" +result := {"allowed": true} if { + some step in input.Steps + step.Source.Type == "Step Template" + step.Source.SlugOrId == "" + not step.Id in input.SkippedSteps + step.Enabled == true } ``` -### Scope policy to runbook runs only +### Require a step template at a specific version -```ruby -package scope_example +Use this when you need to ensure a step template is not just present, but pinned to an approved version. For example, to prevent teams from using an outdated or unpatched version of a shared step. -default evaluate := false +**Conditions:** -evaluate if { - input.Runbook +```rego +package step_template_version_required + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": true} if { + some step in input.Steps + step.Source.Type == "Step Template" + step.Source.SlugOrId == "" + step.Source.Version == "" + not step.Id in input.SkippedSteps + step.Enabled == true } ``` -### Scope policy to a runbook and its runs +### Require a process template -```ruby -package scope_example +Process templates can contribute multiple steps. This example checks that all steps from the specified process template are present and none have been skipped or disabled. -default evaluate := false +**Conditions:** -evaluate if { - # input.Runbook.Name == "" - If you want to use Runbook Name - # input.Runbook.Snapshot == "" - If you want to use Runbook Snapshot - # input.Runbook.Id in ["", ""] - If you want to check multiple Runbooks - input.Runbook.Id == "" +```rego +package process_template_required + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": true} if { + count(process_template_steps) > 0 + + every step in process_template_steps { + not step.Id in input.SkippedSteps + step.Enabled + } } + +process_template_steps := [step | + some step in input.Steps + step.Source.Type == "Process Template" + step.Source.SlugOrId == "" +] ``` -### Scope policy to deployments only +### Require a process template at a specific version -```ruby -package scope_example +Uses `semver.compare` to check that every step from the process template matches the required version. -default evaluate := false +**Conditions:** -evaluate if { - not input.Runbook +```rego +package process_template_version_required + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": true} if { + count(process_template_steps) > 0 + + every step in process_template_steps { + semver.compare(step.Source.Version, "") == 0 + step.Enabled + not step.Id in input.SkippedSteps + } } + +process_template_steps := [step | + some step in input.Steps + step.Source.Type == "Process Template" + step.Source.SlugOrId == "" +] ``` -## Conditions examples +### Prevent steps from being skipped or disabled -The following examples will cover different deployment scenarios that can be enforced with policies: +Use these when you want a blanket rule across all steps, rather than targeting a specific one. -### Check that a step isn't skipped in a deployment +**No steps skipped:** -```ruby -package all_steps_are_not_skipped +```rego +package no_steps_skipped -default result := {"allowed": false} +default result := {"allowed": false, "action": "warn"} -# Check all steps are not skipped result := {"allowed": true} if { count(input.SkippedSteps) == 0 } ``` -### Check that all deployment steps are enabled +**No steps disabled:** -```ruby -package all_steps_must_be_enabled +```rego +package no_steps_disabled -default result := {"allowed": true} +default result := {"allowed": false, "action": "warn"} -# Check if any steps are disabled -result := {"allowed": false} if { - some step in input.Steps - step.Enabled == false +result := {"allowed": true} if { + every step in input.Steps { + step.Enabled + } } ``` -### Check that a step exists at the beginning or at the end during execution +--- -```ruby -package check_step_location +## Control deployments to production -default result := {"allowed": false} +Use these examples when you want stricter rules for production environments, such as enforcing minimum release versions, restricting which branches can be deployed, or blocking in production while only warning elsewhere. -# Step is at the start -result := {"allowed": true} if { - input.Steps[0].Source.SlugOrId == "" +### Block in production, warn elsewhere + +This pattern lets you run a policy in warn mode across all environments but escalate to block in production. Useful when rolling out a new policy: you can observe violations across all environments before they start blocking. + +**Conditions:** + +```rego +package block_in_production_warn_elsewhere + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": false, "action": "block"} if { + production + not compliant +} + +result := {"allowed": false, "action": "warn"} if { + not production + not compliant } -# Step is at the end result := {"allowed": true} if { - input.Steps[count(input.Steps)-1].Source.SlugOrId == "" + compliant +} + +production if { + startswith(input.Environment.Slug, "prod") +} + +# Replace this with your actual compliance check +compliant if { + count(input.SkippedSteps) == 0 } ``` -### Check that a Step Template isn't skipped or disabled during a deployment +### Enforce a minimum release version -```ruby -package step_template_is_executed +Blocks production deployments where the release version is below the required minimum, and warns in other environments. -default result := {"allowed": false} +:::hint +`Release` is only present for deployments, not runbook runs. Use `not input.Runbook` in your scope to prevent evaluation errors. +::: -result := {"allowed": true} if { - some step in input.Steps - step.Source.Type == "Step Template" - step.Source.SlugOrId == "" - not step.Id in input.SkippedSteps - step.Enabled == true +**Scope:** + +```rego +package minimum_release_version + +default evaluate := false + +evaluate if { + not input.Runbook } ``` -### Check that a Step Template is of a certain version when deployments occur +**Conditions:** + +```rego +package minimum_release_version -```ruby -package step_template_with_version_is_executed +default result := {"allowed": false, "action": "warn"} -default result := {"allowed": false} +result := {"allowed": false, "action": "block"} if { + production + version_too_low +} + +result := {"allowed": false, "action": "warn"} if { + not production + version_too_low +} result := {"allowed": true} if { - some step in input.Steps - step.Source.Type == "Step Template" - step.Source.SlugOrId == "" - step.Source.Version == "" - not step.Id in input.SkippedSteps - step.Enabled == true + not version_too_low +} + +production if { + startswith(input.Environment.Slug, "prod") +} + +version_too_low if { + semver.compare(input.Release.Version, "1.0.0") < 0 } ``` -### Check that a Process Template is present, and not skipped -Process template can include multiple steps +### Require releases to come from the main branch -```ruby -package process_template_is_executed +Blocks deployments where the release was created from a branch other than `main`. -default result := {"allowed": false} +:::hint +`Release` is only present for deployments. Use `not input.Runbook` in your scope. +::: -result := {"allowed": true} if { - count(process_template_steps) > 0 +**Scope:** - every step in process_template_steps { - not step.Id in input.SkippedSteps - } +```rego +package release_from_main_branch + +default evaluate := false + +evaluate if { + not input.Runbook } +``` -process_template_steps := [step | - some step in input.Steps - step.Source.Type == "Process Template" - step.Source.SlugOrId == "" -] +**Conditions:** + +```rego +package release_from_main_branch + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": true} if { + input.Release.GitRef == "refs/heads/main" +} ``` -### Check that a Process Template is enabled -Process template can include multiple steps +### Require packages to come from the main branch -```ruby -package process_template_is_enabled +Blocks deployments where any package was built from a branch other than `main`. -default result := {"allowed": false} +**Conditions:** + +```rego +package packages_from_main_branch + +default result := {"allowed": false, "action": "warn"} + +all_packages := [pkg | some step in input.Steps; some pkg in step.Packages] result := {"allowed": true} if { - count(process_template_steps) > 0 + count(all_packages) == 0 +} - every step in process_template_steps { - step.Enabled +result := {"allowed": true} if { + count(all_packages) > 0 + every pkg in all_packages { + pkg.GitRef == "refs/heads/main" } } - -process_template_steps := [step | - some step in input.Steps - step.Source.Type == "Process Template" - step.Source.SlugOrId == "" -] ``` -### Check that a Process Template is at the beginning or end of a process +### Require runbook runs to come from the main branch -```ruby -package process_template_location_check +Blocks runbook runs where the runbook was published from a branch other than `main`. -default result := {"allowed": false} +:::hint +`Runbook` is only present for runbook runs. Use `input.Runbook` in your scope. +::: -# Process Template is at the start -result := {"allowed": true} if { - input.Steps[0].Source.Type == "Process Template" - input.Steps[0].Source.SlugOrId == "" +**Scope:** + +```rego +package runbook_from_main_branch + +default evaluate := false + +evaluate if { + input.Runbook } +``` + +**Conditions:** + +```rego +package runbook_from_main_branch + +default result := {"allowed": false, "action": "warn"} -# Process Template is at the end result := {"allowed": true} if { - input.Steps[count(input.Steps)-1].Source.Type == "Process Template" - input.Steps[count(input.Steps)-1].Source.SlugOrId == "" + input.Runbook.GitRef == "refs/heads/main" } ``` -### Check that a Process Template is of a certain version when deployments occur -Process template can include multiple steps +--- + +## Enforce process structure -```ruby -package process_template_with_version_is_executed +Use these examples when you want to ensure deployments and runbook runs follow a predictable structure, with steps running in the right sequence, nothing running in parallel, and specific steps appearing at known positions in the process. -default result := {"allowed": false} +### Prevent parallel execution -result := {"allowed": true} if { - count(process_template_steps) > 0 +Blocks any deployment or runbook run where steps are configured to run at the same time. Useful for environments where parallel execution makes troubleshooting difficult or violates compliance requirements. - every step in process_template_steps { - semver.compare(step.Source.Version, "") == 0 - step.Enabled - not step.Id in input.SkippedSteps +**Conditions:** + +```rego +package no_parallel_steps + +default result := {"allowed": false, "action": "warn"} + +result := {"allowed": true} if { + every execution in input.Execution { + execution.StartTrigger != "StartWithPrevious" } } - -process_template_steps := [step | - some step in input.Steps - step.Source.Type == "Process Template" - step.Source.SlugOrId == "" -] ``` -### Check that a Process Template exists before or after certain steps +### Require a step to run first or last + +Use this to ensure a specific step, such as a pre-flight check or a notification, always appears at the start or end of the process. -```ruby -package process_template_step_ordering +**Conditions:** -default result := {"allowed": false} +```rego +package step_at_boundary -# Process Template exists before a specific step +default result := {"allowed": false, "action": "warn"} + +# Step runs first result := {"allowed": true} if { - some i, step in input.Steps - step.Source.Type == "Process Template" - step.Source.SlugOrId == "" - some j, target_step in input.Steps - target_step.Source.SlugOrId == "" - i < j + input.Steps[0].Source.SlugOrId == "" } -# Process Template exists after a specific step +# Step runs last result := {"allowed": true} if { - some i, step in input.Steps - step.Source.Type == "Process Template" - step.Source.SlugOrId == "" - some j, target_step in input.Steps - target_step.Source.SlugOrId == "" - i > j + input.Steps[count(input.Steps)-1].Source.SlugOrId == "" } ``` -### Check if a built-in step happens before another built-in step +### Require a process template to run first or last + +Similar to the example above, but for a process template that contributes multiple steps. + +**Conditions:** -```ruby -package builtin_step_before_builtin +```rego +package process_template_at_boundary -default result := {"allowed": false} +default result := {"allowed": false, "action": "warn"} +# Process template is first result := {"allowed": true} if { - some i, first_step in input.Steps - first_step.ActionType == "" - some j, second_step in input.Steps - second_step.ActionType == "" - i < j + input.Steps[0].Source.Type == "Process Template" + input.Steps[0].Source.SlugOrId == "" +} + +# Process template is last +result := {"allowed": true} if { + input.Steps[count(input.Steps)-1].Source.Type == "Process Template" + input.Steps[count(input.Steps)-1].Source.SlugOrId == "" } ``` -### Check if a built-in step happens after another built-in step +### Require a process template to run before or after a specific step + +Use this when relative ordering matters. For example, a security scan process template must always run before a deployment step. -```ruby -package builtin_step_after_builtin +**Conditions:** -default result := {"allowed": false} +```rego +package process_template_ordering +default result := {"allowed": false, "action": "warn"} + +# Process template runs before the target step result := {"allowed": true} if { - some i, first_step in input.Steps - first_step.ActionType == "" - some j, second_step in input.Steps - second_step.ActionType == "" + some i, template_step in input.Steps + template_step.Source.Type == "Process Template" + template_step.Source.SlugOrId == "" + some j, target_step in input.Steps + target_step.Source.SlugOrId == "" + i < j +} + +# Process template runs after the target step +result := {"allowed": true} if { + some i, template_step in input.Steps + template_step.Source.Type == "Process Template" + template_step.Source.SlugOrId == "" + some j, target_step in input.Steps + target_step.Source.SlugOrId == "" i > j } ``` -### Check if a custom step template happens before a built-in step +### Require a step template to run before or after a built-in step + +Use this when you need to enforce ordering between a custom step template and a built-in action type. -```ruby -package step_template_before_builtin +**Conditions:** -default result := {"allowed": false} +```rego +package step_template_and_builtin_ordering +default result := {"allowed": false, "action": "warn"} + +# Step template runs before the built-in step result := {"allowed": true} if { some i, template_step in input.Steps template_step.Source.Type == "Step Template" @@ -382,15 +502,8 @@ result := {"allowed": true} if { builtin_step.ActionType == "" i < j } -``` - -### Check if a custom step template happens after a built-in step - -```ruby -package step_template_after_builtin - -default result := {"allowed": false} +# Step template runs after the built-in step result := {"allowed": true} if { some i, template_step in input.Steps template_step.Source.Type == "Step Template" @@ -401,164 +514,156 @@ result := {"allowed": true} if { } ``` -### Check that a deployment contains a manual intervention step +### Require ordering between two built-in steps -```ruby -package manualintervention +Use this when two built-in action types must always run in a specific sequence. -default result := {"allowed": false} +**Conditions:** -result := {"allowed": true} if { - some step in input.Steps - step.ActionType == "Octopus.Manual" - not manual_intervention_skipped -} +```rego +package builtin_step_ordering -result := {"allowed": false, "reason": "Manual intervention step cannot be skipped in production environment"} if { - manual_intervention_skipped -} +default result := {"allowed": false, "action": "warn"} -manual_intervention_skipped if { - some step in input.Steps - step.Id in input.SkippedSteps - step.ActionType == "Octopus.Manual" +result := {"allowed": true} if { + some i, first_step in input.Steps + first_step.ActionType == "" + some j, second_step in input.Steps + second_step.ActionType == "" + i < j } ``` -### Check that a deployment have packages from main branch only +--- -```ruby -package packages_from_main_branch +## Enforce tagging standards -default result := {"allowed": true} +Use these examples when you want to ensure projects, tenants, or environments carry the tags your organisation uses to classify workloads. For example, to confirm a tenant has been assigned a support tier before a deployment can proceed. -all_packages := [pkg | some step in input.Steps; some pkg in step.Packages] +:::hint +`Tenant` is only present for tenanted deployments. Always check for `input.Tenant` before referencing its properties. If your policy evaluates both tenanted and non-tenanted deployments, add an existence check in your conditions. +::: -result := {"allowed": false} if { - count(all_packages) > 0 - some pkg in all_packages - pkg.GitRef != "refs/heads/main" -} -``` +### Require a project and tenant to carry tags from specific tag sets -### Check that no steps run in parallel +Blocks deployments where the tenant doesn't have a tag from the `size/` set, or the project doesn't have a tag from the `lang/` set. Adjust the tag prefixes to match your own tag sets. -```ruby -package no_parallel_steps +**Conditions:** + +```rego +package required_tags -default result := {"allowed": false} +default result := {"allowed": false, "action": "warn"} result := {"allowed": true} if { - # All steps should have StartAfterPrevious, not StartWithPrevious - every execution in input.Execution { - execution.StartTrigger != "StartWithPrevious" - } + tenant_has_size_tag + project_has_lang_tag +} + +tenant_has_size_tag if { + some tag in input.Tenant.Tags + startswith(tag, "size/") +} + +project_has_lang_tag if { + some tag in input.Project.Tags + startswith(tag, "lang/") } ``` -### Check that a release version is greater than required minimum +--- -This policy will block deployments in production environments, but allow deployments with warnings in other environments. The violation action for this policy has been set to `warn` as a default. +## Scoping examples -```ruby -package specific_release_version +Every policy evaluates all deployments and runbook runs by default. Use scope to limit evaluation to the specific executions your policy is relevant to. -default result := {"allowed": false} +### Scope to a specific space, environment, or project -result := {"allowed": false, "action": "block"} if { - production - version_less_than_required -} +```rego +package scope_example -result := {"allowed": false} if { - not production - version_less_than_required -} +default evaluate := false -result := {"allowed": true} if { - not version_less_than_required +evaluate if { + # Match by slug (recommended), name, or ID + # Use a list to match multiple values: input.Space.Slug in ["slug1", "slug2"] + input.Space.Slug == "" + input.Environment.Slug == "" + input.Project.Slug == "" } +``` -production if { - startswith(input.Environment.Slug, "prod") -} +### Scope to all projects except specific ones -version_less_than_required if { - semver.compare(input.Release.Version, "1.0.0") < 0 +```rego +package scope_example + +default evaluate := true + +evaluate := false if { + input.Project.Slug in ["", ""] } ``` -### Check that release is based on the main branch +### Scope to deployments only -```ruby -package main_branch_release +```rego +package scope_example -default result := {"allowed": false} +default evaluate := false -result := {"allowed": true} if { - input.Release.GitRef == "refs/heads/main" +evaluate if { + not input.Runbook } ``` -### Check that runbook is from the main branch +### Scope to runbook runs only -```ruby -package main_branch_runbook +```rego +package scope_example -default result := {"allowed": false} +default evaluate := false -result := {"allowed": true} if { - input.Runbook.GitRef == "refs/heads/main" +evaluate if { + input.Runbook } ``` -### Check that the project and tenant have a tag from the specified tag set +### Scope to a specific runbook -Example of a policy that checks tags for Environments, Tenants and Projects. - -```ruby -package tags +```rego +package scope_example -default result := {"allowed": false} +default evaluate := false -result := {"allowed": true} if { - has_size_tags - has_lang_tags -} - -has_size_tags if { - some tag in input.Tenant.Tags - startswith(tag, "size/") -} - -has_lang_tags if { - some tag in input.Project.Tags - startswith(tag, "lang/") +evaluate if { + input.Runbook + input.Runbook.Id == "" } ``` +--- + ## Writing policies as OCL files -If you prefer to write policies directly as OCL files in your Git repository instead of using the UI editor, you can create `.ocl` files in the `policies` folder of your Platform Hub Git repository. +If you prefer to write policies directly in your Git repository instead of using the policy editor, create `.ocl` files in the `policies` folder of your Platform Hub repository. ### OCL file format -The OCL file format wraps the Rego code with metadata about the policy. Here's the structure: - -```ruby +```ocl name = "Policy Name" description = "Policy description" -violation_reason = "Custom message shown when policy fails" -violation_action = "warn" or "block" +violation_reason = "Custom message shown when the policy fails" +violation_action = "warn" scope { rego = <<-EOT package policy_file_name - + default evaluate := false - + evaluate if { - # Your scope conditions here + # Your scope Rego here } EOT } @@ -566,20 +671,19 @@ scope { conditions { rego = <<-EOT package policy_file_name - - default result := {"allowed": false} - + + default result := {"allowed": false, "action": "warn"} + result := {"allowed": true} if { - # Your policy conditions here + # Your conditions Rego here } EOT } ``` -### Important notes for OCL files - -- The file name must match the package name in your Rego code (e.g., `checkformanualintervention.ocl` requires `package checkformanualintervention`) -- You cannot use dashes in your policy file name -- The package name must be identical in both the scope and conditions sections -- You must include the `package` declaration in the Rego code when using OCL files +### Rules for OCL files +- The filename must match the package name in your Rego. For example, `checkformanualintervention.ocl` requires `package checkformanualintervention`. +- You can't use dashes in the filename. +- The package name must be identical in both the scope and conditions sections. +- You must include the `package` declaration in both Rego blocks. diff --git a/src/pages/docs/platform-hub/policies/index.md b/src/pages/docs/platform-hub/policies/index.md index 70eabd0dc9..59071e4a81 100644 --- a/src/pages/docs/platform-hub/policies/index.md +++ b/src/pages/docs/platform-hub/policies/index.md @@ -1,121 +1,94 @@ --- layout: src/layouts/Default.astro pubDate: 2025-09-11 -modDate: 2025-11-25 +modDate: 2025-09-11 title: Policies -subtitle: An overview of Policies +subtitle: Enforce deployment standards automatically across your Octopus instance icon: fa-solid fa-lock navTitle: Getting started navSection: Policies -description: Policies let you enforce standards across your Octopus instance with ease. +description: Use policies to enforce deployment standards, block non-compliant executions, and maintain governance across your Octopus instance. navOrder: 160 --- -Policies in Octopus are designed to ensure compliance and governance by default, making it easier to enforce deployment controls at scale. This approach allows you to shift compliance left, alleviating the burden of manual audits and enabling you to maintain high standards across your organization. With policies, you can enforce organization-wide compliance across teams and regions, moving governance out of Confluence docs and Slack threads and into the heart of your delivery pipeline. +Policies let you enforce deployment standards automatically. You write rules in Rego, and Octopus evaluates every deployment and runbook run against those rules before it executes. Non-compliant executions are blocked or flagged, and every evaluation is logged to the audit trail. -Using Rego, you can write custom policy checks that align with your requirements, block non-compliant deployments, and access detailed audit logs of policy evaluation events. This method ensures compliance is not an afterthought; it is embedded within every deployment pipeline, providing a seamless and efficient way to uphold governance standards across all activities. +All policies are stored as OCL files in your Platform Hub repository. If your Platform Hub repository isn't set up yet, see [Platform Hub](/docs/platform-hub/) before continuing. -## When to use Policies +## What you can enforce -Policies streamline the enforcement of standards across all deployments by automating compliance checks and governance measures. +When a deployment or runbook run starts, Octopus passes a structured input object to the policy engine. Your Rego conditions read from that object to decide whether the execution should proceed. -Consider implementing policies if: +Common things teams enforce with policies: -- You want to ensure that every deployment conforms to predefined standards without manual effort. -- You wish to manage these standards centrally, allowing for consistent application across your organization and easy updating of standards. +- A manual intervention or approval step must be present in production deployments +- All packages must come from an approved branch +- Specific steps must not be skipped or disabled +- Steps must run in a particular order +- Projects and tenants must carry required tags -While policies may not be necessary in every deployment scenario, they are invaluable if maintaining compliance and security is a priority. By embedding policies into your deployments, you can minimize risks and ensure that all teams are aligned with your organizational standards. +By default, a policy applies to both deployments and runbook runs. You can scope a policy to one or the other in your Rego. For the full list of input fields available, see the [policy input schema](/docs/platform-hub/policies/schema). -## What can you enforce with policies? +If you want to jump straight to working Rego, see the [examples page](/docs/platform-hub/policies/examples). For a full guide to the Rego language, see the [OPA documentation](https://www.openpolicyagent.org/docs/policy-language). -Policies give you the flexibility to enforce virtually any standard across your deployments and runbook runs. When an execution starts, Octopus provides detailed information about the deployment or runbook run to the policy engine, allowing you to evaluate it against your requirements. +## Build your first policy -Common use cases include: +The steps below walk through creating a policy that requires a manual intervention step in every deployment going to the Development environment for the ACME project. The walkthrough uses a non-production environment and sets the violation action to warn so you can safely verify the policy is working before extending it to production. -- Requiring specific steps (like manual interventions or approvals) in production deployments -- Ensuring all packages come from approved branches -- Validating that certain steps aren't skipped or disabled -- Enforcing step ordering requirements -- Checking that deployments meet environment-specific criteria -- Verifying projects and tenants have required tags +### 1. Create a policy -By default, policies scope to both deployment processes and runbook runs unless you specify otherwise. - -## Getting started - -All policies are written in Rego and saved as an OCL file under a policies folder in your Platform Hub repository. If you need to setup your Platform Hub repository see [Platform Hub](/docs/platform-hub/). For a comprehensive guide to Rego, please visit the official [documentation.](https://www.openpolicyagent.org/docs/policy-language) If you would like to jump straight to examples that are more representative of the deployment scenario you want to enforce, please visit our [examples page](/docs/platform-hub/policies/examples). - -In our example below, we are writing a policy that checks for the existence of a manual intervention step whenever deployments go to production. - -## Building your first policy - -### 1. Create your policies file - -To get started, navigate to the Platform Hub inside of your Octopus instance and click on the Policies section. To create your first policy click the `Create Policy` button. +In your Octopus instance, go to Platform Hub and select **Policies**. Click **Create Policy**. :::figure -![A empty policies list in the Platform Hub](/docs/img/platform-hub/policies/policies-getting-started.png) +![An empty policies list in the Platform Hub](/docs/img/platform-hub/policies/policies-getting-started.png) ::: ### 2. Select a starter policy -You will be presented with the Create Policy modal. The first step is to select a starter policy to base your new policy on. To continue click the `Next` button. +Choose a starter policy to base your new policy on, then click **Next**. :::figure ![A modal to select a starter policy](/docs/img/platform-hub/policies/policies-create-starter-modal.png) ::: -:::div{.hint} - -If you want to start with the most basic policy, choose Create Blank Policy. - +:::hint +To start with a blank slate, choose **Create Blank Policy**. ::: -### 3. Give your policy a name +### 3. Name your policy -You can then set the name of your policy. Octopus will generate a valid slug for your policy based on the name you provide. You can edit this slug before clicking the `Done` button. +Enter a name for your policy. Octopus generates a slug from the name you provide. You can edit the slug before clicking **Done**. :::figure ![A modal to create a new policy](/docs/img/platform-hub/policies/policies-create-modal.png) ::: -:::div{.hint} - -The slug can not be changed once a policy is created. - +:::hint +The slug can't be changed after the policy is created. It becomes the package name in your Rego code and the filename of the OCL file in your repository. ::: -### 4. Update your policy details +### 4. Fill in the policy details -This will create the Policy file in your Platform Hub repository and then take you to the edit Policy page, where you can update the following details for your policy. +Fill in the following fields: -- **Name** - a short, memorable, unique name for this policy. -- **Description** - an optional description. -- **Violation Reason** - a custom message provided to users when they fail to meet the conditions of a policy. -- **Violation Action** - determines what happens when a deployment or runbook run doesn't comply with the policy. -- **Scope Rego** - Rego to scope whether a policy should be evaluated for a particular deployment or runbook run. -- **Conditions Rego** - Rego to determine the rules that a deployment or runbook run will be evaluated against. +- **Name:** a short, memorable name for this policy. +- **Description:** an optional summary of what the policy enforces. +- **Violation reason:** the message shown to users when a deployment or runbook run fails this policy. Make this specific enough for users to understand what they need to fix. +- **Violation action:** whether a failing policy blocks the execution or raises a warning. This default can be overridden per rule in your conditions Rego using the `action` property. :::figure ![The form used to edit a policy](/docs/img/platform-hub/policies/policies-edit-getting-started.png) ::: -:::div{.hint} - -- ```violation_reason``` can be overridden by the value of the ```reason``` property defined in the output result of the conditions Rego code. -- ```violation_action``` can be overridden by the value of the ```action``` property defined in the output result of the conditions Rego code. - -See - +:::hint +The values for both Violation Reason and Violation Action can be overridden by the `reason` and `action` properties returned in your conditions Rego result. See the [output schema](/docs/platform-hub/policies/schema#output-schema) for details. ::: -### 5. Define the policy scope +### 5. Write your scope -You’ll now need to define the policy's scope, as Rego. Octopus will provide data about your deployments to the policy engine to use during evaluation. When you are writing your Rego code for scoping or conditions, this input data is available under the value ```input.VALUE```. This scope section of the policy defines the package name, which must match the underlying .ocl file name the policy is stored in. By default, the policy evaluates to false. The scope will evaluate to true if the deployment is going to the Production environment, for the ACME project, and in the Default space - all three conditions must be true at the same time. +The Scope Rego determines which deployments and runbook runs this policy evaluates. -
- -For example, Octopus provides the environment details that you are deploying to. +Octopus passes the input object for every execution to the policy engine. In your Rego, this is available as `input`. For example, the environment being deployed to looks like this: ```json { @@ -123,70 +96,65 @@ For example, Octopus provides the environment details that you are deploying to. "Id": "Environments-1", "Name": "Development", "Slug": "development", - "Tags": ["country/australia", "animal/octopus"] + "Tags": ["region/us-east"] } } ``` -To use the environment name in your Rego, you would add the following: +To reference the environment name in Rego: -```json -input.Environment.Name = "Development" +```rego +input.Environment.Name == "Development" ``` -Our example applies only to deployments and runbook runs to the production environment for the ACME project, in the default space. **All Rego code has to have a package defined, which is the policy slug.** +For our example, the policy only evaluates deployments going to the Development environment, for the ACME project, in the Default space. All three conditions must be true at once. -:::div{.warning} - -- You cannot rename **evaluate**, it must be called **evaluate**. -- The package name must be the same as your policy file name. - -::: - -```ruby -package manual_intervention_required +```rego +package manual_intervention_required default evaluate := false -evaluate := true if { - input.Environment.Name == "Production" + +evaluate if { + input.Environment.Name == "Development" input.Project.Name == "ACME" input.Space.Name == "Default" } ``` -### 6. Define the policy conditions +:::hint -After defining your scope, you must specify the policy rules. These rules are written in Rego. Octopus will check the results of your Rego code to determine if a deployment complies with the policy. The result should contain a composite value with the properties **allowed** and an optional **reason** and **action**. In this example, we will set the default rule result to be non-compliant. Any deployment that does not meet the policy rules will be prevented from executing. This conditions section of the policy defines the package name, which must match the slug for your policy. By default, the policy evaluates to false. The condition will evaluate to true if the deployment contains the required steps. +- The scope rule must be named `evaluate`. You can't rename it. +- The package name must match your policy slug exactly. -:::div{.warning} +::: -- You cannot rename **result**, it must be called **result**. -- The package name must be the same as your policy file name. +For more scoping patterns, including scoping to tenants, project groups, or runbook runs only, see the [scoping examples](/docs/platform-hub/policies/examples#scoping-examples). -::: +### 6. Write your conditions -```ruby -package manual_intervention_required +The conditions define the rules a deployment or runbook run must meet. Octopus reads the `result` object your Rego returns to decide whether to allow or block the execution. -default result := {"allowed": false} -``` +Start by setting the default result. Because we're still testing this policy, set the `action` to `warn` rather than `block`. A warning lets the execution proceed but records the violation in the task log, dashboard, and audit log, so you can confirm the policy is evaluating correctly without risking broken deployments. -:::div{.info} -Full details on the data available for policy scoping and conditions can be found under the [schema page](/docs/platform-hub/policies/schema). -::: +:::warning + +- The result rule must be named `result`. You can't rename it. +- The package name must match your policy slug exactly. -
+::: -### 7. Check for a deployment step +```rego +package manual_intervention_required -After you’ve set the default state, you’ll need to define the policy rules that will update the **result** state to be true so the deployment can execute. In this example, the deployment must contain at least one manual intervention step. We can do this by checking the step.ActionType is “Octopus.Manual” +default result := {"allowed": false, "action": "warn"} +``` -
+Then add a rule that sets `result` to allowed when the conditions are met. In this example, the deployment must contain at least one manual intervention step: -```ruby -package manual_intervention_required +```rego +package manual_intervention_required -default result := {"allowed": false} +default result := {"allowed": false, "action": "warn"} result := {"allowed": true} if { some step in input.Steps @@ -194,89 +162,66 @@ result := {"allowed": true} if { } ``` -
+Once you've confirmed the policy is evaluating correctly, change `"action": "warn"` to `"action": "block"` and update the scope to cover your production environment. -After your policy details have been finalized you will need to commit, publish and activate your policy for it to be available for evaluation. +For the full list of input fields you can reference in your conditions, see the [policy input schema](/docs/platform-hub/policies/schema). -### 8. Saving a Policy +### 7. Save your policy -Once you've finished making changes to your policy you can commit them to save the changes to your Git repository. You can either **Commit** with a description or quick commit without one. +Once you've finished writing your Rego, commit your changes to save them to your Git repository. You can commit with a description or use quick commit without one. :::figure ![The commit experience for a policy](/docs/img/platform-hub/policies/policies-commit-experience.png) ::: -### 9. Publishing a Policy +### 8. Publish your policy -Once you've made your changes, you will have to publish the policy to reflect the changes you've made. You will have three options to choose from when publishing changes: +After committing, publish the policy to make the changes take effect. Choose the appropriate version type: -- Major changes (breaking) -- Minor changes (non-breaking) -- Patch (bug fixes) +- **Major:** breaking changes +- **Minor:** non-breaking changes +- **Patch:** bug fixes -:::div{.hint} -The first time you publish a policy you can only publish a major version +:::hint +The first time you publish a policy, you can only publish a major version. ::: :::figure ![Publish experience for a policy](/docs/img/platform-hub/policies/policies-publishing.png) ::: -### 10. Activating a policy +### 9. Activate your policy -You must activate the policy before it can be evaluated. Policies can be deactivated after they are activated to stop a policy from being evaluated. +Published policies must be activated before Octopus evaluates them. You can deactivate a policy at any time to stop it from being evaluated without deleting it. -:::div{.hint} -Activation settings can be updated anytime, from the Versions tab on the edit policy page +:::hint +Activation settings can be updated at any time from the **Versions** tab on the edit policy page. ::: :::figure ![Activation status for a policy](/docs/img/platform-hub/policies/policies-activation.png) ::: -### 11. Finalize and test your policy - -You’ve now defined a basic policy to ensure a manual intervention step is present when deploying to any environment. You can test this policy by customizing the values in the scope block, and then deploying to an environment. If you choose not to include the manual intervention step in your process, you will see errors in the task log and project dashboards when you try to run the deployment. All policy evaluations will appear in the Audit log (**Configuration** → **Audit**) with the “Compliance Policy Evaluated” event group filter applied. Audit logs and Server Tasks will only appear for deployments within the policy's scope. - -
- -:::div{.hint} - -- If you wish to see more comprehensive examples for other deployment scenarios, please visit the [examples page](/docs/platform-hub/policies/examples). -- If you wish to see the schema of inputs available for policies, please visit the [schemas page](/docs/platform-hub/policies/schema). - -::: - -## Policy evaluation information - -If you want to see what information was provided to the policy engine when it evaluates a deployment, you can do so in the task log. Every deployment, whether they succeeded or failed due to a policy evaluation, will show information in the following places: +### 10. Test your policy -1. Task logs +With the policy active, trigger a deployment to the Development environment for the ACME project. Because the violation action is set to `warn`, the deployment will proceed regardless. If the manual intervention step is missing, Octopus records the violation in the task log and project dashboard. Check both to confirm the policy is evaluating as expected. - :::figure - ![The task logs showing policy audit records](/docs/img/platform-hub/policies-task-log.png) - ::: +All policy evaluations appear in the audit log under **Configuration** > **Audit**, filtered by the **Compliance Policy Evaluated** event group. Audit log entries and server tasks only appear for executions in the policy's scope. -
+To see the full input object that was passed to the policy engine for a specific execution, turn on the verbose option in the task log. This is useful for checking whether a policy is evaluating the right fields. -2. Project dashboards +Once you're satisfied the policy is working correctly, you can: - :::figure - ![Dashboards showing policy errors](/docs/img/platform-hub/policies-dashboard-notification.png) - ::: +1. Change `"action": "warn"` to `"action": "block"` in your conditions Rego. +2. Update the scope to include your production environment. +3. Commit, publish, and re-activate the policy. -
+See [Troubleshooting policies](/docs/platform-hub/policies/troubleshooting) if evaluations aren't appearing as expected. -3. Audit records - - :::figure - ![Audit log containing policy evaluation records](/docs/img/platform-hub/policies-audit-log.png) - ::: - -
+--- -You can see what information was evaluated at the time of policy evaluation by using the verbose option in the task logs. This is useful if you want to troubleshoot a policy and see if it is evaluating deployments correctly. +## What's next -:::figure -![Verbose options shown in task logs](/docs/img/platform-hub/policies-verbose-task-log.png) -::: +- [Policy examples](/docs/platform-hub/policies/examples): ready-to-use Rego for common enforcement scenarios +- [Policy input schema](/docs/platform-hub/policies/schema): the full reference for fields available in your Rego +- [Best practices](/docs/platform-hub/policies/best-practices): guidance on naming, rollout, and writing reliable policies diff --git a/src/pages/docs/platform-hub/policies/schema.md b/src/pages/docs/platform-hub/policies/schema.md index bc7aa3c1a9..e74983eeea 100644 --- a/src/pages/docs/platform-hub/policies/schema.md +++ b/src/pages/docs/platform-hub/policies/schema.md @@ -2,18 +2,308 @@ layout: src/layouts/Default.astro pubDate: 2025-09-11 modDate: 2025-09-11 -title: Schema for Policies -subtitle: A list of the inputs that are provided to the policy engine +title: Policy input schema +subtitle: The data available to your policy engine when evaluating deployments and runbook runs icon: fa-solid fa-lock -navTitle: Schema for policies +navTitle: Policy input schema navSection: Policies -description: Schema for policies +description: A reference for the input schema passed to the policy engine, including field descriptions, conditional fields, and example patterns. navOrder: 162 --- -## Input Schema +When Octopus evaluates a policy, it passes a structured input object to the policy engine. Your Rego conditions read from this object using `input.` to make decisions about whether a deployment or runbook run should be allowed to proceed. -Octopus has a set number of inputs that are provided to evaluate policies against deployments. The following is the full schema that is passed into the engine to evaluate deployments: +This page describes every field available in that input object, explains which fields are always present and which are conditional, and shows common patterns for using them. For complete working examples, see [Policy examples](/docs/platform-hub/policies/examples). + +## What's in the input object + +The table below summarizes every top-level field available to your policies. + +| Field | Type | Always present? | Description | +| --- | --- | --- | --- | +| `Environment` | object | Yes | The environment the deployment or runbook run is targeting | +| `Project` | object | Yes | The project being deployed | +| `Space` | object | Yes | The space the deployment belongs to | +| `ProjectGroup` | object | Yes | The project group the project belongs to | +| `Steps` | array | Yes | All steps included in the deployment process | +| `SkippedSteps` | array | Yes | IDs of any steps excluded from this deployment | +| `Execution` | array | Yes | Execution order and parallelism settings for each step | +| `Tenant` | object | **No** | Present only for tenanted deployments | +| `Release` | object | **No** | Present only for deployments (not runbook runs) | +| `Runbook` | object | **No** | Present only for runbook runs (not deployments) | + +:::hint +Because `Tenant`, `Release`, and `Runbook` are conditionally present, always check for their existence before referencing them in your conditions. For example, use `input.Runbook` to check the field exists before accessing `input.Runbook.GitRef`. See [Scoping to deployments or runbook runs](#scoping-to-deployments-or-runbook-runs) below. +::: + +## Input fields + +### Environment + +The environment the deployment or runbook run is targeting. + +| Property | Type | Description | +| --- | --- | --- | +| `Id` | string | The unique identifier for the environment | +| `Name` | string | The display name of the environment | +| `Slug` | string | The URL-safe slug for the environment | +| `Tags` | array of strings | Tags applied to the environment | + +**Example usage:** + +```rego +# Match environments whose slug starts with "prod" +production if { + startswith(input.Environment.Slug, "prod") +} + +# Match environments with a specific tag +input.Environment.Tags[_] == "regulated" +``` + +### Project + +The project being deployed. + +| Property | Type | Description | +| --- | --- | --- | +| `Id` | string | The unique identifier for the project | +| `Name` | string | The display name of the project | +| `Slug` | string | The URL-safe slug for the project | +| `Tags` | array of strings | Tags applied to the project | + +### Space + +The Octopus space the deployment belongs to. + +| Property | Type | Description | +| --- | --- | --- | +| `Id` | string | The unique identifier for the space | +| `Name` | string | The display name of the space | +| `Slug` | string | The URL-safe slug for the space | + +### ProjectGroup + +The project group the project belongs to. + +| Property | Type | Description | +| --- | --- | --- | +| `Id` | string | The unique identifier for the project group | +| `Name` | string | The display name of the project group | +| `Slug` | string | The URL-safe slug for the project group | + +### Tenant + +The tenant for tenanted deployments. **This field is absent for non-tenanted deployments.** Always guard against its absence before using it. + +| Property | Type | Description | +| --- | --- | --- | +| `Id` | string | The unique identifier for the tenant | +| `Name` | string | The display name of the tenant | +| `Slug` | string | The URL-safe slug for the tenant | +| `Tags` | array of strings | Tags applied to the tenant | + +**Example usage:** + +```rego +# Only evaluate for tenanted deployments +evaluate if { + input.Tenant +} + +# Check a tag on the tenant +has_required_tag if { + some tag in input.Tenant.Tags + startswith(tag, "tier/") +} +``` + +### Steps and SkippedSteps + +`Steps` contains every step in the deployment process. `SkippedSteps` contains the IDs of any steps the person creating the deployment chose to exclude. + +These two fields work together. A step that's skipped still appears in `Steps`, but its ID is also added to `SkippedSteps`. This means policies that enforce required steps need to check both fields. + +**Steps array, each item contains:** + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `Id` | string | Yes | The unique identifier for the step | +| `Slug` | string | Yes | The URL-safe slug for the step | +| `ActionType` | string | Yes | The built-in action type (e.g. `Octopus.Manual`, `Octopus.Script`) | +| `Enabled` | boolean | Yes | Whether the step is enabled in the process | +| `IsRequired` | boolean | Yes | Whether the step has been marked as required | +| `Source` | object | Yes | Where the step comes from. See the Source object below | +| `Packages` | array | No | Packages referenced by this step | + +**Source object:** + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `Type` | string | Yes | The source type. Valid values: `"Step Template"`, `"Process Template"` | +| `SlugOrId` | string | Yes | The slug or ID of the step or process template | +| `Version` | string | No | The pinned version, if one is set | + +**Packages array, each item contains:** + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `Id` | string | Yes | The unique identifier for the package reference | +| `Name` | string | Yes | The name of the package | +| `Version` | string | No | The resolved package version | +| `GitRef` | string | No | The Git reference the package was built from | + +**Example usage:** + +```rego +# Check that no steps are skipped +result := {"allowed": true} if { + count(input.SkippedSteps) == 0 +} + +# Check a specific step template is present and not skipped +result := {"allowed": true} if { + some step in input.Steps + step.Source.Type == "Step Template" + step.Source.SlugOrId == "" + not step.Id in input.SkippedSteps + step.Enabled == true +} +``` + +See the [steps and skipping examples](/docs/platform-hub/policies/examples#check-that-a-step-isnt-skipped-in-a-deployment) for more patterns. + +### Execution + +The `Execution` array describes the order steps run in and how each step relates to the previous one. Use this field to enforce rules about parallelism or step sequencing. + +Each item in the array contains: + +| Property | Type | Description | +| --- | --- | --- | +| `StartTrigger` | string | How this step starts. `"StartAfterPrevious"` runs sequentially; `"StartWithPrevious"` runs in parallel with the previous step | +| `Steps` | array of strings | The IDs of the steps in this execution group | + +**Example usage:** + +```rego +# Prevent any steps from running in parallel +result := {"allowed": true} if { + every execution in input.Execution { + execution.StartTrigger != "StartWithPrevious" + } +} +``` + +### Release + +Details about the release being deployed. **This field is absent for runbook runs.** + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `Id` | string | Yes | The unique identifier for the release | +| `Name` | string | Yes | The release name | +| `Version` | string | Yes | The release version string | +| `GitRef` | string | No | The Git reference the release was created from | + +**Example usage:** + +```rego +# Only allow releases from the main branch +result := {"allowed": true} if { + input.Release.GitRef == "refs/heads/main" +} + +# Block releases below a minimum version in production +result := {"allowed": false, "action": "block"} if { + production + semver.compare(input.Release.Version, "1.0.0") < 0 +} +``` + +See the [release version examples](/docs/platform-hub/policies/examples#check-that-a-release-version-is-greater-than-required-minimum) for more patterns. + +### Runbook + +Details about the runbook run. **This field is absent for deployments.** + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `Id` | string | Yes | The unique identifier for the runbook | +| `Name` | string | Yes | The display name of the runbook | +| `Snapshot` | string | Yes | The snapshot name used for this run | +| `GitRef` | string | No | The Git reference the runbook was published from | + +**Example usage:** + +```rego +# Scope a policy to runbook runs only +evaluate if { + input.Runbook +} + +# Only allow runbook runs published from main +result := {"allowed": true} if { + input.Runbook.GitRef == "refs/heads/main" +} +``` + +--- + +## Scoping to deployments or runbook runs + +Because `Release` and `Runbook` are mutually exclusive, you can use them to scope a policy to one type of execution or the other. + +```rego +# Deployments only +evaluate if { + not input.Runbook +} + +# Runbook runs only +evaluate if { + input.Runbook +} +``` + +--- + +## Output schema + +Your Rego conditions must define a `result` object that Octopus reads to determine what to do. The `result` object supports three properties: + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `allowed` | boolean | Yes | Whether the policy permits the deployment to proceed | +| `reason` | string | No | A message shown to the user when the policy fails | +| `action` | string | No | Overrides the violation action. Valid values: `"block"` or `"warn"` | + +The `action` field lets individual policy rules override the default violation action set on the policy. This is useful when you want a single policy to block in production but warn in other environments. + +**Example:** + +```rego +# Block in production, warn elsewhere +result := {"allowed": false, "action": "block"} if { + production + version_less_than_required +} + +result := {"allowed": false} if { + not production + version_less_than_required +} + +result := {"allowed": true} if { + not version_less_than_required +} +``` + +--- + +## Full input schema reference + +The complete JSON schema for the policy input object is provided below for use with schema validation tools or IDE integrations. ```json { @@ -24,308 +314,123 @@ Octopus has a set number of inputs that are provided to evaluate policies agains "Environment": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Slug": { - "type": "string" - }, - "Tags": { - "type": "array", - "items": { - "type": [ - "string" - ] - } - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Slug": { "type": "string" }, + "Tags": { "type": "array", "items": { "type": "string" } } }, - "required": [ - "Id", - "Name", - "Slug", - "Tags" - ] + "required": ["Id", "Name", "Slug", "Tags"] }, "Project": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Slug": { - "type": "string" - }, - "Tags": { - "type": "array", - "items": { - "type": [ - "string" - ] - } - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Slug": { "type": "string" }, + "Tags": { "type": "array", "items": { "type": "string" } } }, - "required": [ - "Id", - "Name", - "Slug", - "Tags" - ] + "required": ["Id", "Name", "Slug", "Tags"] }, "Space": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Slug": { - "type": "string" - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Slug": { "type": "string" } }, - "required": [ - "Id", - "Name", - "Slug" - ] + "required": ["Id", "Name", "Slug"] }, "Tenant": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Slug": { - "type": "string" - }, - "Tags": { - "type": "array", - "items": { - "type": [ - "string" - ] - } - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Slug": { "type": "string" }, + "Tags": { "type": "array", "items": { "type": "string" } } }, - "required": [ - "Id", - "Name", - "Slug", - "Tags" - ] + "required": ["Id", "Name", "Slug", "Tags"] }, "ProjectGroup": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Slug": { - "type": "string" - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Slug": { "type": "string" } }, - "required": [ - "Id", - "Name", - "Slug" - ] + "required": ["Id", "Name", "Slug"] }, "SkippedSteps": { "type": "array", - "items": { - "type": [ - "string" - ] - } + "items": { "type": "string" } }, "Steps": { "type": "array", "items": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Slug": { - "type": "string" - }, - "ActionType": { - "type": "string" - }, - "Enabled": { - "type": "boolean" - }, - "IsRequired": { - "type": "boolean" - }, + "Id": { "type": "string" }, + "Slug": { "type": "string" }, + "ActionType": { "type": "string" }, + "Enabled": { "type": "boolean" }, + "IsRequired": { "type": "boolean" }, "Source": { "type": "object", "properties": { - "Type": { - "type": "string" - }, - "SlugOrId": { - "type": "string" - }, - "Version": { - "type": "string" - } + "Type": { "type": "string" }, + "SlugOrId": { "type": "string" }, + "Version": { "type": "string" } }, - "required": [ - "Type", - "SlugOrId" - ] + "required": ["Type", "SlugOrId"] }, "Packages": { "type": "array", "items": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Version": { - "type": "string" - }, - "GitRef": { - "type": "string" - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Version": { "type": "string" }, + "GitRef": { "type": "string" } }, - "required": [ - "Id", - "Name" - ] + "required": ["Id", "Name"] } } }, - "required": [ - "Id", - "Slug", - "ActionType", - "Enabled", - "IsRequired", - "Source" - ] + "required": ["Id", "Slug", "ActionType", "Enabled", "IsRequired", "Source"] } }, "Release": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Version": { - "type": "string" - }, - "GitRef": { - "type": "string" - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Version": { "type": "string" }, + "GitRef": { "type": "string" } }, - "required": [ - "Id", - "Name", - "Version" - ] + "required": ["Id", "Name", "Version"] }, "Runbook": { "type": "object", "properties": { - "Id": { - "type": "string" - }, - "Name": { - "type": "string" - }, - "Snapshot": { - "type": "string" - }, - "GitRef": { - "type": "string" - } + "Id": { "type": "string" }, + "Name": { "type": "string" }, + "Snapshot": { "type": "string" }, + "GitRef": { "type": "string" } }, - "required": [ - "Id", - "Name", - "Snapshot" - ] + "required": ["Id", "Name", "Snapshot"] }, "Execution": { "type": "array", "items": { - "type": [ - "object" - ], + "type": "object", "properties": { - "StartTrigger": { - "type": "string" - }, - "Steps": { - "type": "array", - "items": { - "type": [ - "string" - ] - } - } + "StartTrigger": { "type": "string" }, + "Steps": { "type": "array", "items": { "type": "string" } } }, - "required": [ - "StartTrigger", - "Steps" - ] + "required": ["StartTrigger", "Steps"] } } }, - "required": [ - "Environment", - "Project", - "Space", - "SkippedSteps", - "Steps", - "ProjectGroup", - "Execution" - ] -} -``` - -## Output Result Schema -Octopus expects the conditions Rego code to define a result object that confirms to the following schema: - -```json -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Policy Result Schema", - "type": "object", - "properties": { - "allowed": { - "type": "boolean" - }, - "reason": { - "type": "string" - }, - "action": { - "type": "string", - "enum": ["block", "warn"] - } - }, - "required": ["allowed"] + "required": ["Environment", "Project", "Space", "SkippedSteps", "Steps", "ProjectGroup", "Execution"] } ``` diff --git a/src/pages/docs/platform-hub/policies/troubleshooting.md b/src/pages/docs/platform-hub/policies/troubleshooting.md index 7f605bdf73..0c9e1e90c9 100644 --- a/src/pages/docs/platform-hub/policies/troubleshooting.md +++ b/src/pages/docs/platform-hub/policies/troubleshooting.md @@ -2,25 +2,143 @@ layout: src/layouts/Default.astro pubDate: 2025-11-25 modDate: 2025-11-25 -title: Troubleshooting -subtitle: Known issues that you may run into +title: Troubleshooting policies +subtitle: Diagnose and fix common issues with policy evaluation icon: fa-solid fa-layer-group navTitle: Troubleshooting navSection: Policies -description: Known issues and limitations for policies +description: How to diagnose and fix common issues with policies in Platform Hub. navOrder: 175 --- -## Troubleshooting common issues +This page covers how to read policy evaluation output, diagnose common problems, and fix known issues. If you're setting up your first policy, see the [getting started guide](/docs/platform-hub/policies) first. -You may run into known issues when using policies. We've put together this page to help you diagnose and fix common issues. +## Understanding evaluation output -### Windows Server missing dependency +Every time a policy is evaluated, Octopus records the result in three places. Use these to confirm a policy is working as expected, or to diagnose why a deployment or runbook run was blocked or flagged. -If you try to load or create a policy you might see the following error "The Compliance Policy engine failed to load. There may be missing dependencies on the machine hosting Octopus Server.". +### Task log + +The task log for every deployment or runbook run shows the result of each policy evaluation, including whether it passed or failed and the violation reason if it failed. + +:::figure +![The task log showing policy evaluation records](/docs/img/platform-hub/policies-task-log.png) +::: + +To see the full input object that was passed to the policy engine, turn on the verbose option in the task log. This shows you the exact data your Rego conditions evaluated against, which is useful when a policy isn't behaving as expected. + +:::figure +![Verbose options shown in task logs](/docs/img/platform-hub/policies-verbose-task-log.png) +::: + +### Project dashboard + +Policy violations appear on the project dashboard so teams can see at a glance whether a recent deployment or runbook run was flagged. + +:::figure +![Project dashboard showing policy violation notifications](/docs/img/platform-hub/policies-dashboard-notification.png) +::: + +### Audit log + +All policy evaluations are recorded in the audit log, including evaluations that passed. To view them, go to **Configuration** > **Audit** and filter by the **Compliance Policy Evaluated** event group. + +Audit log entries only appear for executions that fall in the policy's scope. If an execution isn't appearing in the audit log, check that the scope Rego is evaluating correctly for that execution. + +:::figure +![Audit log containing policy evaluation records](/docs/img/platform-hub/policies-audit-log.png) +::: + +--- + +## Common problems + +### Policy is not evaluating + +If a policy isn't appearing in the task log or audit log for an execution you expect it to cover, work through the following checks. + +**Check the policy is activated.** A published policy must be activated before Octopus evaluates it. Go to the **Versions** tab on the edit policy page and confirm the policy is active. + +**Check the scope Rego.** The scope determines which executions the policy evaluates. Open the policy editor and review your scope Rego. Use the verbose task log on a deployment you expect to be in scope to see what input fields were passed, and check whether your scope conditions would match. + +**Check the package name.** The package name in your scope and conditions Rego must exactly match your policy's slug. A mismatch will prevent the policy from evaluating. The slug is shown on the edit policy page. + +### Policy is blocking when it should warn + +If a policy is blocking deployments or runbook runs when you expect it to only warn, check the following. + +**Check the default result.** If your conditions Rego sets `default result := {"allowed": false}` without an `action` property, Octopus uses the violation action set on the policy itself. If that's set to `block`, all failures will block. Add `"action": "warn"` to your default result while testing: + +```rego +default result := {"allowed": false, "action": "warn"} +``` + +**Check the policy violation action.** The violation action on the policy UI sets the default behaviour for all failures. Go to the edit policy page and confirm it's set to **Warn** if you want warnings by default. + +### Policy is not catching skipped steps + +If a policy intended to catch skipped steps isn't working, the conditions are likely only checking for step existence and not checking the `SkippedSteps` field. + +A step that's been skipped still appears in `input.Steps`, but its ID is also added to `input.SkippedSteps`. Your conditions need to check both: + +```rego +result := {"allowed": true} if { + some step in input.Steps + step.Source.SlugOrId == "" + not step.Id in input.SkippedSteps + step.Enabled == true +} +``` + +See [Check for both existence and skipping](/docs/platform-hub/policies/best-practices#check-for-both-existence-and-skipping) in the best practices guide. + +### Policy causes an evaluation error on runbook runs + +If a policy works correctly for deployments but causes an error on runbook runs, it's likely referencing `input.Release` without guarding against its absence. `Release` is only present for deployments. + +Add a scope to limit the policy to deployments only: + +```rego +default evaluate := false + +evaluate if { + not input.Runbook +} +``` + +Or, if the policy must evaluate both, check for the field's existence before referencing it: + +```rego +result := {"allowed": true} if { + input.Release + input.Release.GitRef == "refs/heads/main" +} +``` + +The same applies to `input.Runbook` on deployments, and `input.Tenant` on non-tenanted deployments. See [Guard against conditional fields](/docs/platform-hub/policies/best-practices#guard-against-conditional-fields) in the best practices guide. + +### Policy evaluates the wrong executions + +If a policy is evaluating executions it shouldn't, or not evaluating ones it should, the scope Rego is likely not matching as expected. + +Turn on verbose logging in the task log for an affected execution. This shows the full input object, including the exact values for `Environment.Slug`, `Project.Slug`, `Space.Slug`, and other fields your scope may be checking. Compare these against your scope Rego to identify the mismatch. + +Common causes: + +- Matching on `Name` instead of `Slug`. Names can change; slugs are stable. Prefer `Slug` for matching. +- Case sensitivity. Rego string comparisons are case-sensitive. `"Production"` and `"production"` are not the same value. +- Missing a guard for `input.Runbook` or `input.Tenant` when the scope assumes those fields are always present. + +--- + +## Known issues + +### Windows Server missing Visual C++ dependency + +If you see the error "The Compliance Policy engine failed to load. There may be missing dependencies on the machine hosting Octopus Server" when trying to load or create a policy, your Windows Server host is missing the Visual C++ Redistributable. :::figure -![A error callout trying to load the policies page](/docs/img/platform-hub/policies/policies-missing-dependency.png) +![An error shown when trying to load the policies page due to a missing dependency](/docs/img/platform-hub/policies/policies-missing-dependency.png) ::: -If your host machine is running Windows Server then you are missing the Visual C++ Redistributable. To resolve this error you need to install the latest redistributable version for your machine, see [Visual C++ dependency](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-supported-redistributable-version) for more information. +To fix this, install the latest Visual C++ Redistributable for your machine. See [Latest supported Visual C++ Redistributable downloads](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-supported-redistributable-version) on the Microsoft documentation site.