Skip to content
Open
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
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-use-before-define": 0,
"import/prefer-default-export": 0
"import/prefer-default-export": 0,
"@typescript-eslint/interface-name-prefix": 0
},
"ignorePatterns": [
"node_modules",
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
*.js
!jest.config.js
*.d.ts
**/.jsii

**/.DS_Store

# Created by https://www.gitignore.io/api/node
# Edit at https://www.gitignore.io/?templates=node
Expand Down
14 changes: 14 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

# Exclude typescript source and config
*.ts
tsconfig.json

# Include javascript files and typescript declarations
!*.js
!*.d.ts

# Exclude jsii outdir
dist

# Include .jsii
!.jsii
24 changes: 12 additions & 12 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Make sure your Cloud resources are:
yarn add cloudpatrol
```

### Example
### Example

Given this example:

Expand Down Expand Up @@ -73,14 +73,14 @@ Hasn't been implemented, yet. But it's on the agenda, and probably possible righ
* @cloudformationResource AWS::S3::Bucket
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-versioningconfig.html
*/
export class BucketVersioningPolicy extends Policy implements PolicyInterface {
export class BucketVersioningPolicy extends Policy {
public policyName = 'Bucket Versioning'
public description = 'This ensures that a bucket is properly versioned'
public link = 'https//docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-versioningconfig.html'
public scope = s3.CfnBucket
public validator(node: s3.CfnBucket, reporter: Reportable): void {
if (!node.versioningConfiguration ||

public validator(node: s3.CfnBucket, reporter: IReportable): void {
if (!node.versioningConfiguration ||
(!cdk.Tokenization.isResolvable(node.versioningConfiguration) && node.versioningConfiguration.status !== 'Enabled')) {
reporter.addWarning(node, this, 'Bucket versioning is not enabled');
}
Expand All @@ -100,7 +100,7 @@ export class BucketVersioningPolicy extends Policy implements PolicyInterface {
Policies have to follow this schema

```typescript
class YourCustomPolicy extends Policy implements PolicyInterface {
class YourCustomPolicy extends Policy {
//...
}
```
Expand All @@ -111,7 +111,7 @@ There are two options to define the scope of a Policy:
*Define an explicit scope:*

```typescript
class YourCustomPolicy extends Policy implements PolicyInterface {
class YourCustomPolicy extends Policy {
//...
public scope = s3.CfnBucket
//...
Expand All @@ -121,7 +121,7 @@ class YourCustomPolicy extends Policy implements PolicyInterface {
*Overwrite `isApplicable`:*

```typescript
class YourCustomPolicy extends Policy implements PolicyInterface {
class YourCustomPolicy extends Policy {
//...
public isApplicable(node: cdk.Resource): boolean {
// your custom logic here
Expand All @@ -132,9 +132,9 @@ class YourCustomPolicy extends Policy implements PolicyInterface {
#### Policy Validation Logic

```typescript
class YourCustomPolicy extends Policy implements PolicyInterface {
class YourCustomPolicy extends Policy {
//...
public validator(node: s3.CfnBucket, reporter: Reportable, context: PolicyContext): void {
public validator(node: s3.CfnBucket, reporter: IReportable, context: PolicyContext): void {
// your custom logic here.
}
//...
Expand All @@ -144,7 +144,7 @@ Found issues can be reported via the `reporter` object. You can report multiple

- Info
- Warning
- Error
- Error

`context` is persistent across the entire Stack validation and can be passed in for dynamic information.

Expand All @@ -168,7 +168,7 @@ Cloud Patrol makes use of [Aspects](https://docs.aws.amazon.com/cdk/latest/guide
- [ ] Modularize and detangle `Reporter` to allow multiple ways of reporting
- [ ] [Github Actions](https://github.com/features/actions) for easy integration
- [ ] `.cloudpatrol` file?
- [ ] Provide more policies out of the box
- [ ] Provide more policies out of the box
- [ ] CLI which autodetects Stacks for inspection
- [ ] Integration tests against the last X releases of the [AWS CDK](https://github.com/aws/aws-cdk/)
- [ ] Integrate supported languages of [jsii](https://github.com/aws/jsii)
Expand Down
6 changes: 3 additions & 3 deletions lib/aws-cdk-patrol.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as cdk from "@aws-cdk/core";
import { IConstruct, IAspect } from "constructs";
import { Reportable, TerminalReporter } from "./reporter";
import { IReportable, TerminalReporter } from "./reporter";
import { AwsCdkReporter } from "./aws-cdk-reporter"
import { PolicyPack } from "./policy-pack";
import { PolicyContext } from "./policy";

export class AwsCdkPatrol implements IAspect {
private readonly reporter: Reportable;
private readonly reporter: IReportable;

constructor(private readonly policies: PolicyPack, private context: PolicyContext = {}) {
this.reporter = this.isSynthesizing() ? new AwsCdkReporter() : new TerminalReporter()
}
Expand Down
28 changes: 16 additions & 12 deletions lib/aws-cdk-reporter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Resource } from "@aws-cdk/core"
import { Reportable } from "./reporter";
import { IPolicy } from "./policy"
import { IReportable } from "./reporter";
import { Policy } from "./policy"
import { IConstruct } from "constructs";

export class AwsCdkReporter implements Reportable {
export class AwsCdkReporter implements IReportable {
private violations: Resource[] = []

public generateReport(): void {
Expand All @@ -13,18 +14,21 @@ export class AwsCdkReporter implements Reportable {
return this.violations.length > 0
}

public addInfo(node: Resource, policy: IPolicy, message: string): void {
this.violations.push(node)
node.node.addInfo(message)
public addInfo(node: IConstruct, _policy: Policy, message: string): void {
const resource = node as Resource;
this.violations.push(resource)
resource.node.addInfo(message)
}

public addWarning(node: Resource, policy: IPolicy, message: string): void {
this.violations.push(node)
node.node.addWarning(message)
public addWarning(node: IConstruct, _policy: Policy, message: string): void {
const resource = node as Resource;
this.violations.push(resource)
resource.node.addWarning(message)
}

public addError(node: Resource, policy: IPolicy, message: string): void {
this.violations.push(node)
node.node.addError(message)
public addError(node: IConstruct, _policy: Policy, message: string): void {
const resource = node as Resource;
this.violations.push(resource)
resource.node.addError(message)
}
}
14 changes: 6 additions & 8 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { TerminalReporter, Reportable } from "./reporter";
import { AwsCdkReporter } from "./aws-cdk-reporter"
import { Severity } from "./severity";
import { Policy, PolicyInterface, PolicyContext } from "./policy";
import { PolicyPack } from "./policy-pack";
import { AwsCdkPatrol } from "./aws-cdk-patrol";

export { TerminalReporter, Reportable, Severity, Policy, PolicyPack, AwsCdkPatrol, AwsCdkReporter, PolicyInterface, PolicyContext};
export * from "./reporter";
export * from "./aws-cdk-reporter"
export * from "./severity";
export * from "./policy";
export * from "./policy-pack";
export * from "./aws-cdk-patrol";
8 changes: 4 additions & 4 deletions lib/policy-pack.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { IConstruct } from "constructs";
import { Reportable } from "./reporter";
import { IReportable } from "./reporter";
import { Policy, PolicyContext } from "./policy";

export class PolicyPack {
constructor(private policies: Policy[] = []) { }

public add(policy: Policy): void {
this.policies.push(policy)
}
public validate(node: IConstruct, context: PolicyContext, reporter: Reportable): void {

public validate(node: IConstruct, context: PolicyContext, reporter: IReportable): void {
this.policies.forEach((policy) => {
policy.validate(node, reporter, context);
});
Expand Down
30 changes: 9 additions & 21 deletions lib/policy.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
import { IConstruct } from "constructs";
import { Reportable } from "./reporter";
import { IReportable } from "./reporter";

export interface PolicyContext {
[key: string]: any;
}

interface PolicyScope {
scope?: IConstruct;
isApplicable(node: IConstruct): boolean;
}

export interface PolicyInterface extends PolicyScope {
policyName: string;
description: string;
link: string;
}

/*
Policy
*/
export abstract class Policy implements PolicyScope {
export abstract class Policy {
public policyName = '';
public description = '';
public link = '';
public scope?: IConstruct
public abstract validator(node: IConstruct, reporter: Reportable, context: PolicyContext): void
public abstract validator(node: IConstruct, reporter: IReportable, context: PolicyContext): void

public validate(node: IConstruct, reporter: Reportable, context: PolicyContext): void {
public validate(node: IConstruct, reporter: IReportable, context: PolicyContext): void {
if (this.isApplicable(node)) {
this.validator(node, reporter, context);
}
}

/*
Checks if the policy is applicable to a given node. Can be overwritten in descendants to implement
custom logic.
*/
public isApplicable(node: IConstruct): boolean {
if (!this.scope) throw new Error('If Policy.scope is not defined, `isApplicable` has to be overwritten');

// instanceof doesn't work reliably here. Probably need a better check than this.
// Plus an ugly hack: tsc was complaining `Property 'prototype' does not exist on type`
return node.constructor.name === (this.scope as any).prototype.constructor.name
}
}

/*
A compatible type for valid descendants of Policy class
*/
export type IPolicy = Policy & PolicyInterface
29 changes: 14 additions & 15 deletions lib/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as cdk from "@aws-cdk/core"
import { IPolicy } from "./policy";
import { Policy } from "./policy";
import { Severity } from "./severity";
import { IConstruct } from "constructs";

Expand Down Expand Up @@ -39,7 +39,7 @@ class ViolationList {
public print(): void {
Object.values(this.violationItems).forEach((violationItem) => {
console.log("\x1b[1m%s\x1b[0m", `${violationItem.scope} (${violationItem.resourceType}):`)
console.group()
console.group()
console.log('\n-------------- Violations ------------------')
violationItem.violations.forEach((violation) => {
console.log(violation.message())
Expand All @@ -51,18 +51,17 @@ class ViolationList {
})
}
}

export interface Reportable {
export interface IReportable {
generateReport(): void;
hasViolations(): boolean;
addInfo(node: IConstruct, policy: IPolicy, message: string): void;
addWarning(node: IConstruct, policy: IPolicy, message: string): void;
addError(node: IConstruct, policy: IPolicy, message: string): void;
addInfo(node: IConstruct, policy: Policy, message: string): void;
addWarning(node: IConstruct, policy: Policy, message: string): void;
addError(node: IConstruct, policy: Policy, message: string): void;
}

interface PolicyViolationProps {
node: any;
policy: IPolicy;
policy: Policy;
message: string;
severity: Severity;
}
Expand All @@ -71,7 +70,7 @@ class PolicyViolation {
public scope: string;
public resourceType: string;
private node: cdk.CfnResource;
private policy: IPolicy;
private policy: Policy;

constructor(private props: PolicyViolationProps) {
this.node = this.props.node as cdk.CfnResource;
Expand Down Expand Up @@ -112,15 +111,15 @@ class PolicyViolation {
}
}

export class TerminalReporter implements Reportable {
export class TerminalReporter implements IReportable {
private violations: ViolationList
constructor() {
this.violations = new ViolationList()
}

public generateReport(): void {
console.log("\x1b[4m\x1b[35;1mCloud Patrol Report\x1b[0m\n");
this.violations.print()
this.violations.print()
}

public hasViolations(): boolean {
Expand All @@ -129,16 +128,16 @@ export class TerminalReporter implements Reportable {
}
return false;
}
public addInfo(node: IConstruct, policy: IPolicy, message: string): void {
public addInfo(node: IConstruct, policy: Policy, message: string): void {
this.reportViolation(node, policy, message, Severity.INFO);
}
public addWarning(node: IConstruct, policy: IPolicy, message: string): void {
public addWarning(node: IConstruct, policy: Policy, message: string): void {
this.reportViolation(node, policy, message, Severity.WARNING);
}
public addError(node: IConstruct, policy: IPolicy, message: string): void {
public addError(node: IConstruct, policy: Policy, message: string): void {
this.reportViolation(node, policy, message, Severity.ERROR);
}
private reportViolation(node: IConstruct, policy: IPolicy, message: string, severity: Severity): void {
private reportViolation(node: IConstruct, policy: Policy, message: string, severity: Severity): void {
this.violations.add(
new PolicyViolation({
node,
Expand Down
Loading