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

Commit

Permalink
Fully validate Condition property
Browse files Browse the repository at this point in the history
Fixes #11
  • Loading branch information
jpb committed Feb 8, 2019
1 parent 2ed9ad5 commit 356b76c
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 7 deletions.
99 changes: 98 additions & 1 deletion src/validators/IAMPolicyDocumentValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as _ from 'lodash';
import { Validator, ValidationFn } from '../validate';
import * as validate from '../validate';
import { Path, Error } from '../types';
import { withSuggestion } from '../util';

const listOf = (fn: ValidationFn): ValidationFn => {
return (path: Path, value: any, errors: Error[]) => {
Expand All @@ -15,6 +16,102 @@ const stringOf = (allowedValues: string[]): ValidationFn => {
}
}

// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
const operators = [
'StringEquals',
'StringNotEquals',
'StringEqualsIgnoreCase',
'StringNotEqualsIgnoreCase',
'StringLike',
'StringNotLike',
'NumericEquals',
'NumericNotEquals',
'NumericLessThan',
'NumericLessThanEquals',
'NumericGreaterThan',
'NumericGreaterThanEquals',
'DateEquals',
'DateNotEquals',
'DateLessThan',
'DateLessThanEquals',
'DateGreaterThan',
'DateGreaterThanEquals',
'Bool',
'Null',
'BinaryEquals',
'IpAddress',
'NotIpAddress',
'ArnEquals',
'ArnLike',
'ArnNotEquals',
'ArnNotLike',
];

// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
const conditionKeys = [
'aws:CurrentTime',
'aws:EpochTime',
'aws:MultiFactorAuthAge',
'aws:MultiFactorAuthPresent',
'aws:SecureTransport',
'aws:UserAgent',
'aws:PrincipalOrgID',
/^aws:PrincipalTag\//,
'aws:PrincipalType',
'aws:Referer',
'aws:RequestedRegion',
/^aws:RequestTag\//,
'aws:SourceAccount',
'aws:SourceArn',
'aws:SourceIp',
'aws:SourceVpc',
'aws:SourceVpce',
'aws:TagKeys',
'aws:TokenIssueTime',
'aws:userid',
'aws:username',
];

const validateCondition = (path: Path, condition: any, errors: Error[]): boolean => {
if (validate.object(path, condition, errors)) {
_.forEach(condition, (value, key) => {
path = path.concat(key);
if (!_.includes(operators, key.toString())) {
const message = withSuggestion(
`key must be one of ${operators.join(', ')}, got ${key}`,
operators,
key
);
errors.push({ path, message });
}
if (validate.object(path, value, errors)) {
_.forEach(value, (conditionValue, conditionKey) => {
if(!_.includes(conditionKeys, conditionKey)) {
const keys = _.map(conditionKeys, (c) => String(c).replace(/[^A-Za-z:]/g, ''));
const message = withSuggestion(
`key must be one of ${keys.join(', ')}, got ${conditionKey}`,
keys,
conditionKey
);
errors.push({
path: path.concat(conditionKey),
message: message
});
validate.or(validate.string, listOf(validate.string))(
path.concat(conditionKey),
conditionValue,
errors
);
}
});
}
});
return true;
} else {
return false;
}
}

const validateStatement = (path: Path, value: any, errors: Error[]): boolean => {
const spec = {
Sid: [validate.optional, validate.string],
Expand All @@ -23,7 +120,7 @@ const validateStatement = (path: Path, value: any, errors: Error[]): boolean =>
Principal: [validate.optional, validate.or(validate.string, listOf(validate.string))],
Action: [validate.required, validate.or(validate.string, listOf(validate.string))],
Resource: [validate.optional, validate.or(validate.string, listOf(validate.string))],
Condition: [validate.optional, validate.object],
Condition: [validate.optional, validateCondition],
};
return validate.object(path, value, errors, spec);
}
Expand Down
63 changes: 57 additions & 6 deletions src/validators/__tests__/IAMPolicyDocumentValidator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('IAMPolicyDocumentValidator', () => {
Effect: 'Allow',
Principal: '',
Resource: '',
Condition: {}
Condition: { Bool: { 'aws:SecureTransport': true } }
}
]
}
Expand All @@ -44,12 +44,10 @@ describe('IAMPolicyDocumentValidator', () => {
Version: '',
Statement: [
{
Sid: '',
Action: '',
Effect: 'Deny',
Principal: [''],
Resource: [''],
Condition: {}
}
]
}
Expand Down Expand Up @@ -84,6 +82,62 @@ describe('IAMPolicyDocumentValidator', () => {
expect(lint(template)).toMatchSnapshot();
});

test('invalid conditional', () => {
const template = JSON.stringify({
Resources: {
Role: {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {},
Policies: [{
PolicyName: '',
PolicyDocument: {
Version: '',
Statement: [
{
Action: '',
Effect: 'Allow',
Resource: '',
Condition: { Foo: { 'aws:SecureTransport': true } }
}
]
}
}]
}
}
}
});
expect(lint(template)).toMatchSnapshot();
});

