Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API changes #131

Merged
merged 5 commits into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
vendor/
**/Pulumi.*.yaml
**/yarn-error.log
yarn.lock
**/yarn.lock
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ before_install:
- source ${GOPATH}/src/github.com/pulumi/scripts/ci/prepare-environment.sh
- source ${PULUMI_SCRIPTS}/ci/keep-failed-tests.sh
install:
# Install Pulumi 🍹
- curl -fsSL https://get.pulumi.com/ | bash
- export PATH="$HOME/.pulumi/bin:$PATH"
# Install other tools.
- source ${PULUMI_SCRIPTS}/ci/install-common-toolchain.sh
before_script:
- ${PULUMI_SCRIPTS}/ci/ensure-dependencies
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
## 0.1.2 (unreleased)

### Improvements

- API changes to enable new types of policies (i.e. validating all resource in a stack) and passing
additional information to validation functions (https://github.com/pulumi/pulumi-policy/pull/131).

- `Policy.rules` is now `ResourceValidationPolicy.validateResource`.
- `typedRule` is now `validateTypedResource`.
- Policy violations are now reported through a `reportViolation` callback, rather than using asserts.
- A new `StackValidationPolicy` policy type is available for defining policies that check all resources
in a stack (the API definition is available, but such policies are not enabled yet).
- Validation functions can now return `Promise<void>`.

Example:

```typescript
new PolicyPack("aws-policy-pack", {
policies: [{
name: "s3-no-public-read",
description: "Prohibits setting the publicRead or publicReadWrite permission on AWS S3 buckets.",
enforcementLevel: "mandatory",
validateResource: validateTypedResource(aws.s3.Bucket.isInstance, (it, args, reportViolation) => {
if (it.acl === "public-read" || it.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");
}
}),
}],
});
```

### Bug fixes

- Allow policies to deal with Pulumi secret values
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ PROJECT_NAME := policy
SUB_PROJECTS := sdk/nodejs/policy
include build/common.mk

.PHONY: ensure
ensure::
# Golang dependencies for the integration tests.
go get -t -d ./tests/integration

.PHONY: publish_packages
publish_packages:
$(call STEP_MESSAGE)
Expand All @@ -11,6 +16,10 @@ publish_packages:
check_clean_worktree:
$$(go env GOPATH)/src/github.com/pulumi/scripts/ci/check-worktree-is-clean.sh

.PHONY: test
test:
go test .tests/integration -v -timeout 30m

# The travis_* targets are entrypoints for CI.
.PHONY: travis_cron travis_push travis_pull_request travis_api
travis_cron: all
Expand Down
177 changes: 150 additions & 27 deletions sdk/nodejs/policy/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import * as pulumi from "@pulumi/pulumi";
import { Resource } from "@pulumi/pulumi";
import * as q from "@pulumi/pulumi/queryable";
import { serve } from "./server";

/**
* The set of arguments for constructing a PolicyPack.
*/
export interface PolicyPackArgs {
policies: Policy[];
/**
* The policies associated with a PolicyPack.
*/
policies: Policies;
}

/**
* A PolicyPack contains one or more policies to enforce.
*/
export class PolicyPack {
private readonly policies: Policy[];
private readonly policies: Policies;

constructor(private name: string, args: PolicyPackArgs) {
this.policies = args.policies;
Expand All @@ -36,27 +44,6 @@ export class PolicyPack {
}
}

/** A function that returns true if a resource definition violates some policy. */
export type Rule = (type: string, properties: any) => void;

export function typedRule<TResource extends pulumi.Resource>(
filter: (o: any) => o is TResource,
rule: (properties: q.ResolvedResource<TResource>) => void,
): Rule {
return (type: string, properties: any) => {
properties.__pulumiType = type;
if (filter(properties) === false) {
return;
}
return rule(properties);
};
}

/**
* A keyword or term to associate with a policy, such as "cost" or "security."
*/
export type Tag = string;

/**
* Indicates the impact of a policy violation.
*/
Expand All @@ -82,10 +69,146 @@ export interface Policy {
* proper permissions.
*/
enforcementLevel: EnforcementLevel;
}

/**
* An array of Policies.
*/
export type Policies = (ResourceValidationPolicy | StackValidationPolicy)[];
justinvp marked this conversation as resolved.
Show resolved Hide resolved

/**
* ResourceValidationPolicy is a policy that validates a resource definition.
*/
export interface ResourceValidationPolicy extends Policy {
/**
* A callback function that validates if a resource definition violates a policy (e.g. "S3 buckets
* can't be public"). A single callback function can be specified, or multiple functions, which are
* called in order.
*/
validateResource: ResourceValidation | ResourceValidation[];
}

/**
* ResourceValidation is the callback signature for a `ResourceValidationPolicy`. A resource validation
* is passed `args` with more information about the resource and a `reportViolation` callback that can be
* used to report a policy violation. `reportViolation` can be called multiple times to report multiple
* violations against the same resource. `reportViolation` must be passed a message about the violation.
* The `reportViolation` signature accepts an optional `urn` argument, which is ignored when validating
* resources (the `urn` of the resource being validated is always used).
*/
export type ResourceValidation = (args: ResourceValidationArgs, reportViolation: ReportViolation) => Promise<void> | void;

/**
* ResourceValidationArgs is the argument bag passed to a resource validation.
*/
export interface ResourceValidationArgs {
/**
* The type of the Resource.
*/
type: string;

/**
* The properties of the Resource.
*/
props: Record<string, any>;

// TODO: Add support for the following:
justinvp marked this conversation as resolved.
Show resolved Hide resolved
//
// urn: string;
// name: string;
// opts: PolicyResourceOptions;
}

/**
* A helper function that returns a strongly-typed resource validation function.
* @param typeFilter A type guard used to determine if the args are an instance of the resource.
* @param validate A callback function that validates if the resource definition violates a policy.
*/
export function validateTypedResource<TResource extends Resource>(
typeFilter: (o: any) => o is TResource,
validate: (
resource: q.ResolvedResource<TResource>,
args: ResourceValidationArgs,
reportViolation: ReportViolation) => Promise<void> | void,
): ResourceValidation {
return (args: ResourceValidationArgs, reportViolation: ReportViolation) => {
args.props.__pulumiType = args.type;
if (typeFilter(args.props) === false) {
return;
}
validate(args.props as q.ResolvedResource<TResource>, args, reportViolation);
};
}

/**
* A helper function that returns `props` as a strongly-typed resolved resource based
* on the specified `type` when `filter` returns true, otherwise `undefined` is returned.
* @param typeFilter A type guard used to determine if the args are an instance of the resource.
* @param args Argument bag for specifying the `type` and `props`.
*/
export function asTypedResource<TResource extends Resource>(
typeFilter: (o: any) => o is TResource,
args: { type: string, props: Record<string, any> },
): q.ResolvedResource<TResource> | undefined {
args.props.__pulumiType = args.type;
if (typeFilter(args.props) === false) {
return undefined;
}
return args.props as q.ResolvedResource<TResource>;
}

/**
* StackValidationPolicy is a policy that validates a stack.
*/
export interface StackValidationPolicy extends Policy {
/**
* Chain of rules that return true if a resource definition violates a policy (e.g., "S3 buckets
* can't be public"). Rules are applied in the order they are declared.
* A callback function that validates if a stack violates a policy.
*/
rules: Rule | Rule[];
validateStack: StackValidation;
}

/**
* StackValidation is the callback signature for a `StackValidationPolicy`. A stack validation is passed
* `args` with more information about the stack and a `reportViolation` callback that can be used to
* report a policy violation. `reportViolation` can be called multiple times to report multiple violations
* against the stack. `reportViolation` must be passed a message about the violation, and an optional `urn`
* to a resource in the stack that's in violation of the policy. Not specifying a `urn` indicates the
* overall stack is in violation of the policy.
*/
export type StackValidation = (args: StackValidationArgs, reportViolation: ReportViolation) => Promise<void> | void;

/**
* StackValidationArgs is the argument bag passed to a resource validation.
*/
export interface StackValidationArgs {
/**
* The resources in the stack.
*/
resources: PolicyResource[];
}

/**
* PolicyResource represents a resource in the stack.
*/
export interface PolicyResource {
/**
* The type of the Resource.
*/
type: string;

/**
* The outputs of the Resource.
*/
props: Record<string, any>;

// TODO: Add support for the following:
//
// urn: string;
// name: string;
// opts: PolicyResourceOptions;
}

/**
* ReportViolation is the callback signature used to report policy violations.
*/
export type ReportViolation = (message: string, urn?: string) => void;
10 changes: 2 additions & 8 deletions sdk/nodejs/policy/protoutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const analyzerrpc = require("@pulumi/pulumi/proto/analyzer_grpc_pb.js");
const structproto = require("google-protobuf/google/protobuf/struct_pb.js");
const plugproto = require("@pulumi/pulumi/proto/plugin_pb.js");

import { EnforcementLevel, Policy, Tag } from "./policy";
import { EnforcementLevel, Policies } from "./policy";

export function asGrpcError(e: any, message?: string) {
if (message === undefined || message === "") {
Expand Down Expand Up @@ -71,11 +71,6 @@ export interface Diagnostic {
*/
message: string;

/**
* A keyword or term to associate with a policy, such as "cost" or "security."
*/
tags?: Tag[];

/**
* Indicates what to do on policy violation, e.g., block deployment but allow override with
* proper permissions.
Expand All @@ -90,7 +85,7 @@ export interface Diagnostic {

// ------------------------------------------------------------------------------------------------

export function makeAnalyzerInfo(policyPackName: string, policies: Policy[]): any {
export function makeAnalyzerInfo(policyPackName: string, policies: Policies): any {
const ai: any = new analyzerproto.AnalyzerInfo();
ai.setName(policyPackName);

Expand Down Expand Up @@ -120,7 +115,6 @@ export function makeAnalyzeResponse(ds: Diagnostic[]) {
diagnostic.setPolicypackversion(d.policyPackVersion);
diagnostic.setDescription(d.description);
diagnostic.setMessage(d.message);
diagnostic.setTagsList(d.tags);
diagnostic.setEnforcementlevel(mapEnforcementLevel(d.enforcementLevel));

diagnostics.push(diagnostic);
Expand Down
Loading