Add support for enabling Policy Packs with configuration (#4127)

* Support policy pack configiguration using policy enable cmd.
This commit is contained in:
Sean Holung 2020-03-24 13:30:36 -07:00 committed by GitHub
parent 8ce10e1dfe
commit d0f5e35b50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 245 additions and 10 deletions

View file

@ -10,12 +10,16 @@ CHANGELOG
- Add missing builtin `MapArray` to Go SDK.
[#4144](https://github.com/pulumi/pulumi/pull/4144)
- Add aliases to Go SDK codegen pkg.
[#4157](https://github.com/pulumi/pulumi/pull/4157)
- Discontinue testing on Node 8 (which has been end-of-life since January 2020), and start testing on Node 13.
[#4156](https://github.com/pulumi/pulumi/pull/4156)
- Add support for enabling Policy Packs with configuration.
[#3756](https://github.com/pulumi/pulumi/pull/4127)
## 1.13.0 (2020-03-18)
- Add support for plugin acquisition for Go programs
[#4060](https://github.com/pulumi/pulumi/pull/4060)

View file

@ -665,18 +665,18 @@ func validatePolicyPackVersion(s string) error {
// ApplyPolicyPack enables a `PolicyPack` to the Pulumi organization. If policyGroup is not empty,
// it will enable the PolicyPack on the default PolicyGroup.
func (pc *Client) ApplyPolicyPack(ctx context.Context, orgName, policyGroup,
policyPackName, versionTag string) error {
policyPackName, versionTag string, policyPackConfig map[string]*json.RawMessage) error {
// If a Policy Group was not specified, we use the default Policy Group.
if policyGroup == "" {
policyGroup = apitype.DefaultPolicyGroup
}
// If a Policy Group was specified, enable it for the specific group only.
req := apitype.UpdatePolicyGroupRequest{
AddPolicyPack: &apitype.PolicyPackMetadata{
Name: policyPackName,
VersionTag: versionTag,
Config: policyPackConfig,
},
}

View file

@ -204,9 +204,10 @@ func (pack *cloudPolicyPack) Publish(
func (pack *cloudPolicyPack) Enable(ctx context.Context, policyGroup string, op backend.PolicyPackOperation) error {
if op.VersionTag == nil {
return pack.cl.ApplyPolicyPack(ctx, pack.ref.orgName, policyGroup, string(pack.ref.name), "" /* versionTag */)
return pack.cl.ApplyPolicyPack(ctx, pack.ref.orgName, policyGroup, string(pack.ref.name),
"" /* versionTag */, op.Config)
}
return pack.cl.ApplyPolicyPack(ctx, pack.ref.orgName, policyGroup, string(pack.ref.name), *op.VersionTag)
return pack.cl.ApplyPolicyPack(ctx, pack.ref.orgName, policyGroup, string(pack.ref.name), *op.VersionTag, op.Config)
}
func (pack *cloudPolicyPack) Disable(ctx context.Context, policyGroup string, op backend.PolicyPackOperation) error {

View file

@ -16,6 +16,7 @@ package backend
import (
"context"
"encoding/json"
"github.com/pulumi/pulumi/sdk/go/common/workspace"
@ -36,6 +37,7 @@ type PolicyPackOperation struct {
// If nil, the latest version is assumed.
VersionTag *string
Scopes CancellationScopeSource
Config map[string]*json.RawMessage
}
// PolicyPack is a set of policies associated with a particular backend implementation.

View file

@ -15,7 +15,11 @@
package cmd
import (
"encoding/json"
"github.com/pulumi/pulumi/pkg/backend"
resourceanalyzer "github.com/pulumi/pulumi/pkg/resource/analyzer"
"github.com/pulumi/pulumi/sdk/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/go/common/util/cmdutil"
"github.com/spf13/cobra"
)
@ -24,6 +28,7 @@ const latestKeyword = "latest"
type policyEnableArgs struct {
policyGroup string
config string
}
func newPolicyEnableCmd() *cobra.Command {
@ -48,9 +53,22 @@ func newPolicyEnableCmd() *cobra.Command {
version = &cliArgs[1]
}
// Load the configuration from the user-specified JSON file into config object.
var config map[string]*json.RawMessage
if args.config != "" {
config, err = loadPolicyConfigFromFile(args.config)
if err != nil {
return err
}
}
// Attempt to enable the Policy Pack.
return policyPack.Enable(commandContext(), args.policyGroup, backend.PolicyPackOperation{
VersionTag: version, Scopes: cancellationScopes})
return policyPack.Enable(commandContext(), args.policyGroup,
backend.PolicyPackOperation{
VersionTag: version,
Scopes: cancellationScopes,
Config: config,
})
}),
}
@ -58,5 +76,45 @@ func newPolicyEnableCmd() *cobra.Command {
&args.policyGroup, "policy-group", "",
"The Policy Group for which the Policy Pack will be enabled; if not specified, the default Policy Group is used")
cmd.PersistentFlags().StringVar(
&args.config, "config", "",
"The file path for the Policy Pack configuration file")
return cmd
}
func loadPolicyConfigFromFile(file string) (map[string]*json.RawMessage, error) {
analyzerPolicyConfigMap, err := resourceanalyzer.LoadPolicyPackConfigFromFile(file)
if err != nil {
return nil, err
}
// Convert type map[string]plugin.AnalyzerPolicyConfig to map[string]*json.RawMessage.
config := make(map[string]*json.RawMessage)
for k, v := range analyzerPolicyConfigMap {
raw, err := marshalAnalyzerPolicyConfig(v)
if err != nil {
return nil, err
}
config[k] = raw
}
return config, nil
}
// marshalAnalyzerPolicyConfig converts the type plugin.AnalyzerPolicyConfig to structure the data
// in a format the way the API service is expecting.
func marshalAnalyzerPolicyConfig(c plugin.AnalyzerPolicyConfig) (*json.RawMessage, error) {
m := make(map[string]interface{})
for k, v := range c.Properties {
m[k] = v
}
if c.EnforcementLevel != "" {
m["enforcementLevel"] = c.EnforcementLevel
}
bytes, err := json.Marshal(m)
if err != nil {
return nil, err
}
raw := json.RawMessage(bytes)
return &raw, nil
}

View file

@ -0,0 +1,6 @@
{
"s3-no-public-read": {
"enforcementLevel": "mandatory",
"message": "this message is invalid because it is way more than 10 characters......"
}
}

View file

@ -0,0 +1,9 @@
{
"all": {
"enforcementLevel": "advisory"
},
"s3-no-public-read": {
"enforcementLevel": "mandatory",
"message": "test msg"
}
}

View file

@ -0,0 +1,39 @@
// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
import * as aws from "@pulumi/aws";
import * as policy from "@pulumi/policy";
const packName = process.env.TEST_POLICY_PACK;
if (!packName) {
console.log("no policy name provided");
process.exit(-1);
} else {
const policies = new policy.PolicyPack(packName, {
policies: [
{
name: "s3-no-public-read",
description: "Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
enforcementLevel: "mandatory",
configSchema: {
properties: {
message: {
type: "string",
minLength: 2,
maxLength: 10,
},
},
},
validateResource: policy.validateTypedResource(aws.s3.Bucket, (bucket, args, reportViolation) => {
if (bucket.acl === "public-read" || bucket.acl === "public-read-write") {
reportViolation(
"You cannot set public-read or public-read-write on an S3 bucket. " +
"Read more about ACLs here: " +
"https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html");
}
}),
},
],
});
}

View file

@ -0,0 +1,14 @@
{
"name": "aws-policy",
"version": "0.0.1",
"dependencies": {
"@pulumi/pulumi": "1.13.0",
"@pulumi/aws": "1.27.0",
"@pulumi/policy": "0.4.1-dev.1584625475"
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^10.12.7",
"tslint": "^5.11.0"
}
}

View file

@ -0,0 +1,14 @@
{
"name": "aws-policy",
"version": { policyVersion },
"dependencies": {
"@pulumi/pulumi": "1.13.0",
"@pulumi/aws": "1.27.0",
"@pulumi/policy": "0.4.1-dev.1584625475"
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^10.12.7",
"tslint": "^5.11.0"
}
}

View file

@ -0,0 +1,3 @@
name: aws-policy
description: Example policies for AWS
runtime: nodejs

View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"sourceMap": false,
"stripInternal": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
},
"files": [
"index.ts",
]
}

View file

@ -13,10 +13,65 @@ import (
ptesting "github.com/pulumi/pulumi/sdk/go/common/testing"
)
// TestPolicy tests policy related commands work.
func TestPolicy(t *testing.T) {
t.Skip("Temporarily skipping test that is causing unrelated tests to fail - pulumi/pulumi#4149")
// TestPolicyWithConfig runs integration tests against the policy pack in the policy_pack_w_config
// directory using version 0.4.1-dev of the pulumi/policy sdk.
func TestPolicyWithConfig(t *testing.T) {
e := ptesting.NewEnvironment(t)
defer func() {
if !t.Failed() {
e.DeleteEnvironment()
}
}()
// Confirm we have credentials.
if os.Getenv("PULUMI_ACCESS_TOKEN") == "" {
t.Fatal("PULUMI_ACCESS_TOKEN not found, aborting tests.")
}
name, _ := e.RunCommand("pulumi", "whoami")
orgName := strings.TrimSpace(name)
// Pack and push a Policy Pack for the organization.
policyPackName := fmt.Sprintf("%s-%x", "test-policy-pack", time.Now().UnixNano())
e.ImportDirectory("policy_pack_w_config")
e.RunCommand("yarn", "install")
os.Setenv("TEST_POLICY_PACK", policyPackName)
// Publish the Policy Pack twice.
publishPolicyPackWithVersion(e, orgName, `"0.0.1"`)
publishPolicyPackWithVersion(e, orgName, `"0.0.2"`)
// Check the policy ls commands.
packsOutput, _ := e.RunCommand("pulumi", "policy", "ls", "--json")
var packs []policyPacksJSON
assertJSON(e, packsOutput, &packs)
groupsOutput, _ := e.RunCommand("pulumi", "policy", "group", "ls", "--json")
var groups []policyGroupsJSON
assertJSON(e, groupsOutput, &groups)
// Enable, Disable and then Delete the Policy Pack.
e.RunCommand("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName), "0.0.1")
// Enable Policy Pack with Configuration.
e.RunCommand("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName),
"--config=configs/valid-config.json", "0.0.1")
e.RunCommandExpectError("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName),
"--config=configs/invalid-config.json", "0.0.1")
// Disable Policy Pack specifying version.
e.RunCommand("pulumi", "policy", "disable", fmt.Sprintf("%s/%s", orgName, policyPackName), "--version=0.0.1")
// Enable and Disable without specifying the version number.
e.RunCommand("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName), "latest")
e.RunCommand("pulumi", "policy", "disable", fmt.Sprintf("%s/%s", orgName, policyPackName))
e.RunCommand("pulumi", "policy", "rm", fmt.Sprintf("%s/%s", orgName, policyPackName), "0.0.1")
e.RunCommand("pulumi", "policy", "rm", fmt.Sprintf("%s/%s", orgName, policyPackName), "all")
}
// TestPolicyWithoutConfig runs integration tests against the policy pack in the policy_pack_w_config
// directory. This tests against version 0.2.0 of the pulumi/policy sdk, prior to policy config being supported.
func TestPolicyWithoutConfig(t *testing.T) {
e := ptesting.NewEnvironment(t)
defer func() {
if !t.Failed() {
@ -34,7 +89,7 @@ func TestPolicy(t *testing.T) {
// Pack and push a Policy Pack for the organization.
policyPackName := fmt.Sprintf("%s-%x", "test-policy-pack", time.Now().UnixNano())
e.ImportDirectory("test_policy_pack")
e.ImportDirectory("policy_pack_wo_config")
e.RunCommand("yarn", "install")
os.Setenv("TEST_POLICY_PACK", policyPackName)
@ -81,3 +136,11 @@ func assertJSON(e *ptesting.Environment, out string, respObj interface{}) {
e.Errorf("unable to unmarshal %v", out)
}
}
// publishPolicyPackWithVersion updates the version in package.json so we can
// dynamically publish different versions for testing.
func publishPolicyPackWithVersion(e *ptesting.Environment, orgName, version string) {
cmd := fmt.Sprintf(`sed 's/{ policyVersion }/%s/g' package.json.tmpl | tee package.json`, version)
e.RunCommand("bash", "-c", cmd)
e.RunCommand("pulumi", "policy", "publish", orgName)
}