Add support for enabling Policy Packs with configuration (#4127)
* Support policy pack configiguration using policy enable cmd.
This commit is contained in:
parent
8ce10e1dfe
commit
d0f5e35b50
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"s3-no-public-read": {
|
||||
"enforcementLevel": "mandatory",
|
||||
"message": "this message is invalid because it is way more than 10 characters......"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"all": {
|
||||
"enforcementLevel": "advisory"
|
||||
},
|
||||
"s3-no-public-read": {
|
||||
"enforcementLevel": "mandatory",
|
||||
"message": "test msg"
|
||||
}
|
||||
}
|
39
tests/integration/policy/policy_pack_w_config/index.ts
Normal file
39
tests/integration/policy/policy_pack_w_config/index.ts
Normal 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");
|
||||
}
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
14
tests/integration/policy/policy_pack_w_config/package.json
Normal file
14
tests/integration/policy/policy_pack_w_config/package.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
name: aws-policy
|
||||
description: Example policies for AWS
|
||||
runtime: nodejs
|
22
tests/integration/policy/policy_pack_wo_config/tsconfig.json
Normal file
22
tests/integration/policy/policy_pack_wo_config/tsconfig.json
Normal 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",
|
||||
]
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue