Skip to main content

Require manual approval for applications in production

🎯 Goal: Add metadata to applications and enforce policies so sensitive apps require manual approval before production deployment.

Introduction​

This tutorial captures application‑level attributes and enforces a PCI review step for production releases.

Best paired with build checks

You can use this guide on its own. For stronger gates, see also Build catalog and production quality gates to validate coverage and vulnerabilities at build time. These guides are independent and can be done in any order.

What you’ll set up​

You’ll:

  • Define a catalog specification on the application entity with Responsible, SLO, and PCI fields.
  • Populate those fields when creating applications.
  • Attach an approval policy so non‑PCI applications auto‑approve, while PCI apps require manual approval before deploying to production.

Prerequisites​

You’ll need:

  • Terraform >= 1.13
  • A valid nullplatform API key with roles Admin, Developer, Ops, SecOps at the Organization level
  • A Kubernetes cluster with the nullplatform agent running
  • The nullplatform CLI installed: curl https://cli.nullplatform.com/install.sh | sh
  • Environment variable for the CLI:
    export NULLPLATFORM_API_KEY=<your_api_key_here>

1. Create a catalog spec for application metadata​

Policies read from entity metadata, so declaring catalog schemas ensures the fields exist and are validated consistently across applications.

Run the following terraform to add three fields to the application entity: Responsible (owner), PCI (Yes/No), and SLO (Critical/High/Medium/Low).

Replace these placeholders:

  • <organization=XXXX:account=XXXX:namespace=XXXX> with your NRN.
  • Update the enum for Responsible with names relevant to your org (examples shown).
resource "nullplatform_metadata_specification" "application_metadata" {
name = "Application Metadata"
description = "Owner, PCI, and SLO for applications"
nrn = "<organization=XXXX:account=XXXX:namespace=XXXX>"
entity = "application"
metadata = "metadata_application"

schema = jsonencode({
"type": "object",
"properties": {
"Responsible": {
"description": "Person assigned as responsible for the application.",
"type": "string",
"enum": ["Jane Doe", "John Smith", "Alex Taylor"]
},
"PCI": {
"description": "Is the application in PCI scope?",
"type": "string",
"enum": ["Yes", "No"]
},
"SLO": {
"description": "Reliability class for the application.",
"type": "string",
"enum": ["Critical", "High", "Medium", "Low"]
}
},
"required": ["PCI", "Responsible"],
"additionalProperties": false
})
}
info

Catalog specifications aren’t applied retroactively. Existing applications won’t automatically get values for the new fields. If you already have applications, backfill one catalog instance per application via API.

✅ Checkpoint​

Create a new application and confirm the Responsible, PCI, and SLO fields are visible in the UI.

app-catalog-example.png

2. Require manual approval for PCI apps in production​

PCI systems typically require human oversight. The policy below auto‑approves non‑PCI apps and prompts for manual approval when PCI = Yes.

  1. Create a policy that passes only when PCI = No.
  2. Associate it to a deployment approval action scoped to environment=production.
  3. Configure the action to approve on policy success and require manual on policy fail.

Replace:

  • <api-key> with your nullplatform API key.
  • <organization=XXXX:account=XXXX:namespace=XXXX> with your NRN.
terraform {
required_providers {
nullplatform = {
source = "nullplatform/nullplatform"
}
}
}

provider "nullplatform" {
api_key = "<api-key>"
}

# Policy: passes for non-PCI apps (PCI = No)
resource "nullplatform_approval_policy" "PCI" {
nrn = "<organization=XXXX:account=XXXX:namespace=XXXX>"
name = "PCI"
conditions = jsonencode({
"application.metadata.metadata_application.PCI" = "No"
})
}

# Action: deployment_create
resource "nullplatform_approval_action" "deployment_create" {
nrn = "<organization=XXXX:account=XXXX:namespace=XXXX>"
entity = "deployment"
action = "deployment:create"

dimensions = {
environment = "production"
}

# If policy passes (non-PCI) ⇒ approve. If it fails (PCI) ⇒ manual.
on_policy_success = "approve"
on_policy_fail = "manual"
}

# Attach policy to action
resource "nullplatform_approval_action_policy_association" "PCI" {
approval_action_id = nullplatform_approval_action.deployment_create.id
approval_policy_id = nullplatform_approval_policy.PCI.id
}

Combine with build gates for layered approvals​

When you already enforce build-time quality gates (e.g., coverage, vulnerabilities), you can layer this PCI approval on the same production deployment action. In practice, you keep your existing action and add this PCI policy alongside your build-quality policies so they’re evaluated together at deployment time.

If your Terraform for build gates already defines the deployment approval action and associations, you only need to add this policy resource (and, if not handled elsewhere, an association to the same action):

# Policy: passes for non-PCI apps (PCI = No)
resource "nullplatform_approval_policy" "PCI" {
nrn = "<organization=XXXX:account=XXXX:namespace=XXXX>"
name = "PCI"
conditions = jsonencode({
"application.metadata.metadata_application.PCI" = "No"
})
}

# (Optional if you don’t already associate policies programmatically)
# Attach to the same production deployment action used by build gates
# resource "nullplatform_approval_action_policy_association" "PCI" {
# approval_action_id = nullplatform_approval_action.deployment_create.id
# approval_policy_id = nullplatform_approval_policy.PCI.id
# }

Test that it works​

  1. Create a new application and set the PCI field to Yes.
  2. Trigger a production deployment. You should see a manual approval request.
pci-policy-prod.png

Wrap‑up 🎉​

All done! Now you have:

  • Modeled Owner, SLO, and PCI as application metadata.
  • Enforced that PCI‑scoped applications require manual approval in production.