Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Validate ARN Format #23

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SubValidator from './validators/SubValidator';
import IfValidator from './validators/IfValidator';
import DependsOnValidator from './validators/DependsOnValidator'
import IAMPolicyDocumentValidator from './validators/IAMPolicyDocumentValidator';
import ARNFormatValidator from './validators/ARNFormatValidator';
import {
ResourceTypeValidator,
RequriedResourcePropertyValidator,
Expand All @@ -22,6 +23,7 @@ import {
ResourceInclusivePropertyValidator,
ResourceOnlyOnePropertyValidator,
} from './validators/resources';
import ResolveCfnFnValues from './preprocess/ResolveCfnFnValues';
import { Walker } from './ast';
import { Error } from './types';

Expand Down Expand Up @@ -79,7 +81,10 @@ export function lint(template: string, parameters: object = {}) {
const input = convertCfnFns(yaml.load(template));
const ignoredValidators: IgnoredValidator[] = [];

const prewalk = new Walker([new CfnFnPreparer(parameters)]);
const prewalk = new Walker([
new CfnFnPreparer(parameters),
new ResolveCfnFnValues(parameters)
]);
// Mutates input
prewalk.Root(input);

Expand All @@ -103,6 +108,7 @@ export function lint(template: string, parameters: object = {}) {
new ResourceInclusivePropertyValidator(errors),
new ResourceOnlyOnePropertyValidator(errors),
new IAMPolicyDocumentValidator(errors),
new ARNFormatValidator(errors),
new LaundryIgnore(ignoredValidators, errors),
];
const validate = new Walker(validators);
Expand Down
93 changes: 93 additions & 0 deletions src/preprocess/ResolveCfnFnValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as _ from 'lodash';

import { Visitor } from '../ast';
import { Path } from '../types';
import { PrimitiveType, PropertyValueType, ResourceTypes, Attributes } from '../spec';
import { valueToSpecs, isStringBoolean, isStringNumber, subVariables } from '../util';
import * as yaml from '../yaml';

export default class ResovleCfnFnValues extends Visitor {
private parameterTypes: { [key: string]: PropertyValueType[] } = {};
private parameters: object;
private resources: { [name: string]: string[] } = {}
private mappings: {
[key1: string]: {
[key2: string]: {
[key3: string]: PropertyValueType[]
}
}
} = {};

constructor(p: object) {
super();
this.parameters = p;
}

Resources(path: Path, resources: any) {
if (_.isObject(resources)) {
_.forEach(resources, (resource, name) => {
const type = _.get(resource, 'Type');
this.resources[name] = _.get(ResourceTypes, [type, 'Attributes'], {});
});
}
}

Mappings(path: Path, o: any) {
if (_.isObject(o)) {
_.forEach(o, (o1, k1) => {
if (_.isObject(o1)) {
_.forEach(o1, (o2, k2) => {
if (_.isObject(o2)) {
_.forEach(o2, (o3, k3) => {
// Yay, we're here
const specs = valueToSpecs(o3);
if (specs && !_.isEmpty(specs)) {
_.set(this.mappings, [k1, k2, k3], specs);
}
});
}
});
}
});
}
}

private refValue(ref: string) {
if (_.has(this.parameters, ref)) {
return _.get(this.parameters, ref);
}
}

CfnFn(path: Path, propertyName: string, cfnFn: yaml.CfnFn) {
if (cfnFn instanceof yaml.Ref) {
cfnFn = this.refValue(cfnFn.data);
} else if (cfnFn instanceof yaml.Sub) {
const variables = subVariables(cfnFn);
let resolvedValue = cfnFn.data;
for (const variable of variables) {
const value = this.refValue(variable);
if (!_.isUndefined(value)) {
const r = new RegExp(`\\$\\{\\s*${variable}\\s*\\}`, 'g');
resolvedValue = resolvedValue.replace(r, value);
}
}
cfnFn.resolvedValue = resolvedValue;
} else if (cfnFn instanceof yaml.FindInMap) {
if (_.isArray(cfnFn.data) && cfnFn.data.length === 3) {
// cfnFn.resolvedValue = ;
}
}
}
}

function filterMappings(key: string | yaml.CfnFn, o: object) {
return _.pickBy(o, (v, k) => {
// If any segment of the `FindInMap` path is a dynamic value, accept all
// values at that level
if (key instanceof yaml.CfnFn) {
return true;
} else {
return key == k;
}
});
}
41 changes: 41 additions & 0 deletions src/validate/__tests__/arn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Error } from '../../types';
import arn from '../arn';

describe('arn', () => {
test.each([
['arn:aws:iam::123456789012:user/Development/product_1234/*'],
['arn:aws:s3:::my_corporate_bucket/*'],
['arn:aws:route53:::hostedzone/Z148QEXAMPLE8V'],
['arn:aws:route53:::change/C2RDJ5EXAMPLE2'],
['arn:aws:route53:::change/*'],
['arn:aws:route53::123456789012:domain/example.com'],
['arn:aws:route53resolver:us-west-2:123456789012:resolver-rule/rslvr-rr-5328a0899aexample'],
['arn:aws:route53resolver:us-west-2:123456789012:resolver-endpoint/rslvr-in-60b9fd8fdbexample'],
['arn:aws:ec2:us-east-1:123456789012:instance/*'],
['arn:aws-cn:ec2:us-east-1:123456789012:instance/*'],
])('valid %s', (value) => {
const errors: Error[] = [];
expect(arn([], value, (path, message) => { errors.push({ path, message, source: '' }) })).toBeTruthy();
expect(errors).toEqual([]);
});

test.each([
['arn:aws:iam::123456789012'],
['arn:aws:iam:::user/Development/product_1234/*'],
['arn:aws:iam:us-east-1:123456789012:user/Development/product_1234/*'],
['arn:aws:s3:us-east-1::my_corporate_bucket/*'],
['arn:aws:s3::124356789012:my_corporate_bucket/*'],
['arn:aws:route53:us-east-1::hostedzone/Z148QEXAMPLE8V'],
['arn:aws:route53::123456789012:hostedzone/Z148QEXAMPLE8V'],
['arn:foo:ec2:us-east-1:123456789012:instance/*'],
['foo:aws:ec2:us-east-1:123456789012:instance/*'],
['arn:ec2:us-east-1:123456789012:instance/*'],
['arn:aws:us-east-1:123456789012:instance/*'],
['arn:aws:ec2:123456789012:instance/*'],
['arn:aws:ec2:us-east-1:instance/*'],
])('invalid %s', (value) => {
const errors: Error[] = [];
expect(arn([], value, (path, message) => { errors.push({ path, message, source: '' }) })).toBeFalsy();
expect(errors.length).toBeGreaterThan(0);
});
});
Loading