Skip to content

Commit

Permalink
FT: User Policy validator
Browse files Browse the repository at this point in the history
Validates user policies against the JSON schema per AWS' documentation
 and validator implementation.
  • Loading branch information
rahulreddy committed Jul 7, 2016
1 parent 121ced6 commit 5796c33
Show file tree
Hide file tree
Showing 6 changed files with 882 additions and 8 deletions.
28 changes: 20 additions & 8 deletions errors/arsenalErrors.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,13 @@
"description": "The request signature we calculated does not match the signature you provided."
},
"_comment" : {
"note" : "This is an AWS S3 specific error. We are opting to use the more general 'ServiceUnavailable' error used throughout AWS (IAM/EC2) to have uniformity of error messages even though we are potentially compromising S3 compatibility.",
"note" : "This is an AWS S3 specific error. We are opting to use the more general 'ServiceUnavailable' error used throughout AWS (IAM/EC2) to have uniformity of error messages even though we are potentially compromising S3 compatibility.",
"ServiceUnavailable": {
"code": 503,
"description": "Reduce your request rate."
}
},
"ServiceUnavailable": {
"ServiceUnavailable": {
"code": 503,
"description": "The request has failed due to a temporary failure of the server."
},
Expand Down Expand Up @@ -419,6 +419,18 @@
"code": 500,
"description": "No account was found in Vault, please contact your system administrator."
},
"ValidationError": {
"code": 400,
"description": "The specified value is invalid."
},
"MalformedPolicyDocument": {
"code": 400,
"description": "Syntax errors in policy."
},
"InvalidInput": {
"code": 400,
"description": "The request was rejected because an invalid or out-of-range value was supplied for an input parameter."
},
"_comment": "-------------- Special non-AWS S3 errors --------------",
"MPUinProgress": {
"code": 409,
Expand Down Expand Up @@ -550,7 +562,7 @@
"SecretKeyDoesNotExist": {
"description": "secret key does not exist",
"code": 5030
},
},
"InvalidRegion": {
"description": "Region was not provided or is not recognized by the system",
"code": 5031
Expand Down Expand Up @@ -583,15 +595,15 @@
"BadUrl": {
"description": "url not ok",
"code": 5038
},
},
"BadClientIdList": {
"description": "client id list not ok'",
"code": 5039
},
},
"BadThumbprintList": {
"description": "thumbprint list not ok'",
"code": 5040
},
},
"BadObject": {
"description": "Object not ok'",
"code": 5041
Expand All @@ -600,12 +612,12 @@
"BadRole": {
"description": "role not ok",
"code": 5042
},
},
"_comment": "#### SamlpErrors ####",
"BadSamlp": {
"description": "samlp not ok",
"code": 5043
},
},
"BadMetadataDocument": {
"description": "metadata document not ok",
"code": 5044
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ module.exports = {
},
policies: {
evaluators: require('./lib/policyEvaluator/evaluator.js'),
validateUserPolicy: require('./lib/policy/policyValidator')
.validateUserPolicy,
},
};
117 changes: 117 additions & 0 deletions lib/policy/policyValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use strict'; // eslint-disable-line strict

const Ajv = require('ajv');
const userPolicySchema = require('./userPolicySchema');
const errors = require('../errors');

const ajValidate = new Ajv({ allErrors: true });
// compiles schema to functions and caches them for all cases
const userPolicyValidate = ajValidate.compile(userPolicySchema);

const errDict = {
required: {
Version: 'Policy document must be version 2012-10-17 or greater.',
Action: 'Policy statement must contain actions.',
},
pattern: {
Action: 'Actions/Conditions must be prefaced by a vendor,' +
' e.g., iam, sdb, ec2, etc.',
Resource: 'Resource must be in ARN format or "*".',
},
minItems: {
Resource: 'Policy statement must contain resources.',
},
};

// parse ajv errors and return early with the first relevant error
function _parseErrors(ajvErrors) {
// deep copy is needed as we have to assign custom error description
const parsedErr = Object.assign({}, errors.MalformedPolicyDocument);
parsedErr.description = 'Syntax errors in policy.';
ajvErrors.some(err => {
const resource = err.dataPath;
const field = err.params ? err.params.missingProperty : undefined;
const errType = err.keyword;
if (errType === 'type' && (resource === '.Statement' ||
resource === '.Statement.Resource' ||
resource === '.Statement.NotResource')) {
// skip this as this doesn't have enough error context
return false;
}
if (err.keyword === 'required' && field && errDict.required[field]) {
parsedErr.description = errDict.required[field];
} else if (err.keyword === 'pattern' &&
(resource === '.Statement.Action' ||
resource === '.Statement.NotAction')) {
parsedErr.description = errDict.pattern.Action;
} else if (err.keyword === 'pattern' &&
(resource === '.Statement.Resource' ||
resource === '.Statement.NotResource')) {
parsedErr.description = errDict.pattern.Resource;
} else if (err.keyword === 'minItems' &&
(resource === '.Statement.Resource' ||
resource === '.Statement.NotResource')) {
parsedErr.description = errDict.minItems.Resource;
}
return true;
});
return parsedErr;
}

// parse JSON safely without throwing an exception
function _safeJSONParse(s) {
try {
return JSON.parse(s);
} catch (e) {
return e;
}
}

// validates policy using the validation schema
function _validatePolicy(type, policy) {
if (type === 'user') {
const parseRes = _safeJSONParse(policy);
if (parseRes instanceof Error) {
return { error: Object.assign({}, errors.MalformedPolicyDocument),
valid: false };
}
userPolicyValidate(parseRes);
if (userPolicyValidate.errors) {
return { error: _parseErrors(userPolicyValidate.errors),
valid: false };
}
return { error: null, valid: true };
}
// TODO: add support for resource policies
return { error: errors.NotImplemented, valid: false };
}
/**
* @typedef ValidationResult
* @type Object
* @property {Array|null} error - list of validation errors or null
* @property {Bool} valid - true/false depending on the validation result
*/
/**
* Validates user policy
* @param {String} policy - policy json
* @returns {Object} - returns object with properties error and value
* @returns {ValidationResult} - result of the validation
*/
function validateUserPolicy(policy) {
return _validatePolicy('user', policy);
}

/**
* Validates resource policy
* @param {String} policy - policy json
* @returns {Object} - returns object with properties error and value
* @returns {ValidationResult} - result of the validation
*/
function validateResourcePolicy(policy) {
return _validatePolicy('resource', policy);
}

module.exports = {
validateUserPolicy,
validateResourcePolicy,
};
Loading

0 comments on commit 5796c33

Please sign in to comment.