Policy deployment and self governance with Spinnaker hero graphic

Policy deployment and self governance with Spinnaker

Feb 24, 2021 by Stephen Atwell

Giving developers direct access to production is great, but you want to ensure they stay safe. For example, allowing remote SSH access into an internet-accessible load balancer exposes a potential attack vector. To prevent this, I’m leveraging Armory’s Policy Engine for Spinnaker. Policy Engine enforces policies to govern what developers are allowed to do within Spinnaker, and can prevent load balancers exposing SSH. In addition to implementing the policy to govern Spinnaker, we will also use Spinnaker for our policy deployment.

 

Policy Engine reads policies from Open Policy Agent, which can read policies from ConfigMaps. We will deploy it this way, and then create a configuration map for each of our policies.
The policy deployment pipeline I’ll explain how to configure
The “Deploy OPA Service” stage deploys OPA. The other stages deploy the policies.
ouroboros: a snake eating its own tail
Spinnaker leverages Armory’s Policy Engine to query policies from OPA and enforces them against Spinnaker. Since I am using Spinnaker to deploy its own policies, I needed to secure control over policy deployment! To do this, I implemented two policies that restrict changes to the OPA deployment. It’s a bit of a snake eating its own tail.

Securing Policy Deployment via Policy

Since any configuration maps in the “opa” namespace can change policy,  I created a policy to ensure that only my OPA application can deploy to this namespace:

  package spinnaker.deployment.tasks.before.deployManifest
    deny[“Only the OPA application can deploy to the OPA namespace”]{
        deployToOpaNamespace
            input.deploy.moniker.app!=”opa”
    }
    deployToOpaNamespace{
            input.deploy.manifests[_].metadata.namespace==”opa”
    }

I also created a policy that ensures only I can edit pipelines for the OPA application:

    package spinnaker.execution.stages.before.savePipeline
   deny[“Only Stephen can change policy”] {
      input.pipeline.application==”opa”
     input.pipeline.authentication.user!=”stephenatwell”
    }
MyDeploy policy: protect policy pipeline” stage deploys both policies in two configuration maps. These policies provide error messages for other developers—here’s what it looks like if a different pipeline tries to deploy to my “opa” namespace:
other developers receive a friendly error message

Implementing a policy to Block SSH

After securing policy deployment, I moved on to implementing my policy. The stageDeploy Policy: Restrict Port 22″ deploys the policy that prevents my developers from exposing port 22 in production load balancers:
package spinnaker.deployment.tasks.deployManifest
deny[“LoadBalancer Services must not have port 22 open.”] {
    manifests := input.deploy.manifests
    manifest := manifests[_]
    manifest.kind == “Service”
    manifest.spec.type == “LoadBalancer”
    port := manifest.spec.ports[_]
    port.port == 22
}
In the future, I can easily add new policies as additional stages in my OPA pipeline. If you are interested in leveraging Spinnaker to deploy an Open Policy Agent, or its policies, an export of my deployment pipeline can be found below:

 