test('invalid condition key', () => {
const template = JSON.stringify({
Resources: {
Role: {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {},
Policies: [{
PolicyName: '',
PolicyDocument: {
Version: '',
Statement: [
{
Action: '',
Effect: 'Allow',
Resource: '',
Condition: { Bool: { 'cats': 'dogs' } }
}
]
}
}]
}
}
}
});
expect(lint(template)).toMatchSnapshot();
});

test('invalid types', () => {
const template = JSON.stringify({
Resources: {
Expand Down Expand Up @@ -127,12 +181,9 @@ describe('IAMPolicyDocumentValidator', () => {
Version: '',
Statement: [
{
Sid: '',
Action: '',
Effect: 'foo',
Principal: '',
Resource: '',
Condition: {}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`IAMPolicyDocumentValidator invalid condition key 1`] = `
Array [
Object {
"message": "key must be one of aws:CurrentTime, aws:EpochTime, aws:MultiFactorAuthAge, aws:MultiFactorAuthPresent, aws:SecureTransport, aws:UserAgent, aws:PrincipalOrgID, aws:PrincipalTag, aws:PrincipalType, aws:Referer, aws:RequestedRegion, aws:RequestTag, aws:SourceAccount, aws:SourceArn, aws:SourceIp, aws:SourceVpc, aws:SourceVpce, aws:TagKeys, aws:TokenIssueTime, aws:userid, aws:username, got cats",
"path": Array [
"Root",
"Resources",
"Role",
"Properties",
"Policies",
"0",
"PolicyDocument",
"Statement",
"0",
"Condition",
"Bool",
"cats",
],
},
]
`;

exports[`IAMPolicyDocumentValidator invalid conditional 1`] = `
Array [
Object {
"message": "key must be one of StringEquals, StringNotEquals, StringEqualsIgnoreCase, StringNotEqualsIgnoreCase, StringLike, StringNotLike, NumericEquals, NumericNotEquals, NumericLessThan, NumericLessThanEquals, NumericGreaterThan, NumericGreaterThanEquals, DateEquals, DateNotEquals, DateLessThan, DateLessThanEquals, DateGreaterThan, DateGreaterThanEquals, Bool, Null, BinaryEquals, IpAddress, NotIpAddress, ArnEquals, ArnLike, ArnNotEquals, ArnNotLike, got Foo, did you mean Bool?",
"path": Array [
"Root",
"Resources",
"Role",
"Properties",
"Policies",
"0",
"PolicyDocument",
"Statement",
"0",
"Condition",
"Foo",
],
},
]
`;

exports[`IAMPolicyDocumentValidator invalid effect 1`] = `
Array [
Object {
Expand Down

0 comments on commit 356b76c

Please sign in to comment.