Full pipeline export:
{
“id”: “07ebad32-3083-417d-9c06-c81025146058”,
“metadata”: {
“description”: “A pipeline to deploy OPA that enforces Spinnaker policies”,
“name”: “Ouroboros”,
“owner”: “[email protected]”,
“scopes”: [
“global”
]
},
“pipeline”: {
“keepWaitingPipelines”: false,
“lastModifiedBy”: “stephenatwell”,
“limitConcurrent”: true,
“spelEvaluator”: “v4”,
“stages”: [
{
“account”: “spinnaker”,
“cloudProvider”: “kubernetes”,
“manifests”: [
{
“apiVersion”: “apps/v1”,
“kind”: “Deployment”,
“metadata”: {
“labels”: {
“app”: “opa”
},
“name”: “opa-deployment”,
“namespace”: “opa”
},
“spec”: {
“progressDeadlineSeconds”: 600,
“replicas”: 1,
“revisionHistoryLimit”: 10,
“selector”: {
“matchLabels”: {
“app”: “opa”
}
},
“strategy”: {
“rollingUpdate”: {
“maxSurge”: “25%”,
“maxUnavailable”: “25%”
},
“type”: “RollingUpdate”
},
“template”: {
“metadata”: {
“labels”: {
“app”: “opa”
}
},
“spec”: {
“containers”: [
{
“args”: [
“run”,
“–server”,
“–addr=http://0.0.0.0:8181”,
“–log-level”,
“debug”
],
“image”: “openpolicyagent/opa:0.17.2”,
“imagePullPolicy”: “IfNotPresent”,
“livenessProbe”: {
“failureThreshold”: 3,
“httpGet”: {
“path”: “/health”,
“port”: 8181,
“scheme”: “HTTP”
},
“initialDelaySeconds”: 3,
“periodSeconds”: 5,
“successThreshold”: 1,
“timeoutSeconds”: 1
},
“name”: “opa”,
“readinessProbe”: {
“failureThreshold”: 3,
“httpGet”: {
“path”: “/health”,
“port”: 8181,
“scheme”: “HTTP”
},
“initialDelaySeconds”: 3,
“periodSeconds”: 5,
“successThreshold”: 1,
“timeoutSeconds”: 1
},
“resources”: {},
“terminationMessagePath”: “/dev/termination-log”,
“terminationMessagePolicy”: “File”
},
{
“args”: [
“–policies=opa”,
“–require-policy-label=true”
],
“image”: “openpolicyagent/kube-mgmt:0.9”,
“imagePullPolicy”: “IfNotPresent”,
“name”: “kube-mgmt”,
“resources”: {},
“terminationMessagePath”: “/dev/termination-log”,
“terminationMessagePolicy”: “File”
}
],
“dnsPolicy”: “ClusterFirst”,
“restartPolicy”: “Always”,
“schedulerName”: “default-scheduler”,
“securityContext”: {},
“terminationGracePeriodSeconds”: 30
}
}
}
}
],
“moniker”: {
“app”: “opa”
},
“name”: “Deploy OPA Service”,
“refId”: “1”,
“requisiteStageRefIds”: [],
“skipExpressionEvaluation”: false,
“source”: “text”,
“trafficManagement”: {
“enabled”: false,
“options”: {
“enableTraffic”: false,
“services”: []
}
},
“type”: “deployManifest”
},
{
“account”: “spinnaker”,
“cloudProvider”: “kubernetes”,
“manifests”: [
{
“apiVersion”: “v1”,
“data”: {
“restrict-opa-app.rego”: “package spinnaker.execution.stages.before.savePipeline\ndeny[\”Only Stephen can change policy\”] {\n input.pipeline.application==\”opa\”\n input.pipeline.authentication.user!=\”stephenatwell\”\n}\n”
},
“kind”: “ConfigMap”,
“metadata”: {
“labels”: {
“openpolicyagent.org/policy”: “rego”
},
“name”: “restrict-opa-app”,
“namespace”: “opa”
}
},
{
“apiVersion”: “v1”,
“data”: {
“restrict-opa-namespace.rego”: “package spinnaker.deployment.tasks.before.deployManifest\ndeny[\”Only the OPA application can deploy to the OPA namespace\”]{\n deployToOpaNamespace \n input.deploy.moniker.app!=\”opa\” \n}\ndeployToOpaNamespace{\n input.deploy.manifests[_].metadata.namespace==\”opa\”\n}\n”
},
“kind”: “ConfigMap”,
“metadata”: {
“labels”: {
“openpolicyagent.org/policy”: “rego”
},
“name”: “restrict-opa-namespace”,
“namespace”: “opa”
}
}
],
“moniker”: {
“app”: “opa”
},
“name”: “Deploy Policy: protect policy pipeline”,
“refId”: “4”,
“requisiteStageRefIds”: [
“1”
],
“skipExpressionEvaluation”: false,
“source”: “text”,
“trafficManagement”: {
“enabled”: false,
“options”: {
“enableTraffic”: false,
“services”: []
}
},
“type”: “deployManifest”
},
{
“account”: “spinnaker”,
“cloudProvider”: “kubernetes”,
“manifests”: [
{
“apiVersion”: “v1”,
“data”: {
“prevent-ssh-on-load-balancers.rego”: “package spinnaker.deployment.tasks.deployManifest\n\ndeny[\”LoadBalancer Services must not have port 22 open.\”] {\n manifests := input.deploy.manifests\n manifest := manifests[_]\n manifest.kind == \”Service\”\n manifest.spec.type == \”LoadBalancer\”\n port := manifest.spec.ports[_]\n port.port == 22\n}\n”
},
“kind”: “ConfigMap”,
“metadata”: {
“labels”: {
“openpolicyagent.org/policy”: “rego”
},
“name”: “prevent-ssh-on-load-balancers”,
“namespace”: “opa”
}
}
],
“moniker”: {
“app”: “opa”
},
“name”: “Deploy Policy: Restrict Port 22”,
“refId”: “5”,
“requisiteStageRefIds”: [
“1”
],
“skipExpressionEvaluation”: false,
“source”: “text”,
“trafficManagement”: {
“enabled”: false,
“options”: {
“enableTraffic”: false,
“services”: []
}
},
“type”: “deployManifest”
}
],
“triggers”: [],
“updateTs”: “1614119729000”
},
“protect”: false,
“schema”: “v2”,
“variables”: []
}
Share this post:

Recently Published Posts

How to Become a Site Reliability Engineer (SRE)

Jun 6, 2023

A site reliability engineer (SRE) bridges the gap between IT operations and software development. They understand coding and the overall task of keeping the system operating.  The SRE role originated to give software developers input into how teams deploy and maintain software and to improve it to increase reliability and performance. Before SREs, the software […]

Read more

Continuous Deployment KPIs

May 31, 2023

Key SDLC Performance Metrics for Engineering Leaders Engineering leaders must have an effective system in place to measure their team’s performance and ensure that they are meeting their goals. One way to do this is by monitoring Continuous Deployment Key Performance Indicators (KPIs).  CD and Automated Tests If you’re not aware, Continuous Deployment, or CD, […]

Read more

What Are the Pros and Cons of Rolling Deployments?

May 26, 2023

Rolling deployments use a software release strategy that delivers new versions of an application in phases to minimize downtime. Anyone who has lived through a failed update knows how painful it can be. If a comprehensive update fails, there are hours of downtime while it is rolled back. Even if the deployment happens after hours, […]

Read more