Compare commits
88 Commits
c5055d4e72
...
a280cdd7db
Author | SHA1 | Date |
---|---|---|
bert-e | a280cdd7db | |
dependabot[bot] | 5080817830 | |
bert-e | 1e377c8801 | |
bert-e | 60fe8f09cc | |
Jonathan Gramain | 53a49c3747 | |
bert-e | 76498cf31c | |
bert-e | 003b4cfd27 | |
bert-e | b4763b541e | |
bert-e | 6f5614e461 | |
Dora Korpar | 9716781cbe | |
bert-e | a61c1914d6 | |
naren-scality | 1f5d33f006 | |
bert-e | 65065dd4e3 | |
bert-e | 3f82448a67 | |
bert-e | 6530f0ace4 | |
Dora Korpar | 16c4464864 | |
Dora Korpar | 41c2ebcd61 | |
Dora Korpar | 48eeb1bc72 | |
Dora Korpar | b77199b085 | |
Dora Korpar | 9b82caf129 | |
Ilke | 9c12ff241e | |
bert-e | 2125465761 | |
Ilke | b98c4b6dfd | |
Ilke | d06989a149 | |
bert-e | 0d49eff7e4 | |
Ilke | 5d78367d1c | |
bert-e | b30da5ca67 | |
bert-e | d699f78f91 | |
bert-e | 53cc766032 | |
bert-e | a82f9a2b70 | |
bert-e | d0367eb6d0 | |
bert-e | 9cac91c413 | |
bert-e | 6c62091622 | |
Dora Korpar | ef4a2dc077 | |
bert-e | 5dff968096 | |
bert-e | 2676b8384b | |
bert-e | 4544239269 | |
Dora Korpar | cc5b5e1971 | |
Ilke | f988270a0c | |
bert-e | 2b9ac57230 | |
bert-e | 336e42a9e0 | |
bert-e | fc0123ea5e | |
bert-e | 4d54b49c03 | |
Ilke | 65e92ebd92 | |
Ilke | d350f3db82 | |
bert-e | c848d1f13d | |
bert-e | eeb3ba970c | |
bert-e | c322c3b887 | |
Anurag Mittal | 2c892835cb | |
bert-e | 04b063da70 | |
Dora Korpar | 3d0c3bea2e | |
bert-e | 0d4efa67eb | |
bert-e | 3068ce38a0 | |
bert-e | 030a3f33f1 | |
Taylor McKinnon | ed1cc0f1bf | |
Taylor McKinnon | 80d231a3fa | |
bert-e | 2940500db6 | |
bert-e | 7aedc5f1f7 | |
bert-e | b99577eaeb | |
bert-e | 7f63022caa | |
Dora Korpar | 61d779083f | |
Dora Korpar | b0e56d64cd | |
Dora Korpar | 12ad2d9423 | |
Dora Korpar | 32c895b21a | |
Dora Korpar | 006f77dd28 | |
bert-e | c789d38df0 | |
Dora Korpar | 3b705a9434 | |
bert-e | 6c7de4124d | |
bert-e | 59803d7b67 | |
bert-e | 98737a69ba | |
Dora Korpar | 94653a14c4 | |
bert-e | 0f53c78ccd | |
bert-e | b03f5b80ac | |
bert-e | 933dc1da17 | |
bert-e | ae8dd1bb0e | |
Guillaume Gimenez | c6e06cc235 | |
bert-e | 57c971ef0f | |
bert-e | d8320da1bb | |
bert-e | d5d6243c01 | |
Guillaume Gimenez | 7fb16cbca6 | |
Guillaume Gimenez | 2a8a5dcb94 | |
Guillaume Gimenez | ff5d62f7de | |
bert-e | 97035596e1 | |
bert-e | 8c19dcdc7c | |
Guillaume Gimenez | cae763669b | |
Guillaume Gimenez | b3598c5d0e | |
bert-e | ac365eef18 | |
Guillaume Gimenez | f7aa22f9a6 |
|
@ -252,6 +252,10 @@
|
||||||
"code": 404,
|
"code": 404,
|
||||||
"description": "The lifecycle configuration does not exist."
|
"description": "The lifecycle configuration does not exist."
|
||||||
},
|
},
|
||||||
|
"NoSuchObjectLockConfiguration": {
|
||||||
|
"code": 404,
|
||||||
|
"description": "The specified object does not have a ObjectLock configuration."
|
||||||
|
},
|
||||||
"NoSuchWebsiteConfiguration": {
|
"NoSuchWebsiteConfiguration": {
|
||||||
"code": 404,
|
"code": 404,
|
||||||
"description": "The specified bucket does not have a website configuration"
|
"description": "The specified bucket does not have a website configuration"
|
||||||
|
@ -268,6 +272,10 @@
|
||||||
"code": 404,
|
"code": 404,
|
||||||
"description": "The replication configuration was not found"
|
"description": "The replication configuration was not found"
|
||||||
},
|
},
|
||||||
|
"ObjectLockConfigurationNotFoundError": {
|
||||||
|
"code": 404,
|
||||||
|
"description": "The object lock configuration was not found"
|
||||||
|
},
|
||||||
"NotImplemented": {
|
"NotImplemented": {
|
||||||
"code": 501,
|
"code": 501,
|
||||||
"description": "A header you provided implies functionality that is not implemented."
|
"description": "A header you provided implies functionality that is not implemented."
|
||||||
|
@ -463,6 +471,10 @@
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"description": "The request was rejected because an invalid or out-of-range value was supplied for an input parameter."
|
"description": "The request was rejected because an invalid or out-of-range value was supplied for an input parameter."
|
||||||
},
|
},
|
||||||
|
"MalformedPolicy": {
|
||||||
|
"code": 400,
|
||||||
|
"description": "This policy contains invalid Json"
|
||||||
|
},
|
||||||
"_comment": "-------------- Special non-AWS S3 errors --------------",
|
"_comment": "-------------- Special non-AWS S3 errors --------------",
|
||||||
"MPUinProgress": {
|
"MPUinProgress": {
|
||||||
"code": 409,
|
"code": 409,
|
||||||
|
|
7
index.js
7
index.js
|
@ -60,6 +60,8 @@ module.exports = {
|
||||||
RESTClient: require('./lib/network/rest/RESTClient'),
|
RESTClient: require('./lib/network/rest/RESTClient'),
|
||||||
},
|
},
|
||||||
RoundRobin: require('./lib/network/RoundRobin'),
|
RoundRobin: require('./lib/network/RoundRobin'),
|
||||||
|
kmip: require('./lib/network/kmip'),
|
||||||
|
kmipClient: require('./lib/network/kmip/Client'),
|
||||||
},
|
},
|
||||||
s3routes: {
|
s3routes: {
|
||||||
routes: require('./lib/s3routes/routes'),
|
routes: require('./lib/s3routes/routes'),
|
||||||
|
@ -69,6 +71,7 @@ module.exports = {
|
||||||
userMetadata: require('./lib/s3middleware/userMetadata'),
|
userMetadata: require('./lib/s3middleware/userMetadata'),
|
||||||
convertToXml: require('./lib/s3middleware/convertToXml'),
|
convertToXml: require('./lib/s3middleware/convertToXml'),
|
||||||
escapeForXml: require('./lib/s3middleware/escapeForXml'),
|
escapeForXml: require('./lib/s3middleware/escapeForXml'),
|
||||||
|
objectLegalHold: require('./lib/s3middleware/objectLegalHold'),
|
||||||
tagging: require('./lib/s3middleware/tagging'),
|
tagging: require('./lib/s3middleware/tagging'),
|
||||||
validateConditionalHeaders:
|
validateConditionalHeaders:
|
||||||
require('./lib/s3middleware/validateConditionalHeaders')
|
require('./lib/s3middleware/validateConditionalHeaders')
|
||||||
|
@ -84,6 +87,7 @@ module.exports = {
|
||||||
SubStreamInterface:
|
SubStreamInterface:
|
||||||
require('./lib/s3middleware/azureHelpers/SubStreamInterface'),
|
require('./lib/s3middleware/azureHelpers/SubStreamInterface'),
|
||||||
},
|
},
|
||||||
|
retention: require('./lib/s3middleware/objectRetention'),
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
metadata: {
|
metadata: {
|
||||||
|
@ -112,6 +116,9 @@ module.exports = {
|
||||||
require('./lib/models/ReplicationConfiguration'),
|
require('./lib/models/ReplicationConfiguration'),
|
||||||
LifecycleConfiguration:
|
LifecycleConfiguration:
|
||||||
require('./lib/models/LifecycleConfiguration'),
|
require('./lib/models/LifecycleConfiguration'),
|
||||||
|
BucketPolicy: require('./lib/models/BucketPolicy'),
|
||||||
|
ObjectLockConfiguration:
|
||||||
|
require('./lib/models/ObjectLockConfiguration'),
|
||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
StatsClient: require('./lib/metrics/StatsClient'),
|
StatsClient: require('./lib/metrics/StatsClient'),
|
||||||
|
|
|
@ -34,8 +34,13 @@ function check(request, log, data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
// 604800000 ms (seven days).
|
|
||||||
if (expirationTime > currentTime + 604800000) {
|
const preSignedURLExpiry = process.env.PRE_SIGN_URL_EXPIRY
|
||||||
|
&& !Number.isNaN(process.env.PRE_SIGN_URL_EXPIRY)
|
||||||
|
? Number.parseInt(process.env.PRE_SIGN_URL_EXPIRY, 10)
|
||||||
|
: constants.defaultPreSignedURLExpiry * 1000;
|
||||||
|
|
||||||
|
if (expirationTime > currentTime + preSignedURLExpiry) {
|
||||||
log.debug('expires parameter too far in future',
|
log.debug('expires parameter too far in future',
|
||||||
{ expires: request.query.Expires });
|
{ expires: request.query.Expires });
|
||||||
return { err: errors.AccessDenied };
|
return { err: errors.AccessDenied };
|
||||||
|
|
|
@ -72,4 +72,10 @@ module.exports = {
|
||||||
permittedCapitalizedBuckets: {
|
permittedCapitalizedBuckets: {
|
||||||
METADATA: true,
|
METADATA: true,
|
||||||
},
|
},
|
||||||
|
// Default expiration value of the S3 pre-signed URL duration
|
||||||
|
// 604800 seconds (seven days).
|
||||||
|
defaultPreSignedURLExpiry: 7 * 24 * 60 * 60,
|
||||||
|
// Regex for ISO-8601 formatted date
|
||||||
|
shortIso8601Regex: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/,
|
||||||
|
longIso8601Regex: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,11 @@ const assert = require('assert');
|
||||||
const { WebsiteConfiguration } = require('./WebsiteConfiguration');
|
const { WebsiteConfiguration } = require('./WebsiteConfiguration');
|
||||||
const ReplicationConfiguration = require('./ReplicationConfiguration');
|
const ReplicationConfiguration = require('./ReplicationConfiguration');
|
||||||
const LifecycleConfiguration = require('./LifecycleConfiguration');
|
const LifecycleConfiguration = require('./LifecycleConfiguration');
|
||||||
|
const ObjectLockConfiguration = require('./ObjectLockConfiguration');
|
||||||
|
const BucketPolicy = require('./BucketPolicy');
|
||||||
|
|
||||||
// WHEN UPDATING THIS NUMBER, UPDATE MODELVERSION.MD CHANGELOG
|
// WHEN UPDATING THIS NUMBER, UPDATE MODELVERSION.MD CHANGELOG
|
||||||
const modelVersion = 6;
|
const modelVersion = 7;
|
||||||
|
|
||||||
class BucketInfo {
|
class BucketInfo {
|
||||||
/**
|
/**
|
||||||
|
@ -47,12 +49,16 @@ class BucketInfo {
|
||||||
* @param {string[]} [cors[].exposeHeaders] - headers expose to applications
|
* @param {string[]} [cors[].exposeHeaders] - headers expose to applications
|
||||||
* @param {object} [replicationConfiguration] - replication configuration
|
* @param {object} [replicationConfiguration] - replication configuration
|
||||||
* @param {object} [lifecycleConfiguration] - lifecycle configuration
|
* @param {object} [lifecycleConfiguration] - lifecycle configuration
|
||||||
|
* @param {object} [bucketPolicy] - bucket policy
|
||||||
|
* @param {boolean} [objectLockEnabled] - true when object lock enabled
|
||||||
|
* @param {object} [objectLockConfiguration] - object lock configuration
|
||||||
*/
|
*/
|
||||||
constructor(name, owner, ownerDisplayName, creationDate,
|
constructor(name, owner, ownerDisplayName, creationDate,
|
||||||
mdBucketModelVersion, acl, transient, deleted,
|
mdBucketModelVersion, acl, transient, deleted,
|
||||||
serverSideEncryption, versioningConfiguration,
|
serverSideEncryption, versioningConfiguration,
|
||||||
locationConstraint, websiteConfiguration, cors,
|
locationConstraint, websiteConfiguration, cors,
|
||||||
replicationConfiguration, lifecycleConfiguration) {
|
replicationConfiguration, lifecycleConfiguration,
|
||||||
|
bucketPolicy, objectLockEnabled, objectLockConfiguration) {
|
||||||
assert.strictEqual(typeof name, 'string');
|
assert.strictEqual(typeof name, 'string');
|
||||||
assert.strictEqual(typeof owner, 'string');
|
assert.strictEqual(typeof owner, 'string');
|
||||||
assert.strictEqual(typeof ownerDisplayName, 'string');
|
assert.strictEqual(typeof ownerDisplayName, 'string');
|
||||||
|
@ -112,6 +118,12 @@ class BucketInfo {
|
||||||
if (lifecycleConfiguration) {
|
if (lifecycleConfiguration) {
|
||||||
LifecycleConfiguration.validateConfig(lifecycleConfiguration);
|
LifecycleConfiguration.validateConfig(lifecycleConfiguration);
|
||||||
}
|
}
|
||||||
|
if (bucketPolicy) {
|
||||||
|
BucketPolicy.validatePolicy(bucketPolicy);
|
||||||
|
}
|
||||||
|
if (objectLockConfiguration) {
|
||||||
|
ObjectLockConfiguration.validateConfig(objectLockConfiguration);
|
||||||
|
}
|
||||||
const aclInstance = acl || {
|
const aclInstance = acl || {
|
||||||
Canned: 'private',
|
Canned: 'private',
|
||||||
FULL_CONTROL: [],
|
FULL_CONTROL: [],
|
||||||
|
@ -137,6 +149,9 @@ class BucketInfo {
|
||||||
this._replicationConfiguration = replicationConfiguration || null;
|
this._replicationConfiguration = replicationConfiguration || null;
|
||||||
this._cors = cors || null;
|
this._cors = cors || null;
|
||||||
this._lifecycleConfiguration = lifecycleConfiguration || null;
|
this._lifecycleConfiguration = lifecycleConfiguration || null;
|
||||||
|
this._bucketPolicy = bucketPolicy || null;
|
||||||
|
this._objectLockEnabled = objectLockEnabled || false;
|
||||||
|
this._objectLockConfiguration = objectLockConfiguration || null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -160,6 +175,9 @@ class BucketInfo {
|
||||||
cors: this._cors,
|
cors: this._cors,
|
||||||
replicationConfiguration: this._replicationConfiguration,
|
replicationConfiguration: this._replicationConfiguration,
|
||||||
lifecycleConfiguration: this._lifecycleConfiguration,
|
lifecycleConfiguration: this._lifecycleConfiguration,
|
||||||
|
bucketPolicy: this._bucketPolicy,
|
||||||
|
objectLockEnabled: this._objectLockEnabled,
|
||||||
|
objectLockConfiguration: this._objectLockConfiguration,
|
||||||
};
|
};
|
||||||
if (this._websiteConfiguration) {
|
if (this._websiteConfiguration) {
|
||||||
bucketInfos.websiteConfiguration =
|
bucketInfos.websiteConfiguration =
|
||||||
|
@ -180,7 +198,9 @@ class BucketInfo {
|
||||||
obj.creationDate, obj.mdBucketModelVersion, obj.acl,
|
obj.creationDate, obj.mdBucketModelVersion, obj.acl,
|
||||||
obj.transient, obj.deleted, obj.serverSideEncryption,
|
obj.transient, obj.deleted, obj.serverSideEncryption,
|
||||||
obj.versioningConfiguration, obj.locationConstraint, websiteConfig,
|
obj.versioningConfiguration, obj.locationConstraint, websiteConfig,
|
||||||
obj.cors, obj.replicationConfiguration, obj.lifecycleConfiguration);
|
obj.cors, obj.replicationConfiguration, obj.lifecycleConfiguration,
|
||||||
|
obj.bucketPolicy, obj.objectLockEnabled,
|
||||||
|
obj.objectLockConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,7 +223,9 @@ class BucketInfo {
|
||||||
data._transient, data._deleted, data._serverSideEncryption,
|
data._transient, data._deleted, data._serverSideEncryption,
|
||||||
data._versioningConfiguration, data._locationConstraint,
|
data._versioningConfiguration, data._locationConstraint,
|
||||||
data._websiteConfiguration, data._cors,
|
data._websiteConfiguration, data._cors,
|
||||||
data._replicationConfiguration, data._lifecycleConfiguration);
|
data._replicationConfiguration, data._lifecycleConfiguration,
|
||||||
|
data._bucketPolicy, data._objectLockEnabled,
|
||||||
|
data._objectLockConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -331,6 +353,40 @@ class BucketInfo {
|
||||||
this._lifecycleConfiguration = lifecycleConfiguration;
|
this._lifecycleConfiguration = lifecycleConfiguration;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get bucket policy statement
|
||||||
|
* @return {object|null} bucket policy statement or `null` if the bucket
|
||||||
|
* does not have a bucket policy
|
||||||
|
*/
|
||||||
|
getBucketPolicy() {
|
||||||
|
return this._bucketPolicy;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set bucket policy statement
|
||||||
|
* @param {object} bucketPolicy - bucket policy
|
||||||
|
* @return {BucketInfo} - bucket info instance
|
||||||
|
*/
|
||||||
|
setBucketPolicy(bucketPolicy) {
|
||||||
|
this._bucketPolicy = bucketPolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get object lock configuration
|
||||||
|
* @return {object|null} object lock configuration information or `null` if
|
||||||
|
* the bucket does not have an object lock configuration
|
||||||
|
*/
|
||||||
|
getObjectLockConfiguration() {
|
||||||
|
return this._objectLockConfiguration;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set object lock configuration
|
||||||
|
* @param {object} objectLockConfiguration - object lock information
|
||||||
|
* @return {BucketInfo} - bucket info instance
|
||||||
|
*/
|
||||||
|
setObjectLockConfiguration(objectLockConfiguration) {
|
||||||
|
this._objectLockConfiguration = objectLockConfiguration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get cors resource
|
* Get cors resource
|
||||||
* @return {object[]} cors
|
* @return {object[]} cors
|
||||||
|
@ -521,6 +577,13 @@ class BucketInfo {
|
||||||
return this._versioningConfiguration &&
|
return this._versioningConfiguration &&
|
||||||
this._versioningConfiguration.Status === 'Enabled';
|
this._versioningConfiguration.Status === 'Enabled';
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Check is object lock enabled.
|
||||||
|
* @return {boolean} - depending on whether object lock is enabled
|
||||||
|
*/
|
||||||
|
isObjectLockEnabled() {
|
||||||
|
return !!this._objectLockEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BucketInfo;
|
module.exports = BucketInfo;
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const errors = require('../errors');
|
||||||
|
const { validateResourcePolicy } = require('../policy/policyValidator');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format of json policy:
|
||||||
|
* {
|
||||||
|
* "Id": "Policy id",
|
||||||
|
* "Version": "version date",
|
||||||
|
* "Statement": [
|
||||||
|
* {
|
||||||
|
* "Sid": "Statement id",
|
||||||
|
* "Effect": "Allow",
|
||||||
|
* "Principal": "*",
|
||||||
|
* "Action": "s3:*",
|
||||||
|
* "Resource": "arn:aws:s3:::examplebucket/bucket2/object"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "Sid": "Statement id",
|
||||||
|
* "Effect": "Deny",
|
||||||
|
* "Principal": {
|
||||||
|
* "AWS": ["arn:aws:iam::<account_id>", "different_account_id"]
|
||||||
|
* },
|
||||||
|
* "Action": [ "s3:*" ],
|
||||||
|
* "Resource": [
|
||||||
|
* "arn:aws:s3:::examplebucket", "arn:aws:s3:::otherbucket/*"],
|
||||||
|
* "Condition": {
|
||||||
|
* "StringNotLike": {
|
||||||
|
* "aws:Referer": [
|
||||||
|
* "http://www.example.com/", "http://example.com/*"]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
const objectActions = [
|
||||||
|
's3:AbortMultipartUpload',
|
||||||
|
's3:DeleteObject',
|
||||||
|
's3:DeleteObjectTagging',
|
||||||
|
's3:GetObject',
|
||||||
|
's3:GetObjectAcl',
|
||||||
|
's3:GetObjectTagging',
|
||||||
|
's3:ListMultipartUploadParts',
|
||||||
|
's3:PutObject',
|
||||||
|
's3:PutObjectAcl',
|
||||||
|
's3:PutObjectTagging',
|
||||||
|
];
|
||||||
|
|
||||||
|
class BucketPolicy {
|
||||||
|
/**
|
||||||
|
* Create a Bucket Policy instance
|
||||||
|
* @param {string} json - the json policy
|
||||||
|
* @return {object} - BucketPolicy instance
|
||||||
|
*/
|
||||||
|
constructor(json) {
|
||||||
|
this._json = json;
|
||||||
|
this._policy = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bucket policy
|
||||||
|
* @return {object} - the bucket policy or error
|
||||||
|
*/
|
||||||
|
getBucketPolicy() {
|
||||||
|
const policy = this._getPolicy();
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bucket policy array
|
||||||
|
* @return {object} - contains error if policy validation fails
|
||||||
|
*/
|
||||||
|
_getPolicy() {
|
||||||
|
if (!this._json || this._json === '') {
|
||||||
|
return { error: errors.MalformedPolicy.customizeDescription(
|
||||||
|
'request json is empty or undefined') };
|
||||||
|
}
|
||||||
|
const validSchema = validateResourcePolicy(this._json);
|
||||||
|
if (validSchema.error) {
|
||||||
|
return validSchema;
|
||||||
|
}
|
||||||
|
this._setStatementArray();
|
||||||
|
const valAcRes = this._validateActionResource();
|
||||||
|
if (valAcRes.error) {
|
||||||
|
return valAcRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setStatementArray() {
|
||||||
|
this._policy = JSON.parse(this._json);
|
||||||
|
if (!Array.isArray(this._policy.Statement)) {
|
||||||
|
const statement = this._policy.Statement;
|
||||||
|
this._policy.Statement = [statement];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate action and resource are compatible
|
||||||
|
* @return {error} - contains error or empty obj
|
||||||
|
*/
|
||||||
|
_validateActionResource() {
|
||||||
|
const invalid = this._policy.Statement.every(s => {
|
||||||
|
const actions = typeof s.Action === 'string' ?
|
||||||
|
[s.Action] : s.Action;
|
||||||
|
const resources = typeof s.Resource === 'string' ?
|
||||||
|
[s.Resource] : s.Resource;
|
||||||
|
const objectAction = actions.some(a =>
|
||||||
|
a.includes('Object') || objectActions.includes(a));
|
||||||
|
// wildcardObjectAction checks for actions such as 's3:*' or
|
||||||
|
// 's3:Put*' but will return false for actions such as
|
||||||
|
// 's3:PutBucket*'
|
||||||
|
const wildcardObjectAction = actions.some(
|
||||||
|
a => a.includes('*') && !a.includes('Bucket'));
|
||||||
|
const objectResource = resources.some(r => r.includes('/'));
|
||||||
|
return ((objectAction && !objectResource) ||
|
||||||
|
(objectResource && !objectAction && !wildcardObjectAction));
|
||||||
|
});
|
||||||
|
if (invalid) {
|
||||||
|
return { error: errors.MalformedPolicy.customizeDescription(
|
||||||
|
'Action does not apply to any resource(s) in statement') };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call resource policy schema validation function
|
||||||
|
* @param {object} policy - the bucket policy object to validate
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
static validatePolicy(policy) {
|
||||||
|
// only the BucketInfo constructor calls this function
|
||||||
|
// and BucketInfo will always be passed an object
|
||||||
|
const validated = validateResourcePolicy(JSON.stringify(policy));
|
||||||
|
assert.deepStrictEqual(validated, { error: null, valid: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BucketPolicy;
|
|
@ -0,0 +1,236 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const errors = require('../errors');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format of xml request:
|
||||||
|
*
|
||||||
|
* <ObjectLockConfiguration>
|
||||||
|
* <ObjectLockEnabled>Enabled</ObjectLockEnabled>
|
||||||
|
* <Rule>
|
||||||
|
* <DefaultRetention>
|
||||||
|
* <Mode>GOVERNANCE|COMPLIANCE</Mode>
|
||||||
|
* <Days>1</Days>
|
||||||
|
* <Years>1</Years>
|
||||||
|
* </DefaultRetention>
|
||||||
|
* </Rule>
|
||||||
|
* </ObjectLockConfiguration>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format of config:
|
||||||
|
*
|
||||||
|
* config = {
|
||||||
|
* rule: {
|
||||||
|
* mode: GOVERNANCE|COMPLIANCE,
|
||||||
|
* days|years: integer,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class ObjectLockConfiguration {
|
||||||
|
/**
|
||||||
|
* Create an Object Lock Configuration instance
|
||||||
|
* @param {string} xml - the parsed configuration xml
|
||||||
|
* @return {object} - ObjectLockConfiguration instance
|
||||||
|
*/
|
||||||
|
constructor(xml) {
|
||||||
|
this._parsedXml = xml;
|
||||||
|
this._config = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the object lock configuration
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
getValidatedObjectLockConfiguration() {
|
||||||
|
const validConfig = this._parseObjectLockConfig();
|
||||||
|
if (validConfig.error) {
|
||||||
|
this._config.error = validConfig.error;
|
||||||
|
}
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that mode is valid
|
||||||
|
* @param {array} mode - array containing mode value
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseMode(mode) {
|
||||||
|
const validMode = {};
|
||||||
|
const expectedModes = ['GOVERNANCE', 'COMPLIANCE'];
|
||||||
|
if (!mode || !mode[0]) {
|
||||||
|
validMode.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Mode');
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
if (mode.length > 1) {
|
||||||
|
validMode.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one Mode');
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
if (!expectedModes.includes(mode[0])) {
|
||||||
|
validMode.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Mode request xml must be one of "GOVERNANCE", "COMPLIANCE"');
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
validMode.mode = mode[0];
|
||||||
|
return validMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that time limit is valid
|
||||||
|
* @param {object} dr - DefaultRetention object containing days or years
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseTime(dr) {
|
||||||
|
const validTime = {};
|
||||||
|
if (dr.Days && dr.Years) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains both Days and Years');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
const timeType = dr.Days ? 'Days' : 'Years';
|
||||||
|
if (!dr[timeType] || !dr[timeType][0]) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Days or Years');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
if (dr[timeType].length > 1) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one retention period');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
const timeValue = Number.parseInt(dr[timeType][0], 10);
|
||||||
|
if (Number.isNaN(timeValue)) {
|
||||||
|
validTime.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain valid retention period');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
if (timeValue < 1) {
|
||||||
|
validTime.error = errors.InvalidArgument.customizeDescription(
|
||||||
|
'retention period must be a positive integer');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
if ((timeType === 'Days' && timeValue > 36500) ||
|
||||||
|
(timeType === 'Years' && timeValue > 100)) {
|
||||||
|
validTime.error = errors.InvalidArgument.customizeDescription(
|
||||||
|
'retention period is too large');
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
validTime.timeType = timeType.toLowerCase();
|
||||||
|
validTime.timeValue = timeValue;
|
||||||
|
return validTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that object lock configuration is valid
|
||||||
|
* @return {object} - contains error if parsing failed
|
||||||
|
*/
|
||||||
|
_parseObjectLockConfig() {
|
||||||
|
const validConfig = {};
|
||||||
|
if (!this._parsedXml || this._parsedXml === '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml is undefined or empty');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const objectLockConfig = this._parsedXml.ObjectLockConfiguration;
|
||||||
|
if (!objectLockConfig || objectLockConfig === '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not include ObjectLockConfiguration');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const objectLockEnabled = objectLockConfig.ObjectLockEnabled;
|
||||||
|
if (!objectLockEnabled || objectLockEnabled[0] !== 'Enabled') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not include valid ObjectLockEnabled');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const ruleArray = objectLockConfig.Rule;
|
||||||
|
if (ruleArray.length > 1) {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one rule');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drArray = ruleArray[0].DefaultRetention;
|
||||||
|
if (!drArray || !drArray[0] || drArray[0] === '') {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Rule request xml does not contain DefaultRetention');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
if (!drArray[0].Mode || (!drArray[0].Days && !drArray[0].Years)) {
|
||||||
|
validConfig.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'DefaultRetention request xml does not contain Mode or ' +
|
||||||
|
'retention period (Days or Years)');
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const validMode = this._parseMode(drArray[0].Mode);
|
||||||
|
if (validMode.error) {
|
||||||
|
validConfig.error = validMode.error;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
const validTime = this._parseTime(drArray[0]);
|
||||||
|
if (validTime.error) {
|
||||||
|
validConfig.error = validTime.error;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config.rule = {};
|
||||||
|
this._config.rule.mode = validMode.mode;
|
||||||
|
this._config.rule[validTime.timeType] = validTime.timeValue;
|
||||||
|
return validConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the bucket metadata object lock configuration structure and
|
||||||
|
* value types
|
||||||
|
* @param {object} config - The object lock configuration to validate
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
static validateConfig(config) {
|
||||||
|
assert.strictEqual(typeof config, 'object');
|
||||||
|
const rule = config.rule;
|
||||||
|
assert.strictEqual(typeof rule, 'object');
|
||||||
|
assert.strictEqual(typeof rule.mode, 'string');
|
||||||
|
if (rule.days) {
|
||||||
|
assert.strictEqual(typeof rule.days, 'number');
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(typeof rule.years, 'number');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the XML representation of the configuration object
|
||||||
|
* @param {object} config - The bucket object lock configuration
|
||||||
|
* @return {string} - The XML representation of the configuration
|
||||||
|
*/
|
||||||
|
static getConfigXML(config) {
|
||||||
|
// object lock is enabled on the bucket but object lock configuration
|
||||||
|
// not set
|
||||||
|
if (config.rule === undefined) {
|
||||||
|
return '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||||
|
'<ObjectLockConfiguration ' +
|
||||||
|
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
}
|
||||||
|
const { days, years, mode } = config.rule;
|
||||||
|
const Mode = `<Mode>${mode}</Mode>`;
|
||||||
|
const Days = days !== undefined ? `<Days>${days}</Days>` : '';
|
||||||
|
const Years = years !== undefined ? `<Years>${years}</Years>` : '';
|
||||||
|
const Time = Days || Years;
|
||||||
|
return '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||||
|
'<ObjectLockConfiguration ' +
|
||||||
|
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
|
||||||
|
'<Rule>' +
|
||||||
|
'<DefaultRetention>' +
|
||||||
|
`${Mode}` +
|
||||||
|
`${Time}` +
|
||||||
|
'</DefaultRetention>' +
|
||||||
|
'</Rule>' +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ObjectLockConfiguration;
|
|
@ -883,6 +883,46 @@ class ObjectMD {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set object legal hold status
|
||||||
|
* @param {boolean} legalHold - true if legal hold is 'ON' false if 'OFF'
|
||||||
|
* @return {ObjectMD} itself
|
||||||
|
*/
|
||||||
|
setLegalHold(legalHold) {
|
||||||
|
this._data.legalHold = legalHold || false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get object legal hold status
|
||||||
|
* @return {boolean} legal hold status
|
||||||
|
*/
|
||||||
|
getLegalHold() {
|
||||||
|
return this._data.legalHold || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set object retention information
|
||||||
|
* @param {object} retentionInfo - retention information object
|
||||||
|
* @return {ObjectMD} itself
|
||||||
|
*/
|
||||||
|
setRetentionInfo(retentionInfo) {
|
||||||
|
const { mode, retainUntilDate } = retentionInfo;
|
||||||
|
this._data.retentionInfo = {
|
||||||
|
mode,
|
||||||
|
retainUntilDate,
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns object retention information
|
||||||
|
* @return {object} metadata object
|
||||||
|
*/
|
||||||
|
getRetentionInfo() {
|
||||||
|
return this._data.retentionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns metadata object
|
* Returns metadata object
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,579 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
const async = require('async');
|
||||||
|
|
||||||
|
const errors = require('../../errors');
|
||||||
|
const TTLVCodec = require('./codec/ttlv.js');
|
||||||
|
const TlsTransport = require('./transport/tls.js');
|
||||||
|
const KMIP = require('.');
|
||||||
|
|
||||||
|
const CRYPTOGRAPHIC_OBJECT_TYPE = 'Symmetric Key';
|
||||||
|
const CRYPTOGRAPHIC_ALGORITHM = 'AES';
|
||||||
|
const CRYPTOGRAPHIC_CIPHER_MODE = 'CBC';
|
||||||
|
const CRYPTOGRAPHIC_PADDING_METHOD = 'PKCS5';
|
||||||
|
const CRYPTOGRAPHIC_LENGTH = 256;
|
||||||
|
const CRYPTOGRAPHIC_USAGE_MASK = ['Encrypt', 'Decrypt'];
|
||||||
|
const CRYPTOGRAPHIC_DEFAULT_IV = Buffer.alloc(16).fill(0);
|
||||||
|
|
||||||
|
const searchFilter = {
|
||||||
|
protocolVersionMajor:
|
||||||
|
'Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Protocol Version/' +
|
||||||
|
'Protocol Version Major',
|
||||||
|
protocolVersionMinor:
|
||||||
|
'Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Protocol Version/' +
|
||||||
|
'Protocol Version Minor',
|
||||||
|
extensionName:
|
||||||
|
'Response Message/Batch Item/Response Payload' +
|
||||||
|
'/Extension Information/Extension Name',
|
||||||
|
extensionTag:
|
||||||
|
'Response Message/Batch Item/Response Payload' +
|
||||||
|
'/Extension Information/Extension Tag',
|
||||||
|
vendorIdentification:
|
||||||
|
'Response Message/Batch Item/Response Payload/Vendor Identification',
|
||||||
|
serverInformation:
|
||||||
|
'Response Message/Batch Item/Response Payload/Server Information',
|
||||||
|
operation:
|
||||||
|
'Response Message/Batch Item/Response Payload/Operation',
|
||||||
|
objectType:
|
||||||
|
'Response Message/Batch Item/Response Payload/Object Type',
|
||||||
|
uniqueIdentifier:
|
||||||
|
'Response Message/Batch Item/Response Payload/Unique Identifier',
|
||||||
|
data:
|
||||||
|
'Response Message/Batch Item/Response Payload/Data',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize errors according to arsenal definitions
|
||||||
|
* @param {string | Error} err - an Error instance or a message string
|
||||||
|
* @returns {arsenal.errors} - arsenal error
|
||||||
|
*/
|
||||||
|
function _arsenalError(err) {
|
||||||
|
const messagePrefix = 'KMIP:';
|
||||||
|
if (typeof err === 'string') {
|
||||||
|
return errors.InternalError
|
||||||
|
.customizeDescription(`${messagePrefix} ${err}`);
|
||||||
|
} else if (err instanceof Error) {
|
||||||
|
return errors.InternalError
|
||||||
|
.customizeDescription(`${messagePrefix} ${err.message}`);
|
||||||
|
}
|
||||||
|
return errors.InternalError
|
||||||
|
.customizeDescription(`${messagePrefix} Unspecified error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negotiate with the server the use of a recent version of the protocol and
|
||||||
|
* update the low level driver with this new knowledge.
|
||||||
|
* @param {Object} client - The Client instance
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {Function} cb - The callback triggered after the negotiation.
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
function _negotiateProtocolVersion(client, logger, cb) {
|
||||||
|
return client.kmip.request(logger, 'Discover Versions', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 4),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 3),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 2),
|
||||||
|
]),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::negotiateProtocolVersion',
|
||||||
|
{ error,
|
||||||
|
vendorIdentification: client.vendorIdentification });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const majorVersions =
|
||||||
|
response.lookup(searchFilter.protocolVersionMajor);
|
||||||
|
const minorVersions =
|
||||||
|
response.lookup(searchFilter.protocolVersionMinor);
|
||||||
|
if (majorVersions.length === 0 ||
|
||||||
|
majorVersions.length !== minorVersions.length) {
|
||||||
|
const error = _arsenalError('No suitable protocol version');
|
||||||
|
logger.error('KMIP::negotiateProtocolVersion',
|
||||||
|
{ error,
|
||||||
|
vendorIdentification: client.vendorIdentification });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
client.kmip.changeProtocolVersion(majorVersions[0], minorVersions[0]);
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain from the server the various extensions defined by the vendor
|
||||||
|
* and update the low level driver with this new knowledge.
|
||||||
|
* @param {Object} client - The Client instance
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {Function} cb - The callback triggered after the extension mapping
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
function _mapExtensions(client, logger, cb) {
|
||||||
|
return client.kmip.request(logger, 'Query', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension Map'),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::mapExtensions',
|
||||||
|
{ error,
|
||||||
|
vendorIdentification: client.vendorIdentification });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const extensionNames = response.lookup(searchFilter.extensionName);
|
||||||
|
const extensionTags = response.lookup(searchFilter.extensionTag);
|
||||||
|
if (extensionNames.length !== extensionTags.length) {
|
||||||
|
const error = _arsenalError('Inconsistent extension list');
|
||||||
|
logger.error('KMIP::mapExtensions',
|
||||||
|
{ error,
|
||||||
|
vendorIdentification: client.vendorIdentification });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
extensionNames.forEach((extensionName, idx) => {
|
||||||
|
client.kmip.mapExtension(extensionName, extensionTags[idx]);
|
||||||
|
});
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the Server information and identify its vendor
|
||||||
|
* @param {Object} client - The Client instance
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {Function} cb - The callback triggered after the information discovery
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
function _queryServerInformation(client, logger, cb) {
|
||||||
|
client.kmip.request(logger, 'Query', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Server Information'),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.warn('KMIP::queryServerInformation',
|
||||||
|
{ error });
|
||||||
|
/* no error returned, caller can keep going */
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
client._setVendorIdentification(
|
||||||
|
response.lookup(searchFilter.vendorIdentification)[0]);
|
||||||
|
client._setServerInformation(
|
||||||
|
JSON.stringify(response.lookup(searchFilter.serverInformation)[0]));
|
||||||
|
|
||||||
|
logger.info('KMIP Server identified',
|
||||||
|
{ vendorIdentification: client.vendorIdentification,
|
||||||
|
serverInformation: client.serverInformation,
|
||||||
|
negotiatedProtocolVersion: client.kmip.protocolVersion });
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the Server for the supported operations and managed object types.
|
||||||
|
* The fact that a server doesn't announce the support for a required feature
|
||||||
|
* is not a show stopper because some vendor support more or less what they
|
||||||
|
* announce. If a subsequent request fails, this information can be used to
|
||||||
|
* figure out the reason for the failure.
|
||||||
|
* @param {Object} client - The Client instance
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {Function} cb - The callback triggered after the information discovery
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
function _queryOperationsAndObjects(client, logger, cb) {
|
||||||
|
return client.kmip.request(logger, 'Query', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::queryOperationsAndObjects',
|
||||||
|
{ error,
|
||||||
|
vendorIdentification: client.vendorIdentification });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const supportedOperations = response.lookup(searchFilter.operation);
|
||||||
|
const supportedObjectTypes = response.lookup(searchFilter.objectType);
|
||||||
|
const supportsEncrypt = supportedOperations.includes('Encrypt');
|
||||||
|
const supportsDecrypt = supportedOperations.includes('Decrypt');
|
||||||
|
const supportsActivate = supportedOperations.includes('Activate');
|
||||||
|
const supportsRevoke = supportedOperations.includes('Revoke');
|
||||||
|
const supportsCreate = supportedOperations.includes('Create');
|
||||||
|
const supportsDestroy = supportedOperations.includes('Destroy');
|
||||||
|
const supportsQuery = supportedOperations.includes('Query');
|
||||||
|
const supportsSymmetricKeys =
|
||||||
|
supportedObjectTypes.includes('Symmetric Key');
|
||||||
|
if (!supportsEncrypt || !supportsDecrypt ||
|
||||||
|
!supportsActivate || !supportsRevoke ||
|
||||||
|
!supportsCreate || !supportsDestroy ||
|
||||||
|
!supportsQuery || !supportsSymmetricKeys) {
|
||||||
|
/* This should not be considered as an error since some vendors
|
||||||
|
* are not consistent between what they really support and what
|
||||||
|
* they announce to support.
|
||||||
|
*/
|
||||||
|
logger.warn('KMIP::queryOperationsAndObjects: ' +
|
||||||
|
'The KMIP Server announces that it ' +
|
||||||
|
'does not support all of the required features',
|
||||||
|
{ vendorIdentification: client.vendorIdentification,
|
||||||
|
serverInformation: client.serverInformation,
|
||||||
|
supportsEncrypt, supportsDecrypt,
|
||||||
|
supportsActivate, supportsRevoke,
|
||||||
|
supportsCreate, supportsDestroy,
|
||||||
|
supportsQuery, supportsSymmetricKeys });
|
||||||
|
} else {
|
||||||
|
logger.info('KMIP Server provides the necessary feature set',
|
||||||
|
{ vendorIdentification: client.vendorIdentification });
|
||||||
|
}
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
/**
|
||||||
|
* Construct a high level KMIP driver suitable for cloudserver
|
||||||
|
* @param {Object} options - Instance options
|
||||||
|
* @param {Object} options.kmip - Low level driver options
|
||||||
|
* @param {Object} options.kmip.client - This high level driver options
|
||||||
|
* @param {Object} options.kmip.client.compoundCreateActivate -
|
||||||
|
* Depends on the server's ability. False offers the best
|
||||||
|
* compatibility. True does not offer a significant
|
||||||
|
* performance gain, but can be useful in case of unreliable
|
||||||
|
* time synchronization between the client and the server.
|
||||||
|
* @param {Object} options.kmip.client.bucketNameAttributeName -
|
||||||
|
* Depends on the server's ability. Not specifying this
|
||||||
|
* offers the best compatibility and disable the attachement
|
||||||
|
* of the bucket name as a key attribute.
|
||||||
|
* @param {Object} options.kmip.codec - KMIP Codec options
|
||||||
|
* @param {Object} options.kmip.transport - KMIP Transport options
|
||||||
|
* @param {Class} CodecClass - diversion for the Codec class,
|
||||||
|
* defaults to TTLVCodec
|
||||||
|
* @param {Class} TransportClass - diversion for the Transport class,
|
||||||
|
* defaults to TlsTransport
|
||||||
|
*/
|
||||||
|
constructor(options, CodecClass, TransportClass) {
|
||||||
|
this.options = options.kmip.client || {};
|
||||||
|
this.vendorIdentification = '';
|
||||||
|
this.serverInformation = [];
|
||||||
|
this.kmip = new KMIP(CodecClass || TTLVCodec,
|
||||||
|
TransportClass || TlsTransport,
|
||||||
|
options);
|
||||||
|
this.kmip.registerHandshakeFunction((logger, cb) => {
|
||||||
|
this._kmipHandshake(logger, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this client with the vendor identification of the server
|
||||||
|
* @param {String} vendorIdentification - Vendor identification string
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_setVendorIdentification(vendorIdentification) {
|
||||||
|
this.vendorIdentification = vendorIdentification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this client with the information about the server
|
||||||
|
* @param {Object} serverInformation - Server information object
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_setServerInformation(serverInformation) {
|
||||||
|
this.serverInformation = serverInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the KMIP level handshake with the server
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {Function} cb - Callback to be triggered at the end of the
|
||||||
|
* handshake. cb(err: Error)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_kmipHandshake(logger, cb) {
|
||||||
|
return async.waterfall([
|
||||||
|
next => _negotiateProtocolVersion(this, logger, next),
|
||||||
|
next => _mapExtensions(this, logger, next),
|
||||||
|
next => _queryServerInformation(this, logger, next),
|
||||||
|
next => _queryOperationsAndObjects(this, logger, next),
|
||||||
|
], cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate a cryptographic key managed by the server,
|
||||||
|
* for a specific bucket. This is a required action to perform after
|
||||||
|
* the key creation.
|
||||||
|
* @param {string} keyIdentifier - The bucket key Id
|
||||||
|
* @param {object} logger - Werelog logger object
|
||||||
|
* @param {function} cb - The callback(err: Error)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_activateBucketKey(keyIdentifier, logger, cb) {
|
||||||
|
return this.kmip.request(logger, 'Activate', [
|
||||||
|
KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::_activateBucketKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
if (uniqueIdentifier !== keyIdentifier) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server did not return the expected identifier');
|
||||||
|
logger.error('KMIP::cipherDataKey',
|
||||||
|
{ error, uniqueIdentifier });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb(null, keyIdentifier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new cryptographic key managed by the server,
|
||||||
|
* for a specific bucket
|
||||||
|
* @param {string} bucketName - The bucket name
|
||||||
|
* @param {object} logger - Werelog logger object
|
||||||
|
* @param {function} cb - The callback(err: Error, bucketKeyId: String)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
createBucketKey(bucketName, logger, cb) {
|
||||||
|
const attributes = [];
|
||||||
|
if (!!this.options.bucketNameAttributeName) {
|
||||||
|
attributes.push(KMIP.Attribute('TextString',
|
||||||
|
this.options.bucketNameAttributeName,
|
||||||
|
bucketName));
|
||||||
|
}
|
||||||
|
attributes.push(...[
|
||||||
|
KMIP.Attribute('Enumeration', 'Cryptographic Algorithm',
|
||||||
|
CRYPTOGRAPHIC_ALGORITHM),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Length',
|
||||||
|
CRYPTOGRAPHIC_LENGTH),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Usage Mask',
|
||||||
|
this.kmip.encodeMask('Cryptographic Usage Mask',
|
||||||
|
CRYPTOGRAPHIC_USAGE_MASK))]);
|
||||||
|
if (this.options.compoundCreateActivate) {
|
||||||
|
attributes.push(KMIP.Attribute('Date-Time', 'Activation Date',
|
||||||
|
new Date(Date.UTC())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.kmip.request(logger, 'Create', [
|
||||||
|
KMIP.Enumeration('Object Type', CRYPTOGRAPHIC_OBJECT_TYPE),
|
||||||
|
KMIP.Structure('Template-Attribute', attributes),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::createBucketKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const createdObjectType =
|
||||||
|
response.lookup(searchFilter.objectType)[0];
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
if (createdObjectType !== CRYPTOGRAPHIC_OBJECT_TYPE) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server created an object of wrong type');
|
||||||
|
logger.error('KMIP::createBucketKey',
|
||||||
|
{ error, createdObjectType });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
if (!this.options.compoundCreateActivate) {
|
||||||
|
return this._activateBucketKey(uniqueIdentifier, logger, cb);
|
||||||
|
}
|
||||||
|
return cb(null, uniqueIdentifier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke a cryptographic key managed by the server, for a specific bucket.
|
||||||
|
* This is a required action to perform before being able to destroy the
|
||||||
|
* managed key.
|
||||||
|
* @param {string} bucketKeyId - The bucket key Id
|
||||||
|
* @param {object} logger - Werelog logger object
|
||||||
|
* @param {function} cb - The callback(err: Error)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_revokeBucketKey(bucketKeyId, logger, cb) {
|
||||||
|
// maybe revoke first
|
||||||
|
return this.kmip.request(logger, 'Revoke', [
|
||||||
|
KMIP.TextString('Unique Identifier', bucketKeyId),
|
||||||
|
KMIP.Structure('Revocation Reason', [
|
||||||
|
KMIP.Enumeration('Revocation Reason Code',
|
||||||
|
'Cessation of Operation'),
|
||||||
|
KMIP.TextString('Revocation Message',
|
||||||
|
'About to be deleted'),
|
||||||
|
]),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::_revokeBucketKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
if (uniqueIdentifier !== bucketKeyId) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server did not return the expected identifier');
|
||||||
|
logger.error('KMIP::_revokeBucketKey',
|
||||||
|
{ error, uniqueIdentifier });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a cryptographic key managed by the server, for a specific bucket.
|
||||||
|
* @param {string} bucketKeyId - The bucket key Id
|
||||||
|
* @param {object} logger - Werelog logger object
|
||||||
|
* @param {function} cb - The callback(err: Error)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
destroyBucketKey(bucketKeyId, logger, cb) {
|
||||||
|
return this._revokeBucketKey(bucketKeyId, logger, err => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::destroyBucketKey: revocation failed',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return this.kmip.request(logger, 'Destroy', [
|
||||||
|
KMIP.TextString('Unique Identifier', bucketKeyId),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::destroyBucketKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
if (uniqueIdentifier !== bucketKeyId) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server did not return the expected identifier');
|
||||||
|
logger.error('KMIP::destroyBucketKey',
|
||||||
|
{ error, uniqueIdentifier });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} cryptoScheme - crypto scheme version number
|
||||||
|
* @param {string} masterKeyId - key to retrieve master key
|
||||||
|
* @param {buffer} plainTextDataKey - data key
|
||||||
|
* @param {object} logger - werelog logger object
|
||||||
|
* @param {function} cb - callback
|
||||||
|
* @returns {undefined}
|
||||||
|
* @callback called with (err, cipheredDataKey: Buffer)
|
||||||
|
*/
|
||||||
|
cipherDataKey(cryptoScheme,
|
||||||
|
masterKeyId,
|
||||||
|
plainTextDataKey,
|
||||||
|
logger,
|
||||||
|
cb) {
|
||||||
|
return this.kmip.request(logger, 'Encrypt', [
|
||||||
|
KMIP.TextString('Unique Identifier', masterKeyId),
|
||||||
|
KMIP.Structure('Cryptographic Parameters', [
|
||||||
|
KMIP.Enumeration('Block Cipher Mode',
|
||||||
|
CRYPTOGRAPHIC_CIPHER_MODE),
|
||||||
|
KMIP.Enumeration('Padding Method',
|
||||||
|
CRYPTOGRAPHIC_PADDING_METHOD),
|
||||||
|
KMIP.Enumeration('Cryptographic Algorithm',
|
||||||
|
CRYPTOGRAPHIC_ALGORITHM),
|
||||||
|
]),
|
||||||
|
KMIP.ByteString('Data', plainTextDataKey),
|
||||||
|
KMIP.ByteString('IV/Counter/Nonce', CRYPTOGRAPHIC_DEFAULT_IV),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::cipherDataKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
const data = response.lookup(searchFilter.data)[0];
|
||||||
|
if (uniqueIdentifier !== masterKeyId) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server did not return the expected identifier');
|
||||||
|
logger.error('KMIP::cipherDataKey',
|
||||||
|
{ error, uniqueIdentifier });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb(null, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} cryptoScheme - crypto scheme version number
|
||||||
|
* @param {string} masterKeyId - key to retrieve master key
|
||||||
|
* @param {buffer} cipheredDataKey - data key
|
||||||
|
* @param {object} logger - werelog logger object
|
||||||
|
* @param {function} cb - callback
|
||||||
|
* @returns {undefined}
|
||||||
|
* @callback called with (err, plainTextDataKey: Buffer)
|
||||||
|
*/
|
||||||
|
decipherDataKey(cryptoScheme,
|
||||||
|
masterKeyId,
|
||||||
|
cipheredDataKey,
|
||||||
|
logger,
|
||||||
|
cb) {
|
||||||
|
return this.kmip.request(logger, 'Decrypt', [
|
||||||
|
KMIP.TextString('Unique Identifier', masterKeyId),
|
||||||
|
KMIP.Structure('Cryptographic Parameters', [
|
||||||
|
KMIP.Enumeration('Block Cipher Mode',
|
||||||
|
CRYPTOGRAPHIC_CIPHER_MODE),
|
||||||
|
KMIP.Enumeration('Padding Method',
|
||||||
|
CRYPTOGRAPHIC_PADDING_METHOD),
|
||||||
|
KMIP.Enumeration('Cryptographic Algorithm',
|
||||||
|
CRYPTOGRAPHIC_ALGORITHM),
|
||||||
|
]),
|
||||||
|
KMIP.ByteString('Data', cipheredDataKey),
|
||||||
|
KMIP.ByteString('IV/Counter/Nonce', CRYPTOGRAPHIC_DEFAULT_IV),
|
||||||
|
], (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
const error = _arsenalError(err);
|
||||||
|
logger.error('KMIP::decipherDataKey',
|
||||||
|
{ error,
|
||||||
|
serverInformation: this.serverInformation });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
const uniqueIdentifier =
|
||||||
|
response.lookup(searchFilter.uniqueIdentifier)[0];
|
||||||
|
const data = response.lookup(searchFilter.data)[0];
|
||||||
|
if (uniqueIdentifier !== masterKeyId) {
|
||||||
|
const error = _arsenalError(
|
||||||
|
'Server did not return the right identifier');
|
||||||
|
logger.error('KMIP::decipherDataKey',
|
||||||
|
{ error, uniqueIdentifier });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb(null, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Client;
|
|
@ -0,0 +1,54 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
|
||||||
|
function _lookup(decodedTTLV, path) {
|
||||||
|
const xpath = path.split('/').filter(word => word.length > 0);
|
||||||
|
const canonicalPath = xpath.join('/');
|
||||||
|
const obj = decodedTTLV;
|
||||||
|
let res = [];
|
||||||
|
assert(Array.isArray(obj));
|
||||||
|
for (let current = xpath.shift(); current; current = xpath.shift()) {
|
||||||
|
for (let i = 0; i < obj.length; ++i) {
|
||||||
|
const cell = obj[i];
|
||||||
|
if (cell[current]) {
|
||||||
|
if (xpath.length === 0) {
|
||||||
|
/* Skip if the search path has not been
|
||||||
|
* completely consumed yet */
|
||||||
|
res.push(cell[current].value);
|
||||||
|
} else {
|
||||||
|
const subPath = xpath.join('/');
|
||||||
|
assert(current.length + 1 + subPath.length ===
|
||||||
|
canonicalPath.length);
|
||||||
|
const intermediate =
|
||||||
|
_lookup(cell[current].value, subPath);
|
||||||
|
res = res.concat(intermediate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
/**
|
||||||
|
* Construct a new abstract Message
|
||||||
|
* @param {Object} content - the content of the message
|
||||||
|
*/
|
||||||
|
constructor(content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup the values corresponding to the provided path
|
||||||
|
* @param {String} path - the path in the hierarchy of the values
|
||||||
|
* of interest
|
||||||
|
* @return {Object} - an array of the values matching the provided path
|
||||||
|
*/
|
||||||
|
lookup(path) {
|
||||||
|
return _lookup(this.content, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Message;
|
|
@ -0,0 +1,105 @@
|
||||||
|
# KMIP
|
||||||
|
|
||||||
|
Key Management Interoperability Protocol
|
||||||
|
|
||||||
|
## Preliminary usage example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const {
|
||||||
|
kmipServerHostName,
|
||||||
|
clientKey,
|
||||||
|
clientCert,
|
||||||
|
serverCert,
|
||||||
|
rootCa
|
||||||
|
} = require('./myconfiguration.js');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const tls = require('tls');
|
||||||
|
const werelogs = require('werelogs');
|
||||||
|
const KMIP = require('arsenal').network.kmip;
|
||||||
|
|
||||||
|
const logger = new werelogs.Logger('kmiptest');
|
||||||
|
const kmip = new KMIP;
|
||||||
|
const options = {
|
||||||
|
host: kmipServerHostName,
|
||||||
|
key: fs.readFileSync(clientKey),
|
||||||
|
cert: fs.readFileSync(clientCert),
|
||||||
|
ca: [ fs.readFileSync(serverCert),
|
||||||
|
fs.readFileSync(rootCa), ],
|
||||||
|
checkServerIdentity: console.log,
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = KMIP.Message([
|
||||||
|
KMIP.Structure('Request Message', [
|
||||||
|
KMIP.Structure('Request Header', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 3),
|
||||||
|
]),
|
||||||
|
KMIP.Integer('Maximum Response Size', 3456),
|
||||||
|
KMIP.Integer('Batch Count', 1),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', 'Query'),
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Server Information'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension Map'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
const encodedMessage = kmip.encodeMessage(logger, message);
|
||||||
|
|
||||||
|
const socket = tls.connect(5696, options, () => {
|
||||||
|
socket.write(encodedMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('data', (data) => {
|
||||||
|
const decodedMessage = kmip.decodeMessage(logger, data);
|
||||||
|
const summary = {
|
||||||
|
major: decodedMessage.lookup(
|
||||||
|
'Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Major')[0],
|
||||||
|
minor: decodedMessage.lookup(
|
||||||
|
'Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Minor')[0],
|
||||||
|
supportedOperations: decodedMessage.lookup(
|
||||||
|
'Response Message/Batch Item/Response Payload/Operation'),
|
||||||
|
supportedObjectTypes: decodedMessage.lookup(
|
||||||
|
'Response Message/Batch Item/Response Payload/Object Type'),
|
||||||
|
serverInformation: decodedMessage.lookup(
|
||||||
|
'Response Message/Batch Item/Response Payload/Server Information'),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(JSON.stringify(summary));
|
||||||
|
//console.log(JSON.stringify(decodedMessage.content));
|
||||||
|
//console.log(data.toString('hex'));
|
||||||
|
|
||||||
|
const protocolVersionMajor =
|
||||||
|
decodedMessage.lookup('Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Major');
|
||||||
|
const protocolVersionMinor =
|
||||||
|
decodedMessage.lookup('Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Minor');
|
||||||
|
|
||||||
|
assert(summary.supportedOperations.includes('Encrypt'));
|
||||||
|
assert(summary.supportedOperations.includes('Decrypt'));
|
||||||
|
assert(summary.supportedOperations.includes('Create'));
|
||||||
|
assert(summary.supportedOperations.includes('Destroy'));
|
||||||
|
assert(summary.supportedOperations.includes('Query'));
|
||||||
|
assert(summary.supportedObjectTypes.includes('Symmetric Key'));
|
||||||
|
assert(protocolVersionMajor[0] >= 2 ||
|
||||||
|
(protocolVersionMajor[0] === 1 &&
|
||||||
|
protocolVersionMinor[0] >= 2));
|
||||||
|
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('end', () => {
|
||||||
|
console.log('server ends connection');
|
||||||
|
});
|
||||||
|
```
|
|
@ -0,0 +1,367 @@
|
||||||
|
# KMIP codecs
|
||||||
|
|
||||||
|
The KMIP protocol is based on the exchange of structured messages between a
|
||||||
|
client and a server.
|
||||||
|
|
||||||
|
About the structure of the messages:
|
||||||
|
|
||||||
|
* It is composed of fields and nested fields
|
||||||
|
* It varies depending on the context of the request and who emits the message.
|
||||||
|
* It follows the same encoding rules for both the client and the server
|
||||||
|
* The set of primitive types is the cornerstone of the protocol, the structure
|
||||||
|
description is contained within the messages along with the actual payload.
|
||||||
|
* The set of defined tags is the keystone of the protocol. It permits to
|
||||||
|
attribute a meaning to the fields of a structured message.
|
||||||
|
|
||||||
|
The role of the codec is twofold.
|
||||||
|
|
||||||
|
* To decode a message from a particular encoding, to an abstract
|
||||||
|
representation of the KMIP structured messages.
|
||||||
|
* To encode a message from its abstract representation to the particular
|
||||||
|
encoding.
|
||||||
|
|
||||||
|
The codecs are not responsible for sending the messages on the wire.
|
||||||
|
This task is devoted to the transport layer.
|
||||||
|
|
||||||
|
## Abstract representation
|
||||||
|
|
||||||
|
The primitive data types defined by the protocol are represented internally
|
||||||
|
as data structures following the form
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const abstractKmipField = {
|
||||||
|
[tagName]: {
|
||||||
|
type,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The tag name `tagName` is a string. It is decoded from the tag value
|
||||||
|
using the KMIP nomenclature and identify the meaning of the field in the message.
|
||||||
|
|
||||||
|
The type name `type` is a string and is one of the primitive types
|
||||||
|
defined by the KMIP protocol. This element of a field also implicitly carries
|
||||||
|
the information of length for fixed size data types.
|
||||||
|
|
||||||
|
The value `value` is decoded from the payload of the KMIP field. This
|
||||||
|
element carries the length information for varying data types.
|
||||||
|
|
||||||
|
## Constructing an abstract Message
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const msg = KMIP.Message(content);
|
||||||
|
```
|
||||||
|
|
||||||
|
The static method `KMIP.Message` instantiates an object of the class
|
||||||
|
`Message`. Message objects wrap the content of the message without
|
||||||
|
alteration and offer a `lookup` method to search the message for
|
||||||
|
named fields.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field =
|
||||||
|
KMIP.Structure('Request Header', [
|
||||||
|
field_1,
|
||||||
|
...,
|
||||||
|
field_n,
|
||||||
|
]);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Request Header': {
|
||||||
|
type: 'Structure',
|
||||||
|
value: [
|
||||||
|
field_1,
|
||||||
|
...,
|
||||||
|
field_n
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields in the array parameter must be provided in the order defined by the
|
||||||
|
specification for the considered structure name.
|
||||||
|
|
||||||
|
### Integer
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.Integer('Protocol Version Minor', 3);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Protocol Version Minor': {
|
||||||
|
type: "Integer",
|
||||||
|
value: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Integers are encoded as four-byte long (32 bit) binary signed numbers in 2's
|
||||||
|
complement notation, transmitted big-endian.
|
||||||
|
|
||||||
|
### LongInteger
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.LongInteger('Usage Limits Total', 10 ** 42);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Usage Limits Total': {
|
||||||
|
type: 'LongInteger',
|
||||||
|
value: 1e+42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Long Integers are encoded as eight-byte long (64 bit) binary signed numbers in
|
||||||
|
2's complement notation, transmitted big-endian.
|
||||||
|
|
||||||
|
Due to an accuracy limitation of number representation, `LongInteger` values
|
||||||
|
cannot exceed 2^53. It's expected from the codec to throw an error when
|
||||||
|
attempting to transcode a LongInteger greater than this value.
|
||||||
|
|
||||||
|
### BigInteger
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.BigInteger('Usage Limits Total', value);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Usage Limits Total': {
|
||||||
|
type: 'LongInteger',
|
||||||
|
value: <Buffer ab cd ef ...>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Big Integers are encoded as a sequence of eight-bit bytes, in two's complement
|
||||||
|
notation, transmitted big-endian. If the length of the sequence is not a
|
||||||
|
multiple of eight bytes, then Big Integers SHALL be padded with the minimal
|
||||||
|
number of leading sign-extended bytes to make the length a multiple of eight
|
||||||
|
bytes. These padding bytes are part of the Item Value and SHALL be counted in
|
||||||
|
the Item Length.
|
||||||
|
|
||||||
|
### Enumeration
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.Enumeration('Operation', 'Discover Versions');
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Operation': {
|
||||||
|
type: 'Enumeration',
|
||||||
|
value: 'Discover Versions'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boolean
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.Boolean('Asynchronous Indicator', false);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Asynchronous Indicator': {
|
||||||
|
type: 'Boolean',
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### TextString
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.TextString('Username', 'alice');
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Username': {
|
||||||
|
type: 'TextString',
|
||||||
|
value: 'alice'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Text Strings are sequences of bytes that encode character values according to
|
||||||
|
the UTF-8 encoding standard. There SHALL NOT be null-termination at the end of
|
||||||
|
such strings.
|
||||||
|
|
||||||
|
### ByteString
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.ByteString('Asynchronous Correlation Value', buffer);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Username': {
|
||||||
|
type: 'ByteString',
|
||||||
|
value: <Buffer ab cd ef ...>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Byte Strings are sequences of bytes containing individual unspecified eight-bit
|
||||||
|
binary values, and are interpreted in the same sequence order.
|
||||||
|
|
||||||
|
### DateTime
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.DateTime('Activation Date', new Date);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Username': {
|
||||||
|
type: 'ByteString',
|
||||||
|
value: <Date 2019-01-10T20:41:36.914Z>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
DateTime takes a Date object as its second parameter. The millisecond part of
|
||||||
|
the date is silently discarded and not sent through the Network.
|
||||||
|
|
||||||
|
For this particular example, the 'Activation Date' tag is used for illustration
|
||||||
|
purpose. This is not the appropriate way to instanciate this attribute value and
|
||||||
|
the special function `KMIP.Attribute` must be used instead of `KMIP.DateTime`.
|
||||||
|
|
||||||
|
### Interval
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const field = KMIP.Interval('Lease Time', 42);
|
||||||
|
console.log(field);
|
||||||
|
{
|
||||||
|
'Lease Time': {
|
||||||
|
type: "Interval",
|
||||||
|
value: 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Intervals are encoded as four-byte long (32 bit) binary unsigned numbers,
|
||||||
|
transmitted big-endian. They have a resolution of one second.
|
||||||
|
|
||||||
|
### Special types
|
||||||
|
|
||||||
|
#### Bit Mask
|
||||||
|
|
||||||
|
Bit masks are encoded using the `Integer` primitive type relative to an instance
|
||||||
|
of the KMIP class (e.g. `encodeMask` and `decodemask` are not static class
|
||||||
|
function but regular methods).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const kmip = new KMIP;
|
||||||
|
const mask = ['Encrypt', 'Decrypt'];
|
||||||
|
const bitMask = kmip.encodeMask('Cryptographic Usage Mask', mask);
|
||||||
|
const decodedMask = kmip.decodeMask('Cryptographic Usage Mask', bitMask);
|
||||||
|
assert.deepStrictEqual(decodedMask, mask);
|
||||||
|
assert(bitMask === 12);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Attribute
|
||||||
|
|
||||||
|
Attribute names and values are managed in a way that deviates from the general
|
||||||
|
rule. Particularly when it comes associate the value of an enumeration to its
|
||||||
|
tag. In the nominal form, the value of an enumeration in a field is retrieved
|
||||||
|
from the tag of this field. For the case of an Attribute, the tag of the
|
||||||
|
enumeration is referenced in the `Attribute Name` as a `TextString` and the
|
||||||
|
encoded enumeration value is stored in the `Attribute value`, hence
|
||||||
|
disconnecting the value from its tag.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const cryptographicAlgorithm =
|
||||||
|
KMIP.Attribute('Enumeration', 'Cryptographic Algorithm', 'AES'),
|
||||||
|
const requestPayload =
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Enumeration('Object Type', 'Symmetric Key'),
|
||||||
|
KMIP.Structure('Template-Attribute', [
|
||||||
|
KMIP.Attribute('TextString', 'x-Name', 's3-thekey'),
|
||||||
|
cryptographicAlgorithm,
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Length', 256),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Usage Mask',
|
||||||
|
kmip.encodeMask('Cryptographic Usage Mask',
|
||||||
|
['Encrypt', 'Decrypt'])),
|
||||||
|
KMIP.Attribute('Date-Time', 'Activation Date', new Date),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
console.log(cryptographicAlgorithm);
|
||||||
|
{
|
||||||
|
'Attribute': {
|
||||||
|
type: 'Structure',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
'Attribute Name': {
|
||||||
|
type: 'TextString',
|
||||||
|
value: 'Cryptographic Algorithm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Attribute Value': {
|
||||||
|
type: 'Enumeration'
|
||||||
|
value: 'AES',
|
||||||
|
diversion: 'Cryptographic Algorithm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `diversion` attribute in the `Attribute Value` structure is used by the
|
||||||
|
codec to identify the `Enumeration` the value relates to.
|
||||||
|
|
||||||
|
## Codec Interface
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class MyCodec {
|
||||||
|
/**
|
||||||
|
* Construct a new instance of the codec
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a bitmask
|
||||||
|
* @param {String} tagName - name of the bit mask defining tag
|
||||||
|
* @param {Array of Strings} value - array of named bits to set in the mask
|
||||||
|
* @return {Integer} Integer encoded bitmask
|
||||||
|
*/
|
||||||
|
encodeMask(tagName, value) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a bitmask
|
||||||
|
* @param {string} tagName - name of the bit mask defining tag
|
||||||
|
* @param {Integer} givenMask - bit mask to decode
|
||||||
|
* @return {Array of Strings} array of named bits set in the given bit mask
|
||||||
|
*/
|
||||||
|
decodeMask(tagName, givenMask) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode an abstract message
|
||||||
|
* @param {Object} message - Instance of a KMIP.Message
|
||||||
|
* @return {Buffer} the encoded message suitable for the transport layer
|
||||||
|
*/
|
||||||
|
encode(message) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a raw message, usually received from the transport layer
|
||||||
|
* @param {Object} logger - a Logger instance
|
||||||
|
* @param {Buffer} rawMessage - the message to decode
|
||||||
|
* @return {Object} the decoded message as an instance of KMIP.Message
|
||||||
|
*/
|
||||||
|
decode(logger, rawMessage) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amend the tag nomenclature with a vendor specific extension
|
||||||
|
* @param {String} tagName - Name of the tag to record
|
||||||
|
* @param {Integer} tagValue - Tag value represented as an integer
|
||||||
|
*/
|
||||||
|
mapExtension(tagName, tagValue) {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Encoding specification links
|
||||||
|
|
||||||
|
### TTLV Encoding Baseline Profile
|
||||||
|
|
||||||
|
[TTLV Encoding Specification](http://docs.oasis-open.org/kmip/spec/v1.4/os/kmip-spec-v1.4-os.html#_Toc490660911)
|
||||||
|
|
||||||
|
### XML Encoding Profile
|
||||||
|
|
||||||
|
[XML Encoding Profile Specification](http://docs.oasis-open.org/kmip/profiles/v1.4/csprd01/kmip-profiles-v1.4-csprd01.html#_Toc479342078)
|
||||||
|
|
||||||
|
### JSON Encoding Profile
|
||||||
|
|
||||||
|
[JSON Encoding Profile Specification](http://docs.oasis-open.org/kmip/profiles/v1.4/csprd01/kmip-profiles-v1.4-csprd01.html#_Toc479342090)
|
|
@ -0,0 +1,434 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
/* eslint dot-notation: "off" */
|
||||||
|
|
||||||
|
const KMIPTags = require('../tags.json');
|
||||||
|
const KMIPMessage = require('../Message.js');
|
||||||
|
|
||||||
|
const UINT32_MAX = Math.pow(2, 32);
|
||||||
|
|
||||||
|
function _ttlvPadVector(vec) {
|
||||||
|
let length = 0;
|
||||||
|
vec.forEach(buf => {
|
||||||
|
if (!(buf instanceof Buffer)) {
|
||||||
|
throw Error('Not a Buffer');
|
||||||
|
}
|
||||||
|
length += buf.length;
|
||||||
|
});
|
||||||
|
const paddingLength = (Math.ceil(length / 8) * 8) - length;
|
||||||
|
if (paddingLength > 0) {
|
||||||
|
vec.push(Buffer.alloc(paddingLength).fill(0));
|
||||||
|
}
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _throwError(logger, msg, data) {
|
||||||
|
logger.error(msg, data);
|
||||||
|
throw Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TTLVCodec() {
|
||||||
|
if (!new.target) {
|
||||||
|
return new TTLVCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagDecoder = JSON.parse(JSON.stringify(KMIPTags));
|
||||||
|
const TagEncoder = {};
|
||||||
|
|
||||||
|
const TypeDecoder = {};
|
||||||
|
const TypeEncoder = {};
|
||||||
|
|
||||||
|
const PrimitiveTypes = {
|
||||||
|
'01': {
|
||||||
|
name: 'Structure',
|
||||||
|
decode: (logger, unusedTag, value) => {
|
||||||
|
const funcName = 'Structure::decode';
|
||||||
|
const length = value.length;
|
||||||
|
let i = 0;
|
||||||
|
const result = [];
|
||||||
|
let diversion = null;
|
||||||
|
while (i < length) {
|
||||||
|
const element = {};
|
||||||
|
const elementTag = value.slice(i, i + 3).toString('hex');
|
||||||
|
const elementType =
|
||||||
|
value.slice(i + 3, i + 4).toString('hex');
|
||||||
|
const elementLength = value.readUInt32BE(i + 4);
|
||||||
|
const property = {};
|
||||||
|
if (!TypeDecoder[elementType]) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Unknown element type',
|
||||||
|
{ funcName, elementTag, elementType });
|
||||||
|
}
|
||||||
|
const elementValue = value.slice(i + 8,
|
||||||
|
i + 8 + elementLength);
|
||||||
|
if (elementValue.length !== elementLength) {
|
||||||
|
_throwError(logger, 'BUG: Wrong buffer size',
|
||||||
|
{ funcName, elementLength,
|
||||||
|
bufferLength: elementValue.length });
|
||||||
|
}
|
||||||
|
property.type = TypeDecoder[elementType].name;
|
||||||
|
property.value = TypeDecoder[elementType]
|
||||||
|
.decode(logger, elementTag, elementValue, diversion);
|
||||||
|
if (diversion) {
|
||||||
|
property.diversion = diversion;
|
||||||
|
diversion = null;
|
||||||
|
}
|
||||||
|
const tagInfo = TagDecoder[elementTag];
|
||||||
|
if (!tagInfo) {
|
||||||
|
logger.debug('Unknown element tag',
|
||||||
|
{ funcName, elementTag });
|
||||||
|
property.tag = elementTag;
|
||||||
|
element['Unknown Tag'] = property;
|
||||||
|
} else {
|
||||||
|
element[tagInfo.name] = property;
|
||||||
|
if (tagInfo.name === 'Attribute Name') {
|
||||||
|
if (property.type !== 'TextString') {
|
||||||
|
_throwError(logger,
|
||||||
|
'Invalide type',
|
||||||
|
{ funcName, type: property.type });
|
||||||
|
}
|
||||||
|
diversion = property.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += Math.ceil((8 + elementLength) / 8.0) * 8;
|
||||||
|
result.push(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type = Buffer.from(TypeEncoder['Structure'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
let vectorLength = 0;
|
||||||
|
let encodedValue = [];
|
||||||
|
value.forEach(item => {
|
||||||
|
Object.keys(item).forEach(key => {
|
||||||
|
const itemTagName = key;
|
||||||
|
const itemType = item[key].type;
|
||||||
|
const itemValue = item[key].value;
|
||||||
|
const itemDiversion = item[key].diversion;
|
||||||
|
if (!TagEncoder[itemTagName]) {
|
||||||
|
throw Error(`Unknown Tag '${itemTagName}'`);
|
||||||
|
}
|
||||||
|
if (!TypeEncoder[itemType]) {
|
||||||
|
throw Error(`Unknown Type '${itemType}'`);
|
||||||
|
}
|
||||||
|
const itemResult =
|
||||||
|
TypeEncoder[itemType].encode(itemTagName,
|
||||||
|
itemValue,
|
||||||
|
itemDiversion);
|
||||||
|
encodedValue = encodedValue
|
||||||
|
.concat(_ttlvPadVector(itemResult));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
encodedValue = _ttlvPadVector(encodedValue);
|
||||||
|
encodedValue.forEach(buf => { vectorLength += buf.length; });
|
||||||
|
length.writeUInt32BE(vectorLength);
|
||||||
|
return _ttlvPadVector([tag, type, length, ...encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'02': {
|
||||||
|
name: 'Integer',
|
||||||
|
decode: (logger, tag, value) => {
|
||||||
|
const funcName = 'Integer::decode';
|
||||||
|
const fixedLength = 4;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
return value.readUInt32BE(0);
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type = Buffer.from(TypeEncoder['Integer'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(4);
|
||||||
|
const encodedValue = Buffer.alloc(4);
|
||||||
|
encodedValue.writeUInt32BE(value);
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'03': {
|
||||||
|
name: 'LongInteger',
|
||||||
|
decode: (logger, tag, value) => {
|
||||||
|
const funcName = 'LongInteger::decode';
|
||||||
|
const fixedLength = 8;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
const longUInt = UINT32_MAX * value.readUInt32BE(0) +
|
||||||
|
value.readUInt32BE(4);
|
||||||
|
if (longUInt > Number.MAX_SAFE_INTEGER) {
|
||||||
|
_throwError(logger,
|
||||||
|
'53-bit overflow',
|
||||||
|
{ funcName, longUInt });
|
||||||
|
}
|
||||||
|
return longUInt;
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type =
|
||||||
|
Buffer.from(TypeEncoder['LongInteger'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(8);
|
||||||
|
const encodedValue = Buffer.alloc(8);
|
||||||
|
encodedValue.writeUInt32BE(Math.floor(value / UINT32_MAX), 0);
|
||||||
|
encodedValue.writeUInt32BE(value % UINT32_MAX, 4);
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'04': {
|
||||||
|
name: 'BigInteger',
|
||||||
|
decode: (logger, tag, value) => value,
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type =
|
||||||
|
Buffer.from(TypeEncoder['BigInteger'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(value.length);
|
||||||
|
return _ttlvPadVector([tag, type, length, value]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'05': {
|
||||||
|
name: 'Enumeration',
|
||||||
|
decode: (logger, tag, value, diversion) => {
|
||||||
|
const funcName = 'Enumeration::decode';
|
||||||
|
const fixedLength = 4;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
const enumValue = value.toString('hex');
|
||||||
|
const actualTag = diversion ? TagEncoder[diversion].value : tag;
|
||||||
|
const enumInfo = TagDecoder[actualTag];
|
||||||
|
if (!enumInfo ||
|
||||||
|
!enumInfo.enumeration ||
|
||||||
|
!enumInfo.enumeration[enumValue]) {
|
||||||
|
return { tag,
|
||||||
|
value: enumValue,
|
||||||
|
message: 'Unknown enumeration value',
|
||||||
|
diversion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return enumInfo.enumeration[enumValue];
|
||||||
|
},
|
||||||
|
encode: (tagName, value, diversion) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type =
|
||||||
|
Buffer.from(TypeEncoder['Enumeration'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(4);
|
||||||
|
const actualTag = diversion || tagName;
|
||||||
|
const encodedValue =
|
||||||
|
Buffer.from(TagEncoder[actualTag].enumeration[value],
|
||||||
|
'hex');
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'06': {
|
||||||
|
name: 'Boolean',
|
||||||
|
decode: (logger, tag, value) => {
|
||||||
|
const funcName = 'Boolean::decode';
|
||||||
|
const fixedLength = 8;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
const msUInt = value.readUInt32BE(0);
|
||||||
|
const lsUInt = value.readUInt32BE(4);
|
||||||
|
return !!(msUInt | lsUInt);
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type = Buffer.from(TypeEncoder['Boolean'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(8);
|
||||||
|
const encodedValue = Buffer.alloc(8);
|
||||||
|
encodedValue.writeUInt32BE(0, 0);
|
||||||
|
encodedValue.writeUInt32BE(value ? 1 : 0, 4);
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'07': {
|
||||||
|
name: 'TextString',
|
||||||
|
decode: (logger, tag, value) => value.toString('utf8'),
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type =
|
||||||
|
Buffer.from(TypeEncoder['TextString'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(value.length);
|
||||||
|
return _ttlvPadVector([tag, type, length,
|
||||||
|
Buffer.from(value, 'utf8')]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'08': {
|
||||||
|
name: 'ByteString',
|
||||||
|
decode: (logger, tag, value) => value,
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type =
|
||||||
|
Buffer.from(TypeEncoder['ByteString'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(value.length);
|
||||||
|
return _ttlvPadVector([tag, type, length, value]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'09': {
|
||||||
|
name: 'Date-Time',
|
||||||
|
decode: (logger, tag, value) => {
|
||||||
|
const funcName = 'Date-Time::decode';
|
||||||
|
const fixedLength = 8;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
const d = new Date(0);
|
||||||
|
const utcSeconds = UINT32_MAX * value.readUInt32BE(0) +
|
||||||
|
value.readUInt32BE(4);
|
||||||
|
if (utcSeconds > Number.MAX_SAFE_INTEGER) {
|
||||||
|
_throwError(logger,
|
||||||
|
'53-bit overflow',
|
||||||
|
{ funcName, utcSeconds });
|
||||||
|
}
|
||||||
|
d.setUTCSeconds(utcSeconds);
|
||||||
|
return d;
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type = Buffer.from(TypeEncoder['Date-Time'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(8);
|
||||||
|
const encodedValue = Buffer.alloc(8);
|
||||||
|
const ts = value.getTime() / 1000;
|
||||||
|
encodedValue.writeUInt32BE(Math.floor(ts / UINT32_MAX), 0);
|
||||||
|
encodedValue.writeUInt32BE(ts % UINT32_MAX, 4);
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'0a': {
|
||||||
|
name: 'Interval',
|
||||||
|
decode: (logger, tag, value) => {
|
||||||
|
const funcName = 'Interval::decode';
|
||||||
|
const fixedLength = 4;
|
||||||
|
if (fixedLength !== value.length) {
|
||||||
|
_throwError(logger,
|
||||||
|
'Length mismatch',
|
||||||
|
{ funcName, fixedLength,
|
||||||
|
bufferLength: value.length });
|
||||||
|
}
|
||||||
|
return value.readInt32BE(0);
|
||||||
|
},
|
||||||
|
encode: (tagName, value) => {
|
||||||
|
const tag = Buffer.from(TagEncoder[tagName].value, 'hex');
|
||||||
|
const type = Buffer.from(TypeEncoder['Interval'].value, 'hex');
|
||||||
|
const length = Buffer.alloc(4);
|
||||||
|
length.writeUInt32BE(4);
|
||||||
|
const encodedValue = Buffer.alloc(4);
|
||||||
|
encodedValue.writeUInt32BE(value);
|
||||||
|
return _ttlvPadVector([tag, type, length, encodedValue]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Construct TagDecoder */
|
||||||
|
Object.keys(TagDecoder).forEach(key => {
|
||||||
|
const element = {};
|
||||||
|
element.value = key;
|
||||||
|
if (TagDecoder[key]['enumeration']) {
|
||||||
|
const enumeration = {};
|
||||||
|
Object.keys(TagDecoder[key]['enumeration']).forEach(enumValue => {
|
||||||
|
const enumKey = TagDecoder[key]['enumeration'][enumValue];
|
||||||
|
enumeration[enumKey] = enumValue;
|
||||||
|
});
|
||||||
|
element.enumeration = enumeration;
|
||||||
|
}
|
||||||
|
TagEncoder[TagDecoder[key].name] = element;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* Construct TypeDecoder and TypeEncoder */
|
||||||
|
Object.keys(PrimitiveTypes).forEach(value => {
|
||||||
|
const name = PrimitiveTypes[value].name;
|
||||||
|
const encode = PrimitiveTypes[value].encode;
|
||||||
|
const decode = PrimitiveTypes[value].decode;
|
||||||
|
TypeDecoder[value] = { name, decode };
|
||||||
|
TypeEncoder[name] = { value, encode };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* Public Methods Definition */
|
||||||
|
|
||||||
|
this.encodeMask = (tagName, value) => {
|
||||||
|
let mask = 0;
|
||||||
|
value.forEach(item => {
|
||||||
|
const enumValue = TagEncoder[tagName].enumeration[item];
|
||||||
|
if (!enumValue) {
|
||||||
|
throw Error('Invalid bit name');
|
||||||
|
}
|
||||||
|
mask |= parseInt(enumValue, 16);
|
||||||
|
});
|
||||||
|
return mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.decodeMask = (tagName, givenMask) => {
|
||||||
|
let mask = givenMask;
|
||||||
|
const value = [];
|
||||||
|
const tag = TagEncoder[tagName].value;
|
||||||
|
Object.keys(TagDecoder[tag].enumeration).forEach(key => {
|
||||||
|
const bit = Buffer.from(key, 'hex').readUInt32BE(0);
|
||||||
|
if (bit & mask) {
|
||||||
|
mask &= ~bit;
|
||||||
|
value.push(TagDecoder[tag].enumeration[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.decode = (logger, rawMessage) => {
|
||||||
|
const messageContent =
|
||||||
|
TypeDecoder['01'].decode(logger, null, rawMessage);
|
||||||
|
return new KMIPMessage(messageContent);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.encode = message => {
|
||||||
|
const value = message.content;
|
||||||
|
let result = [];
|
||||||
|
value.forEach(item => {
|
||||||
|
Object.keys(item).forEach(key => {
|
||||||
|
if (!TagEncoder[key]) {
|
||||||
|
throw Error(`Unknown Tag '${key}'`);
|
||||||
|
}
|
||||||
|
const type = item[key].type;
|
||||||
|
if (!TypeEncoder[type]) {
|
||||||
|
throw Error(`Unknown Type '${type}'`);
|
||||||
|
}
|
||||||
|
const itemValue = TypeEncoder[type].encode(key,
|
||||||
|
item[key].value,
|
||||||
|
item[key].diversion);
|
||||||
|
result = result.concat(_ttlvPadVector(itemValue));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Buffer.concat(_ttlvPadVector(result));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mapExtension = (tagName, tagValue) => {
|
||||||
|
const tagValueStr = tagValue.toString(16);
|
||||||
|
TagDecoder[tagValueStr] = { name: tagName };
|
||||||
|
TagEncoder[tagName] = { value: tagValueStr };
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TTLVCodec;
|
|
@ -0,0 +1,350 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
|
const Message = require('./Message.js');
|
||||||
|
|
||||||
|
/* This client requires at least a KMIP 1.2 compatible server */
|
||||||
|
const DEFAULT_PROTOCOL_VERSION_MAJOR = 1;
|
||||||
|
const DEFAULT_PROTOCOL_VERSION_MINOR = 2;
|
||||||
|
/* Response is for one operation, consider raising this value if
|
||||||
|
* compounding ops */
|
||||||
|
const DEFAULT_MAXIMUM_RESPONSE_SIZE = 8000;
|
||||||
|
|
||||||
|
function _uniqueBatchItemID() {
|
||||||
|
const theUUID = Buffer.alloc(16);
|
||||||
|
return uuidv4(null, theUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _PrimitiveType(tagName, type, value) {
|
||||||
|
return { [tagName]: { type, value } };
|
||||||
|
}
|
||||||
|
|
||||||
|
class KMIP {
|
||||||
|
/**
|
||||||
|
* Construct a new KMIP Object
|
||||||
|
* @param {Class} Codec -
|
||||||
|
* @param {Class} Transport -
|
||||||
|
* @param {Object} options -
|
||||||
|
* @param {Function} cb -
|
||||||
|
*/
|
||||||
|
constructor(Codec, Transport, options) {
|
||||||
|
this.protocolVersion = {
|
||||||
|
major: DEFAULT_PROTOCOL_VERSION_MAJOR,
|
||||||
|
minor: DEFAULT_PROTOCOL_VERSION_MINOR,
|
||||||
|
};
|
||||||
|
this.maximumResponseSize = DEFAULT_MAXIMUM_RESPONSE_SIZE;
|
||||||
|
this.options = options.kmip;
|
||||||
|
this.codec = new Codec(options.kmip.codec);
|
||||||
|
this.transport = new Transport(options.kmip.transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static class methods */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a new abstract message instance
|
||||||
|
* @param {Object} content - Most likely a call to KMIP.Structure
|
||||||
|
* with 'Request Message' as tagName
|
||||||
|
* @returns {Object} an instance of Message
|
||||||
|
*/
|
||||||
|
static Message(content) {
|
||||||
|
return new Message(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Structure field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Array} value - array of KMIP fields
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Structure(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'Structure', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Integer field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Number} value - a number
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Integer(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'Integer', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Long Integer field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Number} value - a number (beware of the 53-bit limitation)
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static LongInteger(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'LongInteger', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Big Integer field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Buffer} value - buffer containing the big integer
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static BigInteger(tagName, value) {
|
||||||
|
if (value.length % 8 !== 0) {
|
||||||
|
throw Error('Big Integer value length must be a multiple of 8');
|
||||||
|
}
|
||||||
|
return _PrimitiveType(tagName, 'BigInteger', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Enumeration field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP Enumeration
|
||||||
|
* @param {String} value - Name of the KMIP Enumeration value
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Enumeration(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'Enumeration', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Boolean field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Boolean} value - anything falsey or not (converted to a Boolean)
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Boolean(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'Boolean', !!value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Text String field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {String} value - the text string
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static TextString(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'TextString', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Byte String field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Buffer} value - buffer containing the byte string
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static ByteString(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'ByteString', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Date-Time field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Date} value - instance of a Date (ms are discarded)
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static DateTime(tagName, value) {
|
||||||
|
value.setMilliseconds(0);
|
||||||
|
return _PrimitiveType(tagName, 'Date-Time', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Interval field instance
|
||||||
|
* @param {String} tagName - Name of the KMIP field
|
||||||
|
* @param {Integer} value - number of seconds of the interval
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Interval(tagName, value) {
|
||||||
|
return _PrimitiveType(tagName, 'Interval', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a KMIP Attribute field instance
|
||||||
|
* @param {String} type - type of the attribute value
|
||||||
|
* @param {String} name - Name of the attribute or KMIP field
|
||||||
|
* @param {Object} value - value of the field suitable for the
|
||||||
|
* specified type
|
||||||
|
* @returns {Object} an abstract KMIP field
|
||||||
|
*/
|
||||||
|
static Attribute(type, name, value) {
|
||||||
|
if (type === 'Date-Time') {
|
||||||
|
value.setMilliseconds(0);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
Attribute: {
|
||||||
|
type: 'Structure',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
'Attribute Name': {
|
||||||
|
type: 'TextString',
|
||||||
|
value: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Attribute Value': {
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
diversion: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Object methods */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a higher level handshake function to be called
|
||||||
|
* after the connection is initialized and before the first
|
||||||
|
* message is sent.
|
||||||
|
* @param {Function} handshakeFunction - (logger: Object, cb: Function(err))
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
registerHandshakeFunction(handshakeFunction) {
|
||||||
|
this.transport.registerHandshakeFunction(handshakeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a raw message, usually received from the transport layer
|
||||||
|
* @param {Object} logger - a Logger instance
|
||||||
|
* @param {Buffer} rawMessage - the message to decode
|
||||||
|
* @returns {Object} the decoded message as an instance of KMIP.Message
|
||||||
|
*/
|
||||||
|
_decodeMessage(logger, rawMessage) {
|
||||||
|
return this.codec.decode(logger, rawMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode an message
|
||||||
|
* @param {Object} message - Instance of a KMIP.Message
|
||||||
|
* @returns {Buffer} the encoded message suitable for the transport layer
|
||||||
|
*/
|
||||||
|
_encodeMessage(message) {
|
||||||
|
return this.codec.encode(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a bitmask
|
||||||
|
* @param {string} tagName - name of the bit mask defining tag
|
||||||
|
* @param {Integer} givenMask - bit mask to decode
|
||||||
|
* @returns {Array} array of named bits set in the given bit mask
|
||||||
|
*/
|
||||||
|
decodeMask(tagName, givenMask) {
|
||||||
|
return this.codec.decodeMask(tagName, givenMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a bitmask
|
||||||
|
* @param {String} tagName - name of the bit mask defining tag
|
||||||
|
* @param {Array} value - array of named bits to set in the mask
|
||||||
|
* @returns {Integer} Integer encoded bitmask
|
||||||
|
*/
|
||||||
|
encodeMask(tagName, value) {
|
||||||
|
return this.codec.encodeMask(tagName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amend the tag nomenclature with a vendor specific extension
|
||||||
|
* @param {String} extensionName - Name of the tag to record
|
||||||
|
* @param {Integer} extensionTag - Tag value represented as an integer
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
mapExtension(extensionName, extensionTag) {
|
||||||
|
return this.codec.mapExtension(extensionName, extensionTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeProtocolVersion(major, minor) {
|
||||||
|
this.protocolVersion = { major, minor };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an operation request message to the KMIP Server
|
||||||
|
* @param {Object} logger - Werelog logger object
|
||||||
|
* @param {String} operation - The name of the operation as defined in
|
||||||
|
* the KMIP protocol specification.
|
||||||
|
* @param {Object} payload - payload of the operation request. Specifically
|
||||||
|
* the content of the Request Payload as defined
|
||||||
|
* by the KMIP protocol specification.
|
||||||
|
* @param {Function} cb - The callback(error: Object, response: Object)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
request(logger, operation, payload, cb) {
|
||||||
|
const uuid = _uniqueBatchItemID();
|
||||||
|
const message = KMIP.Message([
|
||||||
|
KMIP.Structure('Request Message', [
|
||||||
|
KMIP.Structure('Request Header', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major',
|
||||||
|
this.protocolVersion.major),
|
||||||
|
KMIP.Integer('Protocol Version Minor',
|
||||||
|
this.protocolVersion.minor)]),
|
||||||
|
KMIP.Integer('Maximum Response Size',
|
||||||
|
this.maximumResponseSize),
|
||||||
|
KMIP.Integer('Batch Count', 1)]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', operation),
|
||||||
|
KMIP.ByteString('Unique Batch Item ID', uuid),
|
||||||
|
KMIP.Structure('Request Payload', payload),
|
||||||
|
])])]);
|
||||||
|
const encodedMessage = this._encodeMessage(message);
|
||||||
|
this.transport.send(
|
||||||
|
logger, encodedMessage,
|
||||||
|
(err, conversation, rawResponse) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('KMIP::request: Failed to encode message',
|
||||||
|
{ error: err });
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
const response = this._decodeMessage(logger, rawResponse);
|
||||||
|
const performedOperation =
|
||||||
|
response.lookup('Response Message/' +
|
||||||
|
'Batch Item/Operation')[0];
|
||||||
|
const resultStatus =
|
||||||
|
response.lookup('Response Message/' +
|
||||||
|
'Batch Item/Result Status')[0];
|
||||||
|
const resultUniqueBatchItemID =
|
||||||
|
response.lookup('Response Message/' +
|
||||||
|
'Batch Item/Unique Batch Item ID')[0];
|
||||||
|
|
||||||
|
if (!resultUniqueBatchItemID ||
|
||||||
|
resultUniqueBatchItemID.compare(uuid) !== 0) {
|
||||||
|
this.transport.abortPipeline(conversation);
|
||||||
|
const error = Error('Invalid batch item ID returned');
|
||||||
|
logger.error('KMIP::request: failed',
|
||||||
|
{ resultUniqueBatchItemID, uuid, error });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
if (performedOperation !== operation) {
|
||||||
|
this.transport.abortPipeline(conversation);
|
||||||
|
const error = Error('Operation mismatch',
|
||||||
|
{ got: performedOperation,
|
||||||
|
expected: operation });
|
||||||
|
logger.error('KMIP::request: Operation mismatch',
|
||||||
|
{ error });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
if (resultStatus !== 'Success') {
|
||||||
|
const resultReason =
|
||||||
|
response.lookup(
|
||||||
|
'Response Message/Batch Item/Result Reason')[0];
|
||||||
|
const resultMessage =
|
||||||
|
response.lookup(
|
||||||
|
'Response Message/Batch Item/Result Message')[0];
|
||||||
|
const error = Error('KMIP request failure',
|
||||||
|
{ resultStatus,
|
||||||
|
resultReason,
|
||||||
|
resultMessage });
|
||||||
|
logger.error('KMIP::request: request failed',
|
||||||
|
{ error, resultStatus,
|
||||||
|
resultReason, resultMessage });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
return cb(null, response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = KMIP;
|
|
@ -0,0 +1,579 @@
|
||||||
|
{
|
||||||
|
"420006": {
|
||||||
|
"name": "Asynchronous Correlation Value"
|
||||||
|
},
|
||||||
|
"420007": {
|
||||||
|
"name": "Asynchronous Indicator"
|
||||||
|
},
|
||||||
|
"420008": {
|
||||||
|
"name": "Attribute"
|
||||||
|
},
|
||||||
|
"420009": {
|
||||||
|
"name": "Attribute Index"
|
||||||
|
},
|
||||||
|
"42000a": {
|
||||||
|
"name": "Attribute Name"
|
||||||
|
},
|
||||||
|
"42000b": {
|
||||||
|
"name": "Attribute Value"
|
||||||
|
},
|
||||||
|
"42000c": {
|
||||||
|
"name": "Authentication"
|
||||||
|
},
|
||||||
|
"42000d": {
|
||||||
|
"name": "Batch Count"
|
||||||
|
},
|
||||||
|
"42000f": {
|
||||||
|
"name": "Batch Item"
|
||||||
|
},
|
||||||
|
"420011": {
|
||||||
|
"name": "Block Cipher Mode",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "CBC",
|
||||||
|
"00000002": "ECB",
|
||||||
|
"00000003": "PCBC",
|
||||||
|
"00000004": "CFB",
|
||||||
|
"00000005": "OFB",
|
||||||
|
"00000006": "CTR",
|
||||||
|
"00000007": "CMAC",
|
||||||
|
"00000008": "CCM",
|
||||||
|
"00000009": "GCM",
|
||||||
|
"0000000a": "CBC-MAC",
|
||||||
|
"0000000b": "XTS",
|
||||||
|
"0000000c": "AESKeyWrapPadding",
|
||||||
|
"0000000d": "NISTKeyWrap",
|
||||||
|
"0000000e": "X9.102 AESKW",
|
||||||
|
"0000000f": "X9.102 TDKW",
|
||||||
|
"00000010": "X9.102 AKW1",
|
||||||
|
"00000011": "X9.102 AKW2",
|
||||||
|
"00000012": "AEAD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420028": {
|
||||||
|
"name": "Cryptographic Algorithm",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "DES",
|
||||||
|
"00000002": "3DES",
|
||||||
|
"00000003": "AES",
|
||||||
|
"00000004": "RSA",
|
||||||
|
"00000005": "DSA",
|
||||||
|
"00000006": "ECDSA",
|
||||||
|
"00000007": "HMAC-SHA1",
|
||||||
|
"00000008": "HMAC-SHA224",
|
||||||
|
"00000009": "HMAC-SHA256",
|
||||||
|
"0000000a": "HMAC-SHA384",
|
||||||
|
"0000000b": "HMAC-SHA512",
|
||||||
|
"0000000c": "HMAC-MD5",
|
||||||
|
"0000000d": "DH",
|
||||||
|
"0000000e": "ECDH",
|
||||||
|
"0000000f": "ECMQV",
|
||||||
|
"00000010": "Blowfish",
|
||||||
|
"00000011": "Camellia",
|
||||||
|
"00000012": "CAST5",
|
||||||
|
"00000013": "IDEA",
|
||||||
|
"00000014": "MARS",
|
||||||
|
"00000015": "RC2",
|
||||||
|
"00000016": "RC4",
|
||||||
|
"00000017": "RC5",
|
||||||
|
"00000018": "SKIPJACK",
|
||||||
|
"00000019": "Twofish",
|
||||||
|
"0000001a": "EC",
|
||||||
|
"0000001b": "One Time Pad",
|
||||||
|
"0000001c": "ChaCha20",
|
||||||
|
"0000001d": "Poly1305",
|
||||||
|
"0000001e": "ChaCha20Poly1305",
|
||||||
|
"0000001f": "SHA3-224",
|
||||||
|
"00000020": "SHA3-256",
|
||||||
|
"00000021": "SHA3-384",
|
||||||
|
"00000022": "SHA3-512",
|
||||||
|
"00000023": "HMAC-SHA3-224",
|
||||||
|
"00000024": "HMAC-SHA3-256",
|
||||||
|
"00000025": "HMAC-SHA3-384",
|
||||||
|
"00000026": "HMAC-SHA3-512",
|
||||||
|
"00000027": "SHAKE-128",
|
||||||
|
"00000028": "SHAKE-256"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"42002b": {
|
||||||
|
"name": "Cryptographic Parameters"
|
||||||
|
},
|
||||||
|
"42002c": {
|
||||||
|
"name": "Cryptographic Usage Mask",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Sign",
|
||||||
|
"00000002": "Verify",
|
||||||
|
"00000004": "Encrypt",
|
||||||
|
"00000008": "Decrypt",
|
||||||
|
"00000010": "Wrap Key",
|
||||||
|
"00000020": "Unwrap Key",
|
||||||
|
"00000040": "Export",
|
||||||
|
"00000080": "MAC Generate",
|
||||||
|
"00000100": "MAC Verify",
|
||||||
|
"00000200": "Derive Key",
|
||||||
|
"00000400": "Content Commitment",
|
||||||
|
"00000800": "Key Agreement",
|
||||||
|
"00001000": "Certificate Sign",
|
||||||
|
"00002000": "CRL Sign",
|
||||||
|
"00004000": "Generate Cryptogram",
|
||||||
|
"00008000": "Validate Cryptogram",
|
||||||
|
"00010000": "Translate Encrypt",
|
||||||
|
"00020000": "Translate Decrypt",
|
||||||
|
"00040000": "Translate Wrap",
|
||||||
|
"00080000": "Translate Unwrap"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"42003d": {
|
||||||
|
"name": "IV/Counter/Nonce"
|
||||||
|
},
|
||||||
|
"420050": {
|
||||||
|
"name": "Maximum Response Size"
|
||||||
|
},
|
||||||
|
"420054": {
|
||||||
|
"name": "Name Type",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Uninterpreted Text String",
|
||||||
|
"00000002": "URI"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420057": {
|
||||||
|
"name": "Object Type",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Certificate",
|
||||||
|
"00000002": "Symmetric Key",
|
||||||
|
"00000003": "Public Key",
|
||||||
|
"00000004": "Private Key",
|
||||||
|
"00000005": "Split Key",
|
||||||
|
"00000006": "Template",
|
||||||
|
"00000007": "Secret Data",
|
||||||
|
"00000008": "Opaque Object",
|
||||||
|
"00000009": "PGP Key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"42005c": {
|
||||||
|
"name": "Operation",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Create",
|
||||||
|
"00000002": "Create Key Pair",
|
||||||
|
"00000003": "Register",
|
||||||
|
"00000004": "Re-key",
|
||||||
|
"00000005": "Derive Key",
|
||||||
|
"00000006": "Certify",
|
||||||
|
"00000007": "Re-certify",
|
||||||
|
"00000008": "Locate",
|
||||||
|
"00000009": "Check",
|
||||||
|
"0000000a": "Get",
|
||||||
|
"0000000b": "Get Attributes",
|
||||||
|
"0000000c": "Get Attribute List",
|
||||||
|
"0000000d": "Add Attribute",
|
||||||
|
"0000000e": "Modify Attribute",
|
||||||
|
"0000000f": "Delete Attribute",
|
||||||
|
"00000010": "Obtain Lease",
|
||||||
|
"00000011": "Get Usage Allocation",
|
||||||
|
"00000012": "Activate",
|
||||||
|
"00000013": "Revoke",
|
||||||
|
"00000014": "Destroy",
|
||||||
|
"00000015": "Archive",
|
||||||
|
"00000016": "Recover",
|
||||||
|
"00000017": "Validate",
|
||||||
|
"00000018": "Query",
|
||||||
|
"00000019": "Cancel",
|
||||||
|
"0000001a": "Poll",
|
||||||
|
"0000001b": "Notify",
|
||||||
|
"0000001c": "Put",
|
||||||
|
"0000001d": "Re-key Key Pair",
|
||||||
|
"0000001e": "Discover Versions",
|
||||||
|
"0000001f": "Encrypt",
|
||||||
|
"00000020": "Decrypt",
|
||||||
|
"00000021": "Sign",
|
||||||
|
"00000022": "Signature Verify",
|
||||||
|
"00000023": "MAC",
|
||||||
|
"00000024": "MAC Verify",
|
||||||
|
"00000025": "RNG Retrieve",
|
||||||
|
"00000026": "RNG Seed",
|
||||||
|
"00000027": "Hash",
|
||||||
|
"00000028": "Create Split Key",
|
||||||
|
"00000029": "Join Split Key",
|
||||||
|
"0000002a": "Import",
|
||||||
|
"0000002b": "Export"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"42005f": {
|
||||||
|
"name": "Padding Method",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "None",
|
||||||
|
"00000002": "OAEP",
|
||||||
|
"00000003": "PKCS5",
|
||||||
|
"00000004": "SSL3",
|
||||||
|
"00000005": "Zeros",
|
||||||
|
"00000006": "ANSI X9.23",
|
||||||
|
"00000007": "ISO 10126",
|
||||||
|
"00000008": "PKCS1 v1.5",
|
||||||
|
"00000009": "X9.31",
|
||||||
|
"0000000a": "PSS"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420069": {
|
||||||
|
"name": "Protocol Version"
|
||||||
|
},
|
||||||
|
"42006a": {
|
||||||
|
"name": "Protocol Version Major"
|
||||||
|
},
|
||||||
|
"42006b": {
|
||||||
|
"name": "Protocol Version Minor"
|
||||||
|
},
|
||||||
|
"420074": {
|
||||||
|
"name": "Query Function",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Query Operations",
|
||||||
|
"00000002": "Query Objects",
|
||||||
|
"00000003": "Query Server Information",
|
||||||
|
"00000004": "Query Application Namespaces",
|
||||||
|
"00000005": "Query Extension List",
|
||||||
|
"00000006": "Query Extension Map",
|
||||||
|
"00000007": "Query Attestation Types",
|
||||||
|
"00000008": "Query RNGs",
|
||||||
|
"00000009": "Query Validations",
|
||||||
|
"0000000a": "Query Profiles",
|
||||||
|
"0000000b": "Query Capabilities",
|
||||||
|
"0000000c": "Query Client Registration Methods"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420077": {
|
||||||
|
"name": "Request Header"
|
||||||
|
},
|
||||||
|
"420078": {
|
||||||
|
"name": "Request Message"
|
||||||
|
},
|
||||||
|
"420079": {
|
||||||
|
"name": "Request Payload"
|
||||||
|
},
|
||||||
|
"42007a": {
|
||||||
|
"name": "Response Header"
|
||||||
|
},
|
||||||
|
"42007b": {
|
||||||
|
"name": "Response Message"
|
||||||
|
},
|
||||||
|
"42007c": {
|
||||||
|
"name": "Response Payload"
|
||||||
|
},
|
||||||
|
"42007d": {
|
||||||
|
"name": "Result Message"
|
||||||
|
},
|
||||||
|
"42007e": {
|
||||||
|
"name": "Result Reason",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Item Not Found",
|
||||||
|
"00000002": "Response Too Large",
|
||||||
|
"00000003": "Authentication Not Successful",
|
||||||
|
"00000004": "Invalid Message",
|
||||||
|
"00000005": "Operation Not Supported",
|
||||||
|
"00000006": "Missing Data",
|
||||||
|
"00000007": "Invalid Field",
|
||||||
|
"00000008": "Feature Not Supported",
|
||||||
|
"00000009": "Operation Canceled By Requester",
|
||||||
|
"0000000a": "Cryptographic Failure",
|
||||||
|
"0000000b": "Illegal Operation",
|
||||||
|
"0000000c": "Permission Denied",
|
||||||
|
"0000000d": "Object archived",
|
||||||
|
"0000000e": "Index Out of Bounds",
|
||||||
|
"0000000f": "Application Namespace Not Supported",
|
||||||
|
"00000010": "Key Format Type Not Supported",
|
||||||
|
"00000011": "Key Compression Type Not Supported",
|
||||||
|
"00000012": "Encoding Option Error",
|
||||||
|
"00000013": "Key Value Not Present",
|
||||||
|
"00000014": "Attestation Required",
|
||||||
|
"00000015": "Attestation Failed",
|
||||||
|
"00000016": "Sensitive",
|
||||||
|
"00000017": "Not Extractable",
|
||||||
|
"00000018": "Object Already Exists",
|
||||||
|
"00000100": "General Failure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"42007f": {
|
||||||
|
"name": "Result Status",
|
||||||
|
"enumeration": {
|
||||||
|
"00000000": "Success",
|
||||||
|
"00000001": "Operation Failed",
|
||||||
|
"00000002": "Operation Pending",
|
||||||
|
"00000003": "Operation Undone"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420080": {
|
||||||
|
"name": "Revocation Message"
|
||||||
|
},
|
||||||
|
"420081": {
|
||||||
|
"name": "Revocation Reason"
|
||||||
|
},
|
||||||
|
"420082": {
|
||||||
|
"name": "Revocation Reason Code",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Unspecified",
|
||||||
|
"00000002": "Key Compromise",
|
||||||
|
"00000003": "CA Compromise",
|
||||||
|
"00000004": "Affiliation Changed",
|
||||||
|
"00000005": "Superseded",
|
||||||
|
"00000006": "Cessation of Operation",
|
||||||
|
"00000007": "Privilege Withdrawn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"420088": {
|
||||||
|
"name": "Server Information"
|
||||||
|
},
|
||||||
|
"420091": {
|
||||||
|
"name": "Template-Attribute"
|
||||||
|
},
|
||||||
|
"420092": {
|
||||||
|
"name": "Time Stamp"
|
||||||
|
},
|
||||||
|
"420093": {
|
||||||
|
"name": "Unique Batch Item ID"
|
||||||
|
},
|
||||||
|
"420094": {
|
||||||
|
"name": "Unique Identifier"
|
||||||
|
},
|
||||||
|
"42009d": {
|
||||||
|
"name": "Vendor Identification"
|
||||||
|
},
|
||||||
|
"4200a4": {
|
||||||
|
"name": "Extension Information"
|
||||||
|
},
|
||||||
|
"4200a5": {
|
||||||
|
"name": "Extension Name"
|
||||||
|
},
|
||||||
|
"4200a6": {
|
||||||
|
"name": "Extension Tag"
|
||||||
|
},
|
||||||
|
"4200a7": {
|
||||||
|
"name": "Extension Type"
|
||||||
|
},
|
||||||
|
"4200c2": {
|
||||||
|
"name": "Data"
|
||||||
|
},
|
||||||
|
"4200eb": {
|
||||||
|
"name": "Profile Information"
|
||||||
|
},
|
||||||
|
"4200ec": {
|
||||||
|
"name": "Profile Name",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Baseline Server Basic KMIP v1.2",
|
||||||
|
"00000002": "Baseline Server TLS v1.2 KMIP v1.2",
|
||||||
|
"00000003": "Baseline Client Basic KMIP v1.2",
|
||||||
|
"00000004": "Baseline Client TLS v1.2 KMIP v1.2",
|
||||||
|
"00000005": "Complete Server Basic KMIP v1.2",
|
||||||
|
"00000006": "Complete Server TLS v1.2 KMIP v1.2",
|
||||||
|
"00000007": "Tape Library Client KMIP v1.0",
|
||||||
|
"00000008": "Tape Library Client KMIP v1.1",
|
||||||
|
"00000009": "Tape Library Client KMIP v1.2",
|
||||||
|
"0000000a": "Tape Library Server KMIP v1.0",
|
||||||
|
"0000000b": "Tape Library Server KMIP v1.1",
|
||||||
|
"0000000c": "Tape Library Server KMIP v1.2",
|
||||||
|
"0000000d": "Symmetric Key Lifecycle Client KMIP v1.0",
|
||||||
|
"0000000e": "Symmetric Key Lifecycle Client KMIP v1.1",
|
||||||
|
"0000000f": "Symmetric Key Lifecycle Client KMIP v1.2",
|
||||||
|
"00000010": "Symmetric Key Lifecycle Server KMIP v1.0",
|
||||||
|
"00000011": "Symmetric Key Lifecycle Server KMIP v1.1",
|
||||||
|
"00000012": "Symmetric Key Lifecycle Server KMIP v1.2",
|
||||||
|
"00000013": "Asymmetric Key Lifecycle Client KMIP v1.0",
|
||||||
|
"00000014": "Asymmetric Key Lifecycle Client KMIP v1.1",
|
||||||
|
"00000015": "Asymmetric Key Lifecycle Client KMIP v1.2",
|
||||||
|
"00000016": "Asymmetric Key Lifecycle Server KMIP v1.0",
|
||||||
|
"00000017": "Asymmetric Key Lifecycle Server KMIP v1.1",
|
||||||
|
"00000018": "Asymmetric Key Lifecycle Server KMIP v1.2",
|
||||||
|
"00000019": "Basic Cryptographic Client KMIP v1.2",
|
||||||
|
"0000001a": "Basic Cryptographic Server KMIP v1.2",
|
||||||
|
"0000001b": "Advanced Cryptographic Client KMIP v1.2",
|
||||||
|
"0000001c": "Advanced Cryptographic Server KMIP v1.2",
|
||||||
|
"0000001d": "RNG Cryptographic Client KMIP v1.2",
|
||||||
|
"0000001e": "RNG Cryptographic Server KMIP v1.2",
|
||||||
|
"0000001f": "Basic Symmetric Key Foundry Client KMIP v1.0",
|
||||||
|
"00000020": "Intermediate Symmetric Key Foundry Client KMIP v1.0",
|
||||||
|
"00000021": "Advanced Symmetric Key Foundry Client KMIP v1.0",
|
||||||
|
"00000022": "Basic Symmetric Key Foundry Client KMIP v1.1",
|
||||||
|
"00000023": "Intermediate Symmetric Key Foundry Client KMIP v1.1",
|
||||||
|
"00000024": "Advanced Symmetric Key Foundry Client KMIP v1.1",
|
||||||
|
"00000025": "Basic Symmetric Key Foundry Client KMIP v1.2",
|
||||||
|
"00000026": "Intermediate Symmetric Key Foundry Client KMIP v1.2",
|
||||||
|
"00000027": "Advanced Symmetric Key Foundry Client KMIP v1.2",
|
||||||
|
"00000028": "Symmetric Key Foundry Server KMIP v1.0",
|
||||||
|
"00000029": "Symmetric Key Foundry Server KMIP v1.1",
|
||||||
|
"0000002a": "Symmetric Key Foundry Server KMIP v1.2",
|
||||||
|
"0000002b": "Opaque Managed Object Store Client KMIP v1.0",
|
||||||
|
"0000002c": "Opaque Managed Object Store Client KMIP v1.1",
|
||||||
|
"0000002d": "Opaque Managed Object Store Client KMIP v1.2",
|
||||||
|
"0000002e": "Opaque Managed Object Store Server KMIP v1.0",
|
||||||
|
"0000002f": "Opaque Managed Object Store Server KMIP v1.1",
|
||||||
|
"00000030": "Opaque Managed Object Store Server KMIP v1.2",
|
||||||
|
"00000031": "Suite B minLOS_128 Client KMIP v1.0",
|
||||||
|
"00000032": "Suite B minLOS_128 Client KMIP v1.1",
|
||||||
|
"00000033": "Suite B minLOS_128 Client KMIP v1.2",
|
||||||
|
"00000034": "Suite B minLOS_128 Server KMIP v1.0",
|
||||||
|
"00000035": "Suite B minLOS_128 Server KMIP v1.1",
|
||||||
|
"00000036": "Suite B minLOS_128 Server KMIP v1.2",
|
||||||
|
"00000037": "Suite B minLOS_192 Client KMIP v1.0",
|
||||||
|
"00000038": "Suite B minLOS_192 Client KMIP v1.1",
|
||||||
|
"00000039": "Suite B minLOS_192 Client KMIP v1.2",
|
||||||
|
"0000003a": "Suite B minLOS_192 Server KMIP v1.0",
|
||||||
|
"0000003b": "Suite B minLOS_192 Server KMIP v1.1",
|
||||||
|
"0000003c": "Suite B minLOS_192 Server KMIP v1.2",
|
||||||
|
"0000003d": "Storage Array with Self Encrypting Drive Client KMIP v1.0",
|
||||||
|
"0000003e": "Storage Array with Self Encrypting Drive Client KMIP v1.1",
|
||||||
|
"0000003f": "Storage Array with Self Encrypting Drive Client KMIP v1.2",
|
||||||
|
"00000040": "Storage Array with Self Encrypting Drive Server KMIP v1.0",
|
||||||
|
"00000041": "Storage Array with Self Encrypting Drive Server KMIP v1.1",
|
||||||
|
"00000042": "Storage Array with Self Encrypting Drive Server KMIP v1.2",
|
||||||
|
"00000043": "HTTPS Client KMIP v1.0",
|
||||||
|
"00000044": "HTTPS Client KMIP v1.1",
|
||||||
|
"00000045": "HTTPS Client KMIP v1.2",
|
||||||
|
"00000046": "HTTPS Server KMIP v1.0",
|
||||||
|
"00000047": "HTTPS Server KMIP v1.1",
|
||||||
|
"00000048": "HTTPS Server KMIP v1.2",
|
||||||
|
"00000049": "JSON Client KMIP v1.0",
|
||||||
|
"0000004a": "JSON Client KMIP v1.1",
|
||||||
|
"0000004b": "JSON Client KMIP v1.2",
|
||||||
|
"0000004c": "JSON Server KMIP v1.0",
|
||||||
|
"0000004d": "JSON Server KMIP v1.1",
|
||||||
|
"0000004e": "JSON Server KMIP v1.2",
|
||||||
|
"0000004f": "XML Client KMIP v1.0",
|
||||||
|
"00000050": "XML Client KMIP v1.1",
|
||||||
|
"00000051": "XML Client KMIP v1.2",
|
||||||
|
"00000052": "XML Server KMIP v1.0",
|
||||||
|
"00000053": "XML Server KMIP v1.1",
|
||||||
|
"00000054": "XML Server KMIP v1.2",
|
||||||
|
"00000055": "Baseline Server Basic KMIP v1.3",
|
||||||
|
"00000056": "Baseline Server TLS v1.2 KMIP v1.3",
|
||||||
|
"00000057": "Baseline Client Basic KMIP v1.3",
|
||||||
|
"00000058": "Baseline Client TLS v1.2 KMIP v1.3",
|
||||||
|
"00000059": "Complete Server Basic KMIP v1.3",
|
||||||
|
"0000005a": "Complete Server TLS v1.2 KMIP v1.3",
|
||||||
|
"0000005b": "Tape Library Client KMIP v1.3",
|
||||||
|
"0000005c": "Tape Library Server KMIP v1.3",
|
||||||
|
"0000005d": "Symmetric Key Lifecycle Client KMIP v1.3",
|
||||||
|
"0000005e": "Symmetric Key Lifecycle Server KMIP v1.3",
|
||||||
|
"0000005f": "Asymmetric Key Lifecycle Client KMIP v1.3",
|
||||||
|
"00000060": "Asymmetric Key Lifecycle Server KMIP v1.3",
|
||||||
|
"00000061": "Basic Cryptographic Client KMIP v1.3",
|
||||||
|
"00000062": "Basic Cryptographic Server KMIP v1.3",
|
||||||
|
"00000063": "Advanced Cryptographic Client KMIP v1.3",
|
||||||
|
"00000064": "Advanced Cryptographic Server KMIP v1.3",
|
||||||
|
"00000065": "RNG Cryptographic Client KMIP v1.3",
|
||||||
|
"00000066": "RNG Cryptographic Server KMIP v1.3",
|
||||||
|
"00000067": "Basic Symmetric Key Foundry Client KMIP v1.3",
|
||||||
|
"00000068": "Intermediate Symmetric Key Foundry Client KMIP v1.3",
|
||||||
|
"00000069": "Advanced Symmetric Key Foundry Client KMIP v1.3",
|
||||||
|
"0000006a": "Symmetric Key Foundry Server KMIP v1.3",
|
||||||
|
"0000006b": "Opaque Managed Object Store Client KMIP v1.3",
|
||||||
|
"0000006c": "Opaque Managed Object Store Server KMIP v1.3",
|
||||||
|
"0000006d": "Suite B minLOS_128 Client KMIP v1.3",
|
||||||
|
"0000006e": "Suite B minLOS_128 Server KMIP v1.3",
|
||||||
|
"0000006f": "Suite B minLOS_192 Client KMIP v1.3",
|
||||||
|
"00000070": "Suite B minLOS_192 Server KMIP v1.3",
|
||||||
|
"00000071": "Storage Array with Self Encrypting Drive Client KMIP v1.3",
|
||||||
|
"00000072": "Storage Array with Self Encrypting Drive Server KMIP v1.3",
|
||||||
|
"00000073": "HTTPS Client KMIP v1.3",
|
||||||
|
"00000074": "HTTPS Server KMIP v1.3",
|
||||||
|
"00000075": "JSON Client KMIP v1.3",
|
||||||
|
"00000076": "JSON Server KMIP v1.3",
|
||||||
|
"00000077": "XML Client KMIP v1.3",
|
||||||
|
"00000078": "XML Server KMIP v1.3",
|
||||||
|
"00000079": "Baseline Server Basic KMIP v1.4",
|
||||||
|
"0000007a": "Baseline Server TLS v1.2 KMIP v1.4",
|
||||||
|
"0000007b": "Baseline Client Basic KMIP v1.4",
|
||||||
|
"0000007c": "Baseline Client TLS v1.2 KMIP v1.4",
|
||||||
|
"0000007d": "Complete Server Basic KMIP v1.4",
|
||||||
|
"0000007e": "Complete Server TLS v1.2 KMIP v1.4",
|
||||||
|
"0000007f": "Tape Library Client KMIP v1.4",
|
||||||
|
"00000080": "Tape Library Server KMIP v1.4",
|
||||||
|
"00000081": "Symmetric Key Lifecycle Client KMIP v1.4",
|
||||||
|
"00000082": "Symmetric Key Lifecycle Server KMIP v1.4",
|
||||||
|
"00000083": "Asymmetric Key Lifecycle Client KMIP v1.4",
|
||||||
|
"00000084": "Asymmetric Key Lifecycle Server KMIP v1.4",
|
||||||
|
"00000085": "Basic Cryptographic Client KMIP v1.4",
|
||||||
|
"00000086": "Basic Cryptographic Server KMIP v1.4",
|
||||||
|
"00000087": "Advanced Cryptographic Client KMIP v1.4",
|
||||||
|
"00000088": "Advanced Cryptographic Server KMIP v1.4",
|
||||||
|
"00000089": "RNG Cryptographic Client KMIP v1.4",
|
||||||
|
"0000008a": "RNG Cryptographic Server KMIP v1.4",
|
||||||
|
"0000008b": "Basic Symmetric Key Foundry Client KMIP v1.4",
|
||||||
|
"0000008c": "Intermediate Symmetric Key Foundry Client KMIP v1.4",
|
||||||
|
"0000008d": "Advanced Symmetric Key Foundry Client KMIP v1.4",
|
||||||
|
"0000008e": "Symmetric Key Foundry Server KMIP v1.4",
|
||||||
|
"0000008f": "Opaque Managed Object Store Client KMIP v1.4",
|
||||||
|
"00000090": "Opaque Managed Object Store Server KMIP v1.4",
|
||||||
|
"00000091": "Suite B minLOS_128 Client KMIP v1.4",
|
||||||
|
"00000092": "Suite B minLOS_128 Server KMIP v1.4",
|
||||||
|
"00000093": "Suite B minLOS_192 Client KMIP v1.4",
|
||||||
|
"00000094": "Suite B minLOS_192 Server KMIP v1.4",
|
||||||
|
"00000095": "Storage Array with Self Encrypting Drive Client KMIP v1.4",
|
||||||
|
"00000096": "Storage Array with Self Encrypting Drive Server KMIP v1.4",
|
||||||
|
"00000097": "HTTPS Client KMIP v1.4",
|
||||||
|
"00000098": "HTTPS Server KMIP v1.4",
|
||||||
|
"00000099": "JSON Client KMIP v1.4",
|
||||||
|
"0000009a": "JSON Server KMIP v1.4",
|
||||||
|
"0000009b": "XML Client KMIP v1.4",
|
||||||
|
"0000009c": "XML Server KMIP v1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4200ed": {
|
||||||
|
"name": "Server URI"
|
||||||
|
},
|
||||||
|
"4200ee": {
|
||||||
|
"name": "Server Port"
|
||||||
|
},
|
||||||
|
"4200ef": {
|
||||||
|
"name": "Streaming Capability"
|
||||||
|
},
|
||||||
|
"4200f0": {
|
||||||
|
"name": "Asynchronous Capability"
|
||||||
|
},
|
||||||
|
"4200f1": {
|
||||||
|
"name": "Attestation Capability"
|
||||||
|
},
|
||||||
|
"4200f2": {
|
||||||
|
"name": "Unwrap Mode",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Unspecified",
|
||||||
|
"00000002": "Processed",
|
||||||
|
"00000003": "Not Processed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4200f3": {
|
||||||
|
"name": "Destroy Action",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Unspecified",
|
||||||
|
"00000002": "Key Material Deleted",
|
||||||
|
"00000003": "Key Material Shredded",
|
||||||
|
"00000004": "Meta Data Deleted",
|
||||||
|
"00000005": "Meta Data Shredded",
|
||||||
|
"00000006": "Deleted",
|
||||||
|
"00000007": "Shredded"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4200f4": {
|
||||||
|
"name": "Shredding Algorithm",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Unspecified",
|
||||||
|
"00000002": "Cryptographic",
|
||||||
|
"00000003": "Unsupported"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4200f5": {
|
||||||
|
"name": "RNG Mode",
|
||||||
|
"enumeration": {
|
||||||
|
"00000001": "Unspecified",
|
||||||
|
"00000002": "Shared Instantiation",
|
||||||
|
"00000003": "Non-Shared Instantiation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4200f6": {
|
||||||
|
"name": "Client Registration Method"
|
||||||
|
},
|
||||||
|
"4200f7": {
|
||||||
|
"name": "Capability Information"
|
||||||
|
},
|
||||||
|
"420105": {
|
||||||
|
"name": "Client Correlation Value"
|
||||||
|
},
|
||||||
|
"420106": {
|
||||||
|
"name": "Server Correlation Value"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const DEFAULT_PIPELINE_DEPTH = 8;
|
||||||
|
const DEFAULT_KMIP_PORT = 5696;
|
||||||
|
|
||||||
|
class TransportTemplate {
|
||||||
|
/**
|
||||||
|
* Construct a new object of the TransportTemplate class
|
||||||
|
* @param {Object} channel - Typically the tls object
|
||||||
|
* @param {Object} options - Instance options
|
||||||
|
* @param {Number} options.pipelineDepth - depth of the pipeline
|
||||||
|
* @param {Object} options.tls - Standard TLS socket initialization
|
||||||
|
* parameters
|
||||||
|
* @param {Number} options.tls.port - TLS server port to connect to
|
||||||
|
*/
|
||||||
|
constructor(channel, options) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.options = options;
|
||||||
|
this.pipelineDepth = Math.max(1, options.pipelineDepth ||
|
||||||
|
DEFAULT_PIPELINE_DEPTH);
|
||||||
|
this.callbackPipeline = [];
|
||||||
|
this.deferedRequests = [];
|
||||||
|
this.pipelineDrainedCallback = null;
|
||||||
|
this.handshakeFunction = null;
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drain the outstanding and defered request queues by
|
||||||
|
* calling the associated callback with an error
|
||||||
|
* @param {Error} error - the error to call the callback function with.
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_drainQueuesWithError(error) {
|
||||||
|
this.callbackPipeline.forEach(queuedCallback => {
|
||||||
|
queuedCallback(error);
|
||||||
|
});
|
||||||
|
this.deferedRequests.forEach(deferedRequest => {
|
||||||
|
deferedRequest.cb(error);
|
||||||
|
});
|
||||||
|
this.callbackPipeline = [];
|
||||||
|
this.deferedRequests = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a higher level handshake function to be called
|
||||||
|
* after the connection is initialized and before the first
|
||||||
|
* message is sent.
|
||||||
|
* @param {Function} handshakeFunction - (logger: Object, cb: Function(err))
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
registerHandshakeFunction(handshakeFunction) {
|
||||||
|
this.handshakeFunction = handshakeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new conversation (e.g. a socket) between the client
|
||||||
|
* and the server.
|
||||||
|
* @param {Object} logger - Werelogs logger object
|
||||||
|
* @param {Function} readyCallback - callback function to call when the
|
||||||
|
* conversation is ready to be initiated
|
||||||
|
* func(err: Error)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_createConversation(logger, readyCallback) {
|
||||||
|
try {
|
||||||
|
const socket = this.channel.connect(
|
||||||
|
this.options.tls.port || DEFAULT_KMIP_PORT,
|
||||||
|
this.options.tls,
|
||||||
|
() => {
|
||||||
|
socket.on('data', data => {
|
||||||
|
const queuedCallback = this.callbackPipeline.shift();
|
||||||
|
queuedCallback(null, socket, data);
|
||||||
|
|
||||||
|
if (this.callbackPipeline.length <
|
||||||
|
this.pipelineDepth &&
|
||||||
|
this.deferedRequests.length > 0) {
|
||||||
|
const deferedRequest = this.deferedRequests.shift();
|
||||||
|
process.nextTick(() => {
|
||||||
|
this.send(logger,
|
||||||
|
deferedRequest.encodedMessage,
|
||||||
|
deferedRequest.cb);
|
||||||
|
});
|
||||||
|
} else if (this.callbackPipeline.length === 0 &&
|
||||||
|
this.deferedRequests.length === 0 &&
|
||||||
|
this.pipelineDrainedCallback) {
|
||||||
|
this.pipelineDrainedCallback();
|
||||||
|
this.pipelineDrainedCallback = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('end', () => {
|
||||||
|
const error = Error('Conversation interrupted');
|
||||||
|
this._drainQueuesWithError(error);
|
||||||
|
this.socket = null;
|
||||||
|
});
|
||||||
|
socket.on('error', err => {
|
||||||
|
this._drainQueuesWithError(err);
|
||||||
|
});
|
||||||
|
if (this.handshakeFunction) {
|
||||||
|
this.handshakeFunction(logger, readyCallback);
|
||||||
|
} else {
|
||||||
|
readyCallback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.socket = socket;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error();
|
||||||
|
readyCallback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_doSend(logger, encodedMessage, cb) {
|
||||||
|
const socket = this.socket;
|
||||||
|
if (!socket || socket.destroyed) {
|
||||||
|
const error = new Error('Socket to server not available');
|
||||||
|
logger.error('TransportTemplate::_doSend', { error });
|
||||||
|
return cb(error);
|
||||||
|
}
|
||||||
|
this.callbackPipeline.push(cb);
|
||||||
|
socket.cork();
|
||||||
|
socket.write(encodedMessage);
|
||||||
|
socket.uncork();
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an encoded message to the server
|
||||||
|
* @param {Object} logger - Werelogs logger object
|
||||||
|
* @param {Buffer} encodedMessage - the encoded message to send to the
|
||||||
|
* server
|
||||||
|
* @param {Function} cb - (err, conversation, rawResponse)
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
send(logger, encodedMessage, cb) {
|
||||||
|
if (this.callbackPipeline.length >= this.pipelineDepth) {
|
||||||
|
return this.deferedRequests.push({ encodedMessage, cb });
|
||||||
|
}
|
||||||
|
assert(encodedMessage.length !== 0);
|
||||||
|
if (this.socket === null || this.socket.destroyed) {
|
||||||
|
return this._createConversation(logger, err => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
return this._doSend(logger, encodedMessage, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._doSend(logger, encodedMessage, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gracefuly interrupt the conversation. If the caller keeps sending
|
||||||
|
* message after calling this function, the conversation won't
|
||||||
|
* converge to its end.
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
end() {
|
||||||
|
if (!this.socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.callbackPipeline.length !== 0 ||
|
||||||
|
this.deferedRequests.length !== 0) {
|
||||||
|
this.pipelineDrainedCallback = this.socket.end.bind(this.socket);
|
||||||
|
} else {
|
||||||
|
this.socket.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abruptly interrupt the conversation and cancel the outstanding and
|
||||||
|
* defered requests
|
||||||
|
* @param {Object} conversation - the conversation to abort
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
abortPipeline(conversation) {
|
||||||
|
conversation.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TransportTemplate;
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
|
||||||
|
const tls = require('tls');
|
||||||
|
const TransportTemplate = require('./TransportTemplate.js');
|
||||||
|
|
||||||
|
class TlsTransport extends TransportTemplate {
|
||||||
|
constructor(options) {
|
||||||
|
super(tls, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TlsTransport;
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
const Ajv = require('ajv');
|
const Ajv = require('ajv');
|
||||||
const userPolicySchema = require('./userPolicySchema');
|
const userPolicySchema = require('./userPolicySchema');
|
||||||
|
const resourcePolicySchema = require('./resourcePolicySchema');
|
||||||
const errors = require('../errors');
|
const errors = require('../errors');
|
||||||
|
|
||||||
const ajValidate = new Ajv({ allErrors: true });
|
const ajValidate = new Ajv({ allErrors: true });
|
||||||
// compiles schema to functions and caches them for all cases
|
// compiles schema to functions and caches them for all cases
|
||||||
const userPolicyValidate = ajValidate.compile(userPolicySchema);
|
const userPolicyValidate = ajValidate.compile(userPolicySchema);
|
||||||
|
const resourcePolicyValidate = ajValidate.compile(resourcePolicySchema);
|
||||||
|
|
||||||
const errDict = {
|
const errDict = {
|
||||||
required: {
|
required: {
|
||||||
|
@ -24,33 +26,38 @@ const errDict = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// parse ajv errors and return early with the first relevant error
|
// parse ajv errors and return early with the first relevant error
|
||||||
function _parseErrors(ajvErrors) {
|
function _parseErrors(ajvErrors, policyType) {
|
||||||
// deep copy is needed as we have to assign custom error description
|
let parsedErr;
|
||||||
const parsedErr = Object.assign({}, errors.MalformedPolicyDocument);
|
if (policyType === 'user') {
|
||||||
parsedErr.description = 'Syntax errors in policy.';
|
// deep copy is needed as we have to assign custom error description
|
||||||
|
parsedErr = Object.assign({}, errors.MalformedPolicyDocument);
|
||||||
|
}
|
||||||
|
if (policyType === 'resource') {
|
||||||
|
parsedErr = Object.assign({}, errors.MalformedPolicy);
|
||||||
|
}
|
||||||
ajvErrors.some(err => {
|
ajvErrors.some(err => {
|
||||||
const resource = err.dataPath;
|
const resource = err.dataPath;
|
||||||
const field = err.params ? err.params.missingProperty : undefined;
|
const field = err.params ? err.params.missingProperty : undefined;
|
||||||
const errType = err.keyword;
|
const errType = err.keyword;
|
||||||
if (errType === 'type' && (resource === '.Statement' ||
|
if (errType === 'type' && (resource === '.Statement' ||
|
||||||
resource === '.Statement.Resource' ||
|
resource.includes('.Resource') ||
|
||||||
resource === '.Statement.NotResource')) {
|
resource.includes('.NotResource'))) {
|
||||||
// skip this as this doesn't have enough error context
|
// skip this as this doesn't have enough error context
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (err.keyword === 'required' && field && errDict.required[field]) {
|
if (err.keyword === 'required' && field && errDict.required[field]) {
|
||||||
parsedErr.description = errDict.required[field];
|
parsedErr.description = errDict.required[field];
|
||||||
} else if (err.keyword === 'pattern' &&
|
} else if (err.keyword === 'pattern' &&
|
||||||
(resource === '.Statement.Action' ||
|
(resource.includes('.Action') ||
|
||||||
resource === '.Statement.NotAction')) {
|
resource.includes('.NotAction'))) {
|
||||||
parsedErr.description = errDict.pattern.Action;
|
parsedErr.description = errDict.pattern.Action;
|
||||||
} else if (err.keyword === 'pattern' &&
|
} else if (err.keyword === 'pattern' &&
|
||||||
(resource === '.Statement.Resource' ||
|
(resource.includes('.Resource') ||
|
||||||
resource === '.Statement.NotResource')) {
|
resource.includes('.NotResource'))) {
|
||||||
parsedErr.description = errDict.pattern.Resource;
|
parsedErr.description = errDict.pattern.Resource;
|
||||||
} else if (err.keyword === 'minItems' &&
|
} else if (err.keyword === 'minItems' &&
|
||||||
(resource === '.Statement.Resource' ||
|
(resource.includes('.Resource') ||
|
||||||
resource === '.Statement.NotResource')) {
|
resource.includes('.NotResource'))) {
|
||||||
parsedErr.description = errDict.minItems.Resource;
|
parsedErr.description = errDict.minItems.Resource;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -77,12 +84,24 @@ function _validatePolicy(type, policy) {
|
||||||
}
|
}
|
||||||
userPolicyValidate(parseRes);
|
userPolicyValidate(parseRes);
|
||||||
if (userPolicyValidate.errors) {
|
if (userPolicyValidate.errors) {
|
||||||
return { error: _parseErrors(userPolicyValidate.errors),
|
return { error: _parseErrors(userPolicyValidate.errors, 'user'),
|
||||||
valid: false };
|
valid: false };
|
||||||
}
|
}
|
||||||
return { error: null, valid: true };
|
return { error: null, valid: true };
|
||||||
}
|
}
|
||||||
// TODO: add support for resource policies
|
if (type === 'resource') {
|
||||||
|
const parseRes = _safeJSONParse(policy);
|
||||||
|
if (parseRes instanceof Error) {
|
||||||
|
return { error: Object.assign({}, errors.MalformedPolicy),
|
||||||
|
valid: false };
|
||||||
|
}
|
||||||
|
resourcePolicyValidate(parseRes);
|
||||||
|
if (resourcePolicyValidate.errors) {
|
||||||
|
return { error: _parseErrors(resourcePolicyValidate.errors,
|
||||||
|
'resource'), valid: false };
|
||||||
|
}
|
||||||
|
return { error: null, valid: true };
|
||||||
|
}
|
||||||
return { error: errors.NotImplemented, valid: false };
|
return { error: errors.NotImplemented, valid: false };
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,477 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"title": "AWS Bucket Policy schema.",
|
||||||
|
"description": "This schema describes a bucket policy per AWS policy grammar rules",
|
||||||
|
"definitions": {
|
||||||
|
"principalService": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Service": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"backbeat"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"principalCanonicalUser": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"CanonicalUser": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9a-z]{64}$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"principalAnonymous": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\*$"
|
||||||
|
},
|
||||||
|
"principalAWSAccountID": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]{12}$"
|
||||||
|
},
|
||||||
|
"principalAWSAccountArn": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^arn:aws:iam::[0-9]{12}:root$"
|
||||||
|
},
|
||||||
|
"principalAWSUserArn": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^arn:aws:iam::[0-9]{12}:user/[\\w+=,.@ -]{1,64}$"
|
||||||
|
},
|
||||||
|
"principalAWSRoleArn": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^arn:aws:iam::[0-9]{12}:role/[\\w+=,.@ -]{1,64}$"
|
||||||
|
},
|
||||||
|
"principalAWSItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"AWS": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/principalAWSAccountID" },
|
||||||
|
{ "$ref": "#/definitions/principalAnonymous" },
|
||||||
|
{ "$ref": "#/definitions/principalAWSAccountArn" },
|
||||||
|
{ "$ref": "#/definitions/principalAWSUserArn" },
|
||||||
|
{ "$ref": "#/definitions/principalAWSRoleArn" },
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/principalAWSAccountID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/principalAWSAccountArn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/principalAWSRoleArn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/principalAWSUserArn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"principalItem": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/principalAWSItem" },
|
||||||
|
{ "$ref": "#/definitions/principalAnonymous" },
|
||||||
|
{ "$ref": "#/definitions/principalService" },
|
||||||
|
{ "$ref": "#/definitions/principalCanonicalUser" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"actionItem": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[^*:]+:([^:])+|^\\*$"
|
||||||
|
},
|
||||||
|
"resourceItem": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\*|arn:(aws|scality)(:(\\*{1}|[a-z0-9\\*\\-]{2,})*?){3}:((?!\\$\\{\\}).)*?$"
|
||||||
|
},
|
||||||
|
"conditionKeys" : {
|
||||||
|
"properties": {
|
||||||
|
"aws:CurrentTime": {},
|
||||||
|
"aws:EpochTime": {},
|
||||||
|
"aws:MultiFactorAuthAge": {},
|
||||||
|
"aws:MultiFactorAuthPresent": {},
|
||||||
|
"aws:PrincipalArn": {},
|
||||||
|
"aws:PrincipalOrgId": {},
|
||||||
|
"aws:PrincipalTag/${TagKey}": {},
|
||||||
|
"aws:PrincipalType": {},
|
||||||
|
"aws:Referer": {},
|
||||||
|
"aws:RequestTag/${TagKey}": {},
|
||||||
|
"aws:RequestedRegion": {},
|
||||||
|
"aws:SecureTransport": {},
|
||||||
|
"aws:SourceAccount": {},
|
||||||
|
"aws:SourceArn": {},
|
||||||
|
"aws:SourceIp": {},
|
||||||
|
"aws:SourceVpc": {},
|
||||||
|
"aws:SourceVpce": {},
|
||||||
|
"aws:TagKeys": {},
|
||||||
|
"aws:TokenIssueTime": {},
|
||||||
|
"aws:UserAgent": {},
|
||||||
|
"aws:userid": {},
|
||||||
|
"aws:username": {},
|
||||||
|
"s3:ExistingJobOperation": {},
|
||||||
|
"s3:ExistingJobPriority": {},
|
||||||
|
"s3:ExistingObjectTag/<key>": {},
|
||||||
|
"s3:JobSuspendedCause": {},
|
||||||
|
"s3:LocationConstraint": {},
|
||||||
|
"s3:RequestJobOperation": {},
|
||||||
|
"s3:RequestJobPriority": {},
|
||||||
|
"s3:RequestObjectTag/<key>": {},
|
||||||
|
"s3:RequestObjectTagKeys": {},
|
||||||
|
"s3:VersionId": {},
|
||||||
|
"s3:authtype": {},
|
||||||
|
"s3:delimiter": {},
|
||||||
|
"s3:locationconstraint": {},
|
||||||
|
"s3:max-keys": {},
|
||||||
|
"s3:object-lock-legal-hold": {},
|
||||||
|
"s3:object-lock-mode": {},
|
||||||
|
"s3:object-lock-remaining-retention-days": {},
|
||||||
|
"s3:object-lock-retain-until-date": {},
|
||||||
|
"s3:prefix": {},
|
||||||
|
"s3:signatureage": {},
|
||||||
|
"s3:signatureversion": {},
|
||||||
|
"s3:versionid": {},
|
||||||
|
"s3:x-amz-acl": {},
|
||||||
|
"s3:x-amz-content-sha256": {},
|
||||||
|
"s3:x-amz-copy-source": {},
|
||||||
|
"s3:x-amz-grant-full-control": {},
|
||||||
|
"s3:x-amz-grant-read": {},
|
||||||
|
"s3:x-amz-grant-read-acp": {},
|
||||||
|
"s3:x-amz-grant-write": {},
|
||||||
|
"s3:x-amz-grant-write-acp": {},
|
||||||
|
"s3:x-amz-metadata-directive": {},
|
||||||
|
"s3:x-amz-server-side-encryption": {},
|
||||||
|
"s3:x-amz-server-side-encryption-aws-kms-key-id": {},
|
||||||
|
"s3:x-amz-storage-class": {},
|
||||||
|
"s3:x-amz-website-redirect-location": {}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"conditions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ArnEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnLike": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnLikeIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnNotEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnNotEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnNotLike": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ArnNotLikeIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"BinaryEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"BinaryEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"BinaryNotEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"BinaryNotEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Bool": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"BoolIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateGreaterThan": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateGreaterThanEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateGreaterThanEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateGreaterThanIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateLessThan": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateLessThanEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateLessThanEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateLessThanIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateNotEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DateNotEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"IpAddress": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"IpAddressIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NotIpAddress": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NotIpAddressIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Null": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericGreaterThan": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericGreaterThanEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericGreaterThanEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericGreaterThanIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericLessThan": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericLessThanEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericLessThanEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericLessThanIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericNotEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NumericNotEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringEqualsIgnoreCase": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringEqualsIgnoreCaseIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringLike": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringLikeIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotEquals": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotEqualsIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotEqualsIgnoreCase": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotEqualsIgnoreCaseIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotLike": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"StringNotLikeIfExists": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"Version": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"2012-10-17"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Statement": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Sid": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-zA-Z0-9]+$"
|
||||||
|
},
|
||||||
|
"Action": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/actionItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/actionItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Effect": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Allow",
|
||||||
|
"Deny"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Principal": {
|
||||||
|
"$ref": "#/definitions/principalItem"
|
||||||
|
},
|
||||||
|
"Resource": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/resourceItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/resourceItem"
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Condition": {
|
||||||
|
"$ref": "#/definitions/conditions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"Action",
|
||||||
|
"Effect",
|
||||||
|
"Principal",
|
||||||
|
"Resource"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"object"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Sid": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-zA-Z0-9]+$"
|
||||||
|
},
|
||||||
|
"Action": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/actionItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/actionItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Effect": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Allow",
|
||||||
|
"Deny"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Principal": {
|
||||||
|
"$ref": "#/definitions/principalItem"
|
||||||
|
},
|
||||||
|
"Resource": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/resourceItem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/resourceItem"
|
||||||
|
},
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Condition": {
|
||||||
|
"$ref": "#/definitions/conditions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"Action",
|
||||||
|
"Effect",
|
||||||
|
"Resource",
|
||||||
|
"Principal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"Version",
|
||||||
|
"Statement"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "AWS Policy schema.",
|
"title": "AWS User Policy schema.",
|
||||||
"description": "This schema describes a user policy per AWS policy grammar rules",
|
"description": "This schema describes a user policy per AWS policy grammar rules",
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"principalService": {
|
"principalService": {
|
||||||
|
|
|
@ -13,6 +13,7 @@ const _actionMap = {
|
||||||
bucketGet: 's3:ListBucket',
|
bucketGet: 's3:ListBucket',
|
||||||
bucketGetACL: 's3:GetBucketAcl',
|
bucketGetACL: 's3:GetBucketAcl',
|
||||||
bucketGetCors: 's3:GetBucketCORS',
|
bucketGetCors: 's3:GetBucketCORS',
|
||||||
|
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
|
||||||
bucketGetVersioning: 's3:GetBucketVersioning',
|
bucketGetVersioning: 's3:GetBucketVersioning',
|
||||||
bucketGetWebsite: 's3:GetBucketWebsite',
|
bucketGetWebsite: 's3:GetBucketWebsite',
|
||||||
bucketGetLocation: 's3:GetBucketLocation',
|
bucketGetLocation: 's3:GetBucketLocation',
|
||||||
|
@ -20,6 +21,7 @@ const _actionMap = {
|
||||||
bucketPut: 's3:CreateBucket',
|
bucketPut: 's3:CreateBucket',
|
||||||
bucketPutACL: 's3:PutBucketAcl',
|
bucketPutACL: 's3:PutBucketAcl',
|
||||||
bucketPutCors: 's3:PutBucketCORS',
|
bucketPutCors: 's3:PutBucketCORS',
|
||||||
|
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
|
||||||
// for bucketDeleteCors need s3:PutBucketCORS permission
|
// for bucketDeleteCors need s3:PutBucketCORS permission
|
||||||
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
|
// see http://docs.aws.amazon.com/AmazonS3/latest/API/
|
||||||
// RESTBucketDELETEcors.html
|
// RESTBucketDELETEcors.html
|
||||||
|
@ -32,6 +34,9 @@ const _actionMap = {
|
||||||
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
|
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
|
||||||
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
|
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
|
||||||
bucketDeleteLifecycle: 's3:DeleteLifecycleConfiguration',
|
bucketDeleteLifecycle: 's3:DeleteLifecycleConfiguration',
|
||||||
|
bucketPutPolicy: 's3:PutBucketPolicy',
|
||||||
|
bucketGetPolicy: 's3:GetBucketPolicy',
|
||||||
|
bucketDeletePolicy: 's3:DeleteBucketPolicy',
|
||||||
completeMultipartUpload: 's3:PutObject',
|
completeMultipartUpload: 's3:PutObject',
|
||||||
initiateMultipartUpload: 's3:PutObject',
|
initiateMultipartUpload: 's3:PutObject',
|
||||||
listMultipartUploads: 's3:ListBucketMultipartUploads',
|
listMultipartUploads: 's3:ListBucketMultipartUploads',
|
||||||
|
@ -45,12 +50,16 @@ const _actionMap = {
|
||||||
objectGetVersion: 's3:GetObjectVersion',
|
objectGetVersion: 's3:GetObjectVersion',
|
||||||
objectGetACL: 's3:GetObjectAcl',
|
objectGetACL: 's3:GetObjectAcl',
|
||||||
objectGetACLVersion: 's3:GetObjectVersionAcl',
|
objectGetACLVersion: 's3:GetObjectVersionAcl',
|
||||||
|
objectGetLegalHold: 's3:GetObjectLegalHold',
|
||||||
|
objectGetRetention: 's3:GetObjectRetention',
|
||||||
objectGetTagging: 's3:GetObjectTagging',
|
objectGetTagging: 's3:GetObjectTagging',
|
||||||
objectGetTaggingVersion: 's3:GetObjectVersionTagging',
|
objectGetTaggingVersion: 's3:GetObjectVersionTagging',
|
||||||
objectHead: 's3:GetObject',
|
objectHead: 's3:GetObject',
|
||||||
objectPut: 's3:PutObject',
|
objectPut: 's3:PutObject',
|
||||||
objectPutACL: 's3:PutObjectAcl',
|
objectPutACL: 's3:PutObjectAcl',
|
||||||
objectPutACLVersion: 's3:PutObjectVersionAcl',
|
objectPutACLVersion: 's3:PutObjectVersionAcl',
|
||||||
|
objectPutLegalHold: 's3:PutObjectLegalHold',
|
||||||
|
objectPutRetention: 's3:PutObjectRetention',
|
||||||
objectPutPart: 's3:PutObject',
|
objectPutPart: 's3:PutObject',
|
||||||
objectPutTagging: 's3:PutObjectTagging',
|
objectPutTagging: 's3:PutObjectTagging',
|
||||||
objectPutTaggingVersion: 's3:PutObjectVersionTagging',
|
objectPutTaggingVersion: 's3:PutObjectVersionTagging',
|
||||||
|
@ -93,6 +102,9 @@ const _actionMapIAM = {
|
||||||
listUsers: 'iam:ListUsers',
|
listUsers: 'iam:ListUsers',
|
||||||
putGroupPolicy: 'iam:PutGroupPolicy',
|
putGroupPolicy: 'iam:PutGroupPolicy',
|
||||||
removeUserFromGroup: 'iam:RemoveUserFromGroup',
|
removeUserFromGroup: 'iam:RemoveUserFromGroup',
|
||||||
|
updateAccessKey: 'iam:UpdateAccessKey',
|
||||||
|
updateGroup: 'iam:UpdateGroup',
|
||||||
|
updateUser: 'iam:UpdateUser',
|
||||||
};
|
};
|
||||||
|
|
||||||
const _actionMapSSO = {
|
const _actionMapSSO = {
|
||||||
|
|
|
@ -67,7 +67,7 @@ function isResourceApplicable(requestContext, statementResource, log) {
|
||||||
* @param {Object} log - logger
|
* @param {Object} log - logger
|
||||||
* @return {boolean} true if applicable, false if not
|
* @return {boolean} true if applicable, false if not
|
||||||
*/
|
*/
|
||||||
function isActionApplicable(requestAction, statementAction, log) {
|
evaluators.isActionApplicable = (requestAction, statementAction, log) => {
|
||||||
if (!Array.isArray(statementAction)) {
|
if (!Array.isArray(statementAction)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
statementAction = [statementAction];
|
statementAction = [statementAction];
|
||||||
|
@ -89,7 +89,7 @@ function isActionApplicable(requestAction, statementAction, log) {
|
||||||
{ requestAction });
|
{ requestAction });
|
||||||
// If no match found, return false
|
// If no match found, return false
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether request meets policy conditions
|
* Check whether request meets policy conditions
|
||||||
|
@ -209,14 +209,14 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
|
||||||
// If affirmative action is in policy and request action is not
|
// If affirmative action is in policy and request action is not
|
||||||
// applicable, move on to next statement
|
// applicable, move on to next statement
|
||||||
if (currentStatement.Action &&
|
if (currentStatement.Action &&
|
||||||
!isActionApplicable(requestContext.getAction(),
|
!evaluators.isActionApplicable(requestContext.getAction(),
|
||||||
currentStatement.Action, log)) {
|
currentStatement.Action, log)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If NotAction is in policy and action matches NotAction in policy,
|
// If NotAction is in policy and action matches NotAction in policy,
|
||||||
// move on to next statement
|
// move on to next statement
|
||||||
if (currentStatement.NotAction &&
|
if (currentStatement.NotAction &&
|
||||||
isActionApplicable(requestContext.getAction(),
|
evaluators.isActionApplicable(requestContext.getAction(),
|
||||||
currentStatement.NotAction, log)) {
|
currentStatement.NotAction, log)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
const { parseString } = require('xml2js');
|
||||||
|
const errors = require('../errors');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Format of the xml request:
|
||||||
|
<LegalHold>
|
||||||
|
<Status>ON|OFF</Status>
|
||||||
|
</LegalHold>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} status - legal hold status parsed from xml to be validated
|
||||||
|
* @return {Error|object} - legal hold status or error
|
||||||
|
*/
|
||||||
|
function _validateStatus(status) {
|
||||||
|
const validatedStatus = {};
|
||||||
|
const expectedValues = new Set(['OFF', 'ON']);
|
||||||
|
if (!status || status[0] === '') {
|
||||||
|
validatedStatus.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Status');
|
||||||
|
return validatedStatus;
|
||||||
|
}
|
||||||
|
if (status.length > 1) {
|
||||||
|
validatedStatus.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one Status');
|
||||||
|
return validatedStatus;
|
||||||
|
}
|
||||||
|
if (!expectedValues.has(status[0])) {
|
||||||
|
validatedStatus.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Status request xml must be one of "ON", "OFF"');
|
||||||
|
return validatedStatus;
|
||||||
|
}
|
||||||
|
validatedStatus.status = status[0];
|
||||||
|
return validatedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validate legal hold - validates legal hold xml
|
||||||
|
* @param {object} parsedXml - parsed legal hold xml object
|
||||||
|
* @return {object} - object with status or error
|
||||||
|
*/
|
||||||
|
function _validateLegalHold(parsedXml) {
|
||||||
|
const validatedLegalHold = {};
|
||||||
|
if (!parsedXml) {
|
||||||
|
validatedLegalHold.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml is undefined or empty');
|
||||||
|
return validatedLegalHold;
|
||||||
|
}
|
||||||
|
if (!parsedXml.LegalHold) {
|
||||||
|
validatedLegalHold.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain LegalHold');
|
||||||
|
return validatedLegalHold;
|
||||||
|
}
|
||||||
|
const validatedStatus = _validateStatus(parsedXml.LegalHold.Status);
|
||||||
|
if (validatedStatus.error) {
|
||||||
|
validatedLegalHold.error = validatedStatus.error;
|
||||||
|
return validatedLegalHold;
|
||||||
|
}
|
||||||
|
validatedLegalHold.status = validatedStatus.status;
|
||||||
|
return validatedLegalHold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse object legal hold - parse and validate xml body
|
||||||
|
* @param {string} xml - xml body to parse and validate
|
||||||
|
* @param {object} log - werelogs logger
|
||||||
|
* @param {function} cb - callback to server
|
||||||
|
* @return {undefined} - calls callback with legal hold status or error
|
||||||
|
*/
|
||||||
|
function parseLegalHoldXml(xml, log, cb) {
|
||||||
|
parseString(xml, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
log.debug('xml parsing failed', {
|
||||||
|
error: { message: err.message },
|
||||||
|
method: 'parseLegalHoldXml',
|
||||||
|
xml,
|
||||||
|
});
|
||||||
|
return cb(errors.MalformedXML);
|
||||||
|
}
|
||||||
|
const validatedLegalHold = _validateLegalHold(result);
|
||||||
|
const validatedLegalHoldStatus = validatedLegalHold.status === 'ON';
|
||||||
|
if (validatedLegalHold.error) {
|
||||||
|
log.debug('legal hold validation failed', {
|
||||||
|
error: { message: validatedLegalHold.error.message },
|
||||||
|
method: 'parseLegalHoldXml',
|
||||||
|
xml,
|
||||||
|
});
|
||||||
|
return cb(validatedLegalHold.error);
|
||||||
|
}
|
||||||
|
return cb(null, validatedLegalHoldStatus);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert to xml - generates legal hold xml
|
||||||
|
* @param {(boolean|undefined)} legalHold - true if legal hold is on
|
||||||
|
* false if legal hold is off, undefined if legal hold is not set
|
||||||
|
* @return {string} - returns legal hold xml
|
||||||
|
*/
|
||||||
|
function convertToXml(legalHold) {
|
||||||
|
if (!legalHold && legalHold !== false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||||
|
`<LegalHold><Status>${legalHold ? 'ON' : 'OFF'}</Status></LegalHold>`;
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
convertToXml,
|
||||||
|
parseLegalHoldXml,
|
||||||
|
};
|
|
@ -0,0 +1,158 @@
|
||||||
|
const { parseString } = require('xml2js');
|
||||||
|
|
||||||
|
const constants = require('../constants');
|
||||||
|
const errors = require('../errors');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Format of xml request:
|
||||||
|
<Retention>
|
||||||
|
<Mode>COMPLIANCE|GOVERNANCE</Mode>
|
||||||
|
<RetainUntilDate>2020-05-20T04:58:45.413000Z</RetainUntilDate>
|
||||||
|
</Retention>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validateMode - validate retention mode
|
||||||
|
* @param {array} mode - parsed xml mode array
|
||||||
|
* @return {object} - contains mode or error
|
||||||
|
*/
|
||||||
|
function validateMode(mode) {
|
||||||
|
const modeObj = {};
|
||||||
|
const expectedModes = new Set(['GOVERNANCE', 'COMPLIANCE']);
|
||||||
|
if (!mode || !mode[0]) {
|
||||||
|
modeObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Mode');
|
||||||
|
return modeObj;
|
||||||
|
}
|
||||||
|
if (mode.length > 1) {
|
||||||
|
modeObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml contains more than one Mode');
|
||||||
|
return modeObj;
|
||||||
|
}
|
||||||
|
if (!expectedModes.has(mode[0])) {
|
||||||
|
modeObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'Mode request xml must be one of "GOVERNANCE", "COMPLIANCE"');
|
||||||
|
return modeObj;
|
||||||
|
}
|
||||||
|
modeObj.mode = mode[0];
|
||||||
|
return modeObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validateRetainDate - validate retain until date
|
||||||
|
* @param {array} retainDate - parsed xml retention date array
|
||||||
|
* @return {object} - contains retain until date or error
|
||||||
|
*/
|
||||||
|
function validateRetainDate(retainDate) {
|
||||||
|
const dateObj = {};
|
||||||
|
if (!retainDate || !retainDate[0]) {
|
||||||
|
dateObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain RetainUntilDate');
|
||||||
|
return dateObj;
|
||||||
|
}
|
||||||
|
if (!constants.shortIso8601Regex.test(retainDate[0]) &&
|
||||||
|
!constants.longIso8601Regex.test(retainDate[0])) {
|
||||||
|
dateObj.error = errors.InvalidRequest.customizeDescription(
|
||||||
|
'RetainUntilDate timestamp must be ISO-8601 format');
|
||||||
|
return dateObj;
|
||||||
|
}
|
||||||
|
const date = new Date(retainDate[0]);
|
||||||
|
if (date < Date.now()) {
|
||||||
|
dateObj.error = errors.InvalidRequest.customizeDescription(
|
||||||
|
'RetainUntilDate must be in the future');
|
||||||
|
return dateObj;
|
||||||
|
}
|
||||||
|
dateObj.date = retainDate[0];
|
||||||
|
return dateObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validate retention - validate retention xml
|
||||||
|
* @param {object} parsedXml - parsed retention xml object
|
||||||
|
* @return {object} - contains retention information on success,
|
||||||
|
* error on failure
|
||||||
|
*/
|
||||||
|
function validateRetention(parsedXml) {
|
||||||
|
const retentionObj = {};
|
||||||
|
if (!parsedXml) {
|
||||||
|
retentionObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml is undefined or empty');
|
||||||
|
return retentionObj;
|
||||||
|
}
|
||||||
|
const retention = parsedXml.Retention;
|
||||||
|
if (!retention) {
|
||||||
|
retentionObj.error = errors.MalformedXML.customizeDescription(
|
||||||
|
'request xml does not contain Retention');
|
||||||
|
return retentionObj;
|
||||||
|
}
|
||||||
|
const modeObj = validateMode(retention.Mode);
|
||||||
|
if (modeObj.error) {
|
||||||
|
retentionObj.error = modeObj.error;
|
||||||
|
return retentionObj;
|
||||||
|
}
|
||||||
|
const dateObj = validateRetainDate(retention.RetainUntilDate);
|
||||||
|
if (dateObj.error) {
|
||||||
|
retentionObj.error = dateObj.error;
|
||||||
|
return retentionObj;
|
||||||
|
}
|
||||||
|
retentionObj.retention = {
|
||||||
|
mode: modeObj.mode,
|
||||||
|
retainUntilDate: dateObj.date,
|
||||||
|
};
|
||||||
|
return retentionObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseRetentionXml - Parse and validate xml body, returning callback with
|
||||||
|
* object retentionInfo: { mode: <value>, retainUntilDate: <value> }
|
||||||
|
* @param {string} xml - xml body to parse and validate
|
||||||
|
* @param {object} log - Werelogs logger
|
||||||
|
* @param {function} cb - callback to server
|
||||||
|
* @return {undefined} - calls callback with object retention or error
|
||||||
|
*/
|
||||||
|
function parseRetentionXml(xml, log, cb) {
|
||||||
|
parseString(xml, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
log.trace('xml parsing failed', {
|
||||||
|
error: err,
|
||||||
|
method: 'parseRetentionXml',
|
||||||
|
});
|
||||||
|
log.debug('invalid xml', { xml });
|
||||||
|
return cb(errors.MalformedXML);
|
||||||
|
}
|
||||||
|
const retentionObj = validateRetention(result);
|
||||||
|
if (retentionObj.error) {
|
||||||
|
log.debug('retention validation failed', {
|
||||||
|
error: retentionObj.error,
|
||||||
|
method: 'validateRetention',
|
||||||
|
xml,
|
||||||
|
});
|
||||||
|
return cb(retentionObj.error);
|
||||||
|
}
|
||||||
|
return cb(null, retentionObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convertToXml - Convert retention info object to xml
|
||||||
|
* @param {object} retentionInfo - retention information object
|
||||||
|
* @return {string} - returns retention information xml string
|
||||||
|
*/
|
||||||
|
function convertToXml(retentionInfo) {
|
||||||
|
const xml = [];
|
||||||
|
const ret = retentionInfo.retention;
|
||||||
|
xml.push('<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">');
|
||||||
|
if (ret && ret.mode && ret.retainUntilDate) {
|
||||||
|
xml.push(`<Mode>${ret.mode}</Mode>`);
|
||||||
|
xml.push(`<RetainUntilDate>${ret.retainUntilDate}</RetainUntilDate>`);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
xml.push('</Retention>');
|
||||||
|
return xml.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parseRetentionXml,
|
||||||
|
convertToXml,
|
||||||
|
};
|
|
@ -45,6 +45,13 @@ function routeDELETE(request, response, api, log, statsClient) {
|
||||||
return routesUtils.responseNoBody(err, corsHeaders,
|
return routesUtils.responseNoBody(err, corsHeaders,
|
||||||
response, 204, log);
|
response, 204, log);
|
||||||
});
|
});
|
||||||
|
} else if (request.query.policy !== undefined) {
|
||||||
|
return api.callApiMethod('bucketDeletePolicy', request,
|
||||||
|
response, log, (err, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseNoBody(err, corsHeaders,
|
||||||
|
response, 204, log);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
api.callApiMethod('bucketDelete', request, response, log,
|
api.callApiMethod('bucketDelete', request, response, log,
|
||||||
(err, corsHeaders) => {
|
(err, corsHeaders) => {
|
||||||
|
|
|
@ -51,11 +51,11 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
});
|
});
|
||||||
} else if (request.query.lifecycle !== undefined) {
|
} else if (request.query.lifecycle !== undefined) {
|
||||||
api.callApiMethod('bucketGetLifecycle', request, response, log,
|
api.callApiMethod('bucketGetLifecycle', request, response, log,
|
||||||
(err, xml, corsHeaders) => {
|
(err, xml, corsHeaders) => {
|
||||||
routesUtils.statsReport500(err, statsClient);
|
routesUtils.statsReport500(err, statsClient);
|
||||||
routesUtils.responseXMLBody(err, xml, response, log,
|
routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
corsHeaders);
|
corsHeaders);
|
||||||
});
|
});
|
||||||
} else if (request.query.uploads !== undefined) {
|
} else if (request.query.uploads !== undefined) {
|
||||||
// List MultipartUploads
|
// List MultipartUploads
|
||||||
api.callApiMethod('listMultipartUploads', request, response, log,
|
api.callApiMethod('listMultipartUploads', request, response, log,
|
||||||
|
@ -71,6 +71,20 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
return routesUtils.responseXMLBody(err, xml, response, log,
|
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
corsHeaders);
|
corsHeaders);
|
||||||
});
|
});
|
||||||
|
} else if (request.query.policy !== undefined) {
|
||||||
|
api.callApiMethod('bucketGetPolicy', request, response, log,
|
||||||
|
(err, xml, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseXMLBody(err, xml, response,
|
||||||
|
log, corsHeaders);
|
||||||
|
});
|
||||||
|
} else if (request.query['object-lock'] !== undefined) {
|
||||||
|
api.callApiMethod('bucketGetObjectLock', request, response, log,
|
||||||
|
(err, xml, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseXMLBody(err, xml, response,
|
||||||
|
log, corsHeaders);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// GET bucket
|
// GET bucket
|
||||||
api.callApiMethod('bucketGet', request, response, log,
|
api.callApiMethod('bucketGet', request, response, log,
|
||||||
|
@ -81,7 +95,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* eslint-disable no-lonely-if */
|
|
||||||
if (request.query.acl !== undefined) {
|
if (request.query.acl !== undefined) {
|
||||||
// GET object ACL
|
// GET object ACL
|
||||||
api.callApiMethod('objectGetACL', request, response, log,
|
api.callApiMethod('objectGetACL', request, response, log,
|
||||||
|
@ -90,8 +103,14 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
return routesUtils.responseXMLBody(err, xml, response, log,
|
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
corsHeaders);
|
corsHeaders);
|
||||||
});
|
});
|
||||||
|
} else if (request.query['legal-hold'] !== undefined) {
|
||||||
|
api.callApiMethod('objectGetLegalHold', request, response, log,
|
||||||
|
(err, xml, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
|
corsHeaders);
|
||||||
|
});
|
||||||
} else if (request.query.tagging !== undefined) {
|
} else if (request.query.tagging !== undefined) {
|
||||||
// GET object Tagging
|
|
||||||
api.callApiMethod('objectGetTagging', request, response, log,
|
api.callApiMethod('objectGetTagging', request, response, log,
|
||||||
(err, xml, corsHeaders) => {
|
(err, xml, corsHeaders) => {
|
||||||
routesUtils.statsReport500(err, statsClient);
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
@ -106,6 +125,13 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
return routesUtils.responseXMLBody(err, xml, response, log,
|
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
corsHeaders);
|
corsHeaders);
|
||||||
});
|
});
|
||||||
|
} else if (request.query.retention !== undefined) {
|
||||||
|
api.callApiMethod('objectGetRetention', request, response, log,
|
||||||
|
(err, xml, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||||
|
corsHeaders);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// GET object
|
// GET object
|
||||||
api.callApiMethod('objectGet', request, response, log,
|
api.callApiMethod('objectGet', request, response, log,
|
||||||
|
@ -121,7 +147,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
||||||
range, log);
|
range, log);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
return routesUtils.responseNoBody(
|
return routesUtils.responseNoBody(
|
||||||
errors.BadRequest, null, response, null, log);
|
errors.BadRequest, null, response, null, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT bucket ACL
|
// PUT bucket ACL
|
||||||
if (request.query.acl !== undefined) {
|
if (request.query.acl !== undefined) {
|
||||||
api.callApiMethod('bucketPutACL', request, response, log,
|
api.callApiMethod('bucketPutACL', request, response, log,
|
||||||
|
@ -60,6 +59,20 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||||
log);
|
log);
|
||||||
});
|
});
|
||||||
|
} else if (request.query.policy !== undefined) {
|
||||||
|
api.callApiMethod('bucketPutPolicy', request, response, log,
|
||||||
|
(err, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||||
|
log);
|
||||||
|
});
|
||||||
|
} else if (request.query['object-lock'] !== undefined) {
|
||||||
|
api.callApiMethod('bucketPutObjectLock', request, response, log,
|
||||||
|
(err, corsHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||||
|
log);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// PUT bucket
|
// PUT bucket
|
||||||
return api.callApiMethod('bucketPut', request, response, log,
|
return api.callApiMethod('bucketPut', request, response, log,
|
||||||
|
@ -73,8 +86,8 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// PUT object, PUT object ACL, PUT object multipart or
|
// PUT object, PUT object ACL, PUT object multipart,
|
||||||
// PUT object copy
|
// PUT object copy or PUT object legal hold
|
||||||
// if content-md5 is not present in the headers, try to
|
// if content-md5 is not present in the headers, try to
|
||||||
// parse content-md5 from meta headers
|
// parse content-md5 from meta headers
|
||||||
|
|
||||||
|
@ -132,6 +145,13 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
return routesUtils.responseNoBody(err, resHeaders,
|
return routesUtils.responseNoBody(err, resHeaders,
|
||||||
response, 200, log);
|
response, 200, log);
|
||||||
});
|
});
|
||||||
|
} else if (request.query['legal-hold'] !== undefined) {
|
||||||
|
api.callApiMethod('objectPutLegalHold', request, response, log,
|
||||||
|
(err, resHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseNoBody(err, resHeaders,
|
||||||
|
response, 200, log);
|
||||||
|
});
|
||||||
} else if (request.query.tagging !== undefined) {
|
} else if (request.query.tagging !== undefined) {
|
||||||
api.callApiMethod('objectPutTagging', request, response, log,
|
api.callApiMethod('objectPutTagging', request, response, log,
|
||||||
(err, resHeaders) => {
|
(err, resHeaders) => {
|
||||||
|
@ -139,6 +159,13 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
return routesUtils.responseNoBody(err, resHeaders,
|
return routesUtils.responseNoBody(err, resHeaders,
|
||||||
response, 200, log);
|
response, 200, log);
|
||||||
});
|
});
|
||||||
|
} else if (request.query.retention !== undefined) {
|
||||||
|
api.callApiMethod('objectPutRetention', request, response, log,
|
||||||
|
(err, resHeaders) => {
|
||||||
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
return routesUtils.responseNoBody(err, resHeaders,
|
||||||
|
response, 200, log);
|
||||||
|
});
|
||||||
} else if (request.headers['x-amz-copy-source']) {
|
} else if (request.headers['x-amz-copy-source']) {
|
||||||
return api.callApiMethod('objectCopy', request, response, log,
|
return api.callApiMethod('objectCopy', request, response, log,
|
||||||
(err, xml, additionalHeaders) => {
|
(err, xml, additionalHeaders) => {
|
||||||
|
@ -160,7 +187,6 @@ function routePUT(request, response, api, log, statsClient) {
|
||||||
log.end().addDefaultFields({
|
log.end().addDefaultFields({
|
||||||
contentLength: request.parsedContentLength,
|
contentLength: request.parsedContentLength,
|
||||||
});
|
});
|
||||||
|
|
||||||
api.callApiMethod('objectPut', request, response, log,
|
api.callApiMethod('objectPut', request, response, log,
|
||||||
(err, resHeaders) => {
|
(err, resHeaders) => {
|
||||||
routesUtils.statsReport500(err, statsClient);
|
routesUtils.statsReport500(err, statsClient);
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"async": "~2.1.5",
|
"async": "~2.1.5",
|
||||||
"debug": "~2.3.3",
|
"debug": "~2.3.3",
|
||||||
"diskusage": "^1.1.1",
|
"diskusage": "^1.1.1",
|
||||||
"ioredis": "4.9.5",
|
"ioredis": "4.17.3",
|
||||||
"ipaddr.js": "1.2.0",
|
"ipaddr.js": "1.2.0",
|
||||||
"level": "~5.0.1",
|
"level": "~5.0.1",
|
||||||
"level-sublevel": "~6.6.5",
|
"level-sublevel": "~6.6.5",
|
||||||
|
@ -46,6 +46,7 @@
|
||||||
"eslint-config-scality": "scality/Guidelines#ec33dfb",
|
"eslint-config-scality": "scality/Guidelines#ec33dfb",
|
||||||
"lolex": "1.5.2",
|
"lolex": "1.5.2",
|
||||||
"mocha": "2.5.3",
|
"mocha": "2.5.3",
|
||||||
|
"sinon": "^9.0.2",
|
||||||
"temp": "0.8.3"
|
"temp": "0.8.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const async = require('async');
|
||||||
|
const TTLVCodec = require('../../../lib/network/kmip/codec/ttlv.js');
|
||||||
|
const LoopbackServerChannel =
|
||||||
|
require('../../utils/kmip/LoopbackServerChannel.js');
|
||||||
|
const TransportTemplate =
|
||||||
|
require('../../../lib/network/kmip/transport/TransportTemplate.js');
|
||||||
|
const KMIP = require('../../../lib/network/kmip');
|
||||||
|
const KMIPClient = require('../../../lib/network/kmip/Client.js');
|
||||||
|
const {
|
||||||
|
logger,
|
||||||
|
} = require('../../utils/kmip/ersatz.js');
|
||||||
|
|
||||||
|
class LoopbackServerTransport extends TransportTemplate {
|
||||||
|
constructor(options) {
|
||||||
|
super(new LoopbackServerChannel(KMIP, TTLVCodec), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('KMIP High Level Driver', () => {
|
||||||
|
[null, 'dummyAttributeName'].forEach(bucketNameAttributeName => {
|
||||||
|
[false, true].forEach(compoundCreateActivate => {
|
||||||
|
const options = {
|
||||||
|
kmip: {
|
||||||
|
client: {
|
||||||
|
bucketNameAttributeName,
|
||||||
|
compoundCreateActivate,
|
||||||
|
},
|
||||||
|
codec: {},
|
||||||
|
transport: {
|
||||||
|
pipelineDepth: 8,
|
||||||
|
tls: {
|
||||||
|
port: 5696,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
it('should work with' +
|
||||||
|
` x-name attribute: ${!!bucketNameAttributeName},` +
|
||||||
|
` compound creation: ${compoundCreateActivate}`,
|
||||||
|
done => {
|
||||||
|
const kmipClient = new KMIPClient(options, TTLVCodec,
|
||||||
|
LoopbackServerTransport);
|
||||||
|
const plaintext = Buffer.from(crypto.randomBytes(32));
|
||||||
|
async.waterfall([
|
||||||
|
next => kmipClient.createBucketKey('plop', logger, next),
|
||||||
|
(id, next) =>
|
||||||
|
kmipClient.cipherDataKey(1, id, plaintext,
|
||||||
|
logger, (err, ciphered) => {
|
||||||
|
next(err, id, ciphered);
|
||||||
|
}),
|
||||||
|
(id, ciphered, next) =>
|
||||||
|
kmipClient.decipherDataKey(
|
||||||
|
1, id, ciphered, logger, (err, deciphered) => {
|
||||||
|
assert(plaintext
|
||||||
|
.compare(deciphered) === 0);
|
||||||
|
next(err, id);
|
||||||
|
}),
|
||||||
|
(id, next) =>
|
||||||
|
kmipClient.destroyBucketKey(id, logger, next),
|
||||||
|
], done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,52 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const TTLVCodec = require('../../../lib/network/kmip/codec/ttlv.js');
|
||||||
|
const TransportTemplate =
|
||||||
|
require('../../../lib/network/kmip/transport/TransportTemplate.js');
|
||||||
|
const KMIP = require('../../../lib/network/kmip');
|
||||||
|
const {
|
||||||
|
logger,
|
||||||
|
MirrorChannel,
|
||||||
|
} = require('../../utils/kmip/ersatz.js');
|
||||||
|
const lowlevelFixtures = require('../../utils/kmip/lowlevelFixtures.js');
|
||||||
|
|
||||||
|
|
||||||
|
class MirrorTransport extends TransportTemplate {
|
||||||
|
constructor(options) {
|
||||||
|
super(new MirrorChannel(KMIP, TTLVCodec), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
kmip: {
|
||||||
|
codec: {},
|
||||||
|
transport: {
|
||||||
|
pipelineDepth: 8,
|
||||||
|
tls: {
|
||||||
|
port: 5696,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('KMIP Low Level Driver', () => {
|
||||||
|
lowlevelFixtures.forEach((fixture, n) => {
|
||||||
|
it(`should work with fixture #${n}`, done => {
|
||||||
|
const kmip = new KMIP(TTLVCodec, MirrorTransport, options);
|
||||||
|
const requestPayload = fixture.payload(kmip);
|
||||||
|
kmip.request(logger, fixture.operation,
|
||||||
|
requestPayload, (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
const responsePayload = response.lookup(
|
||||||
|
'Response Message/Batch Item/Response Payload'
|
||||||
|
)[0];
|
||||||
|
assert.deepStrictEqual(responsePayload,
|
||||||
|
requestPayload);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,82 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
|
||||||
|
const async = require('async');
|
||||||
|
const assert = require('assert');
|
||||||
|
const TransportTemplate =
|
||||||
|
require('../../../lib/network/kmip/transport/TransportTemplate.js');
|
||||||
|
const { logger, EchoChannel } = require('../../utils/kmip/ersatz.js');
|
||||||
|
|
||||||
|
describe('KMIP Transport Template Class', () => {
|
||||||
|
const pipelineDepths = [1, 2, 4, 8, 16, 32];
|
||||||
|
const requestNumbers = [1, 37, 1021, 8191];
|
||||||
|
|
||||||
|
pipelineDepths.forEach(pipelineDepth => {
|
||||||
|
requestNumbers.forEach(iterations => {
|
||||||
|
it(`should survive ${iterations} iterations` +
|
||||||
|
` with ${pipelineDepth}way pipeline`,
|
||||||
|
done => {
|
||||||
|
const transport = new TransportTemplate(
|
||||||
|
new EchoChannel,
|
||||||
|
{
|
||||||
|
pipelineDepth,
|
||||||
|
tls: {
|
||||||
|
port: 5696,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const request = Buffer.alloc(10).fill(6);
|
||||||
|
async.times(iterations, (n, next) => {
|
||||||
|
transport.send(logger, request,
|
||||||
|
(err, conversation, response) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
if (request.compare(response) !== 0) {
|
||||||
|
return next(Error('arg'));
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
}, err => {
|
||||||
|
transport.end();
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[true, false].forEach(doEmit => {
|
||||||
|
it('should report errors to outstanding requests.' +
|
||||||
|
` w:${pipelineDepth}, i:${iterations}, e:${doEmit}`,
|
||||||
|
done => {
|
||||||
|
const echoChannel = new EchoChannel;
|
||||||
|
echoChannel.clog();
|
||||||
|
const transport = new TransportTemplate(
|
||||||
|
echoChannel,
|
||||||
|
{
|
||||||
|
pipelineDepth,
|
||||||
|
tls: {
|
||||||
|
port: 5696,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const request = Buffer.alloc(10).fill(6);
|
||||||
|
/* Using a for loop here instead of anything
|
||||||
|
* asynchronous, the callbacks get stuck in
|
||||||
|
* the conversation queue and are unwind with
|
||||||
|
* an error. It is the purpose of this test */
|
||||||
|
for (let i = 0; i < iterations; ++i) {
|
||||||
|
transport.send(
|
||||||
|
logger, request,
|
||||||
|
(err, conversation, response) => {
|
||||||
|
assert(err);
|
||||||
|
assert(!response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (doEmit) {
|
||||||
|
echoChannel.emit('error', new Error('awesome'));
|
||||||
|
} else {
|
||||||
|
transport.abortPipeline(echoChannel);
|
||||||
|
}
|
||||||
|
transport.end();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
|
||||||
const queryAuthCheck =
|
const queryAuthCheck =
|
||||||
require('../../../../lib/auth/v2/queryAuthCheck').check;
|
require('../../../../lib/auth/v2/queryAuthCheck').check;
|
||||||
|
@ -26,3 +27,97 @@ describe('v2: queryAuthCheck', () => {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('v2: queryAuthCheck', () => {
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers();
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.PRE_SIGN_URL_EXPIRY = 604800000;
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
it('URL should not expire before 7 days with default expiry', () => {
|
||||||
|
const currentTime = Date.now() / 1000;
|
||||||
|
const expires = currentTime + 604799; // in seconds
|
||||||
|
const mockRequest = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'mockurl',
|
||||||
|
query: {
|
||||||
|
Expires: expires,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': 'c',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const data = {
|
||||||
|
Expires: expires,
|
||||||
|
AWSAccessKeyId: 'keyId',
|
||||||
|
Signature: 'sign',
|
||||||
|
};
|
||||||
|
const res = queryAuthCheck(mockRequest, log, data);
|
||||||
|
assert.notStrictEqual(res.err.AccessDenied, true);
|
||||||
|
assert.notStrictEqual(res.err.RequestTimeTooSkewed, true);
|
||||||
|
});
|
||||||
|
it('URL should expire after 7 days with default expiry', () => {
|
||||||
|
clock.tick(604800000); // take time 604800000ms (7 days) ahead
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const request = { method: 'GET', query: { Expires: currentTime } };
|
||||||
|
const data = { Expires: currentTime };
|
||||||
|
const res = queryAuthCheck(request, log, data);
|
||||||
|
assert.notStrictEqual(res.err, null);
|
||||||
|
assert.notStrictEqual(res.err, undefined);
|
||||||
|
assert.strictEqual(res.err.AccessDenied, true);
|
||||||
|
});
|
||||||
|
it('URL should not expire before 7 days with custom expiry', () => {
|
||||||
|
process.env.PRE_SIGN_URL_EXPIRY = 31556952000; // in ms (1 year)
|
||||||
|
const currentTime = Date.now() / 1000;
|
||||||
|
const expires = currentTime + 604799; // in seconds
|
||||||
|
const mockRequest = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'mockurl',
|
||||||
|
query: {
|
||||||
|
Expires: expires,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': 'c',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const data = {
|
||||||
|
Expires: expires,
|
||||||
|
AWSAccessKeyId: 'keyId',
|
||||||
|
Signature: 'sign',
|
||||||
|
};
|
||||||
|
const res = queryAuthCheck(mockRequest, log, data);
|
||||||
|
assert.notStrictEqual(res.err.AccessDenied, true);
|
||||||
|
assert.notStrictEqual(res.err.RequestTimeTooSkewed, true);
|
||||||
|
});
|
||||||
|
it('URL should still not expire after 7 days with custom expiry', () => {
|
||||||
|
clock.tick(604800000); // take time 604800000ms (7 days) ahead
|
||||||
|
process.env.PRE_SIGN_URL_EXPIRY = 31556952000; // in ms (1 year)
|
||||||
|
const currentTime = Date.now() / 1000;
|
||||||
|
const request = { method: 'GET', query: { Expires: currentTime } };
|
||||||
|
const data = { Expires: currentTime };
|
||||||
|
const res = queryAuthCheck(request, log, data);
|
||||||
|
assert.notStrictEqual(res.err.AccessDenied, true);
|
||||||
|
});
|
||||||
|
it('should return RequestTimeTooSkewed with current time > expiry', () => {
|
||||||
|
clock.tick(123);
|
||||||
|
const expires = 0;
|
||||||
|
const request = { method: 'GET', query: { Expires: expires } };
|
||||||
|
const data = { Expires: expires };
|
||||||
|
const res = queryAuthCheck(request, log, data);
|
||||||
|
assert.notStrictEqual(res.err, null);
|
||||||
|
assert.notStrictEqual(res.err, undefined);
|
||||||
|
assert.strictEqual(res.err.RequestTimeTooSkewed, true);
|
||||||
|
});
|
||||||
|
it('should return MissingSecurityHeader with invalid expires param', () => {
|
||||||
|
const request = { method: 'GET', query: { Expires: 'a string' } };
|
||||||
|
const data = { Expires: 'a string' };
|
||||||
|
const res = queryAuthCheck(request, log, data);
|
||||||
|
assert.notStrictEqual(res.err, null);
|
||||||
|
assert.notStrictEqual(res.err, undefined);
|
||||||
|
assert.strictEqual(res.err.MissingSecurityHeader, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const TTLVCodec = require('../../../lib/network/kmip/codec/ttlv.js');
|
||||||
|
const KMIP = require('../../../lib/network/kmip');
|
||||||
|
const ttlvFixtures = require('../../utils/kmip/ttlvFixtures');
|
||||||
|
const badTtlvFixtures = require('../../utils/kmip/badTtlvFixtures');
|
||||||
|
const messageFixtures = require('../../utils/kmip/messageFixtures');
|
||||||
|
const { logger } = require('../../utils/kmip/ersatz.js');
|
||||||
|
|
||||||
|
function newKMIP() {
|
||||||
|
return new KMIP(TTLVCodec,
|
||||||
|
class DummyTransport {},
|
||||||
|
{ kmip: {} }, () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('KMIP TTLV Codec', () => {
|
||||||
|
it('should map, encode and decode an extension', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
kmip.mapExtension('Dummy Extension', 0x54a000);
|
||||||
|
const msg = KMIP.Message([
|
||||||
|
KMIP.TextString('Dummy Extension', 'beautiful'),
|
||||||
|
]);
|
||||||
|
const encodedMsg = kmip._encodeMessage(msg);
|
||||||
|
const decodedMsg = kmip._decodeMessage(logger, encodedMsg);
|
||||||
|
assert.deepStrictEqual(msg, decodedMsg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
ttlvFixtures.forEach((item, idx) => {
|
||||||
|
['request', 'response'].forEach(fixture => {
|
||||||
|
it(`should decode the TTLV ${fixture} fixture[${idx}]`, done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const msg = kmip._decodeMessage(logger, item[fixture]);
|
||||||
|
if (!item.degenerated) {
|
||||||
|
const encodedMsg = kmip._encodeMessage(msg);
|
||||||
|
assert(encodedMsg.compare(item[fixture]) === 0);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate supported operations', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const msg = kmip._decodeMessage(logger, ttlvFixtures[1].response);
|
||||||
|
|
||||||
|
const supportedOperations =
|
||||||
|
msg.lookup('Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Operation');
|
||||||
|
const supportedObjectTypes =
|
||||||
|
msg.lookup('Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Object Type');
|
||||||
|
const protocolVersionMajor =
|
||||||
|
msg.lookup('Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Major');
|
||||||
|
const protocolVersionMinor =
|
||||||
|
msg.lookup('Response Message/Response Header/' +
|
||||||
|
'Protocol Version/Protocol Version Minor');
|
||||||
|
|
||||||
|
assert(supportedOperations.includes('Encrypt'));
|
||||||
|
assert(supportedOperations.includes('Decrypt'));
|
||||||
|
assert(supportedOperations.includes('Create'));
|
||||||
|
assert(supportedOperations.includes('Destroy'));
|
||||||
|
assert(supportedOperations.includes('Query'));
|
||||||
|
assert(supportedObjectTypes.includes('Symmetric Key'));
|
||||||
|
assert(protocolVersionMajor[0] >= 2 ||
|
||||||
|
(protocolVersionMajor[0] === 1 &&
|
||||||
|
protocolVersionMinor[0] >= 2));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect unsupported operations', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const msg = kmip._decodeMessage(logger, ttlvFixtures[2].response);
|
||||||
|
|
||||||
|
const supportedOperations =
|
||||||
|
msg.lookup('Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Operation');
|
||||||
|
|
||||||
|
assert(!supportedOperations.includes('Encrypt'));
|
||||||
|
assert(!supportedOperations.includes('Decrypt'));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support non canonical search path', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const msg = kmip._decodeMessage(logger, ttlvFixtures[1].response);
|
||||||
|
|
||||||
|
const supportedOperations =
|
||||||
|
msg.lookup('/Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Operation');
|
||||||
|
const supportedObjectTypes =
|
||||||
|
msg.lookup('Response Message/Batch Item/' +
|
||||||
|
'Response Payload/Object Type/');
|
||||||
|
const protocolVersionMajor =
|
||||||
|
msg.lookup('Response Message//Response Header///' +
|
||||||
|
'Protocol Version////Protocol Version Major');
|
||||||
|
const protocolVersionMinor =
|
||||||
|
msg.lookup('/Response Message////Response Header///' +
|
||||||
|
'Protocol Version//Protocol Version Minor/');
|
||||||
|
|
||||||
|
assert(supportedOperations.includes('Encrypt'));
|
||||||
|
assert(supportedOperations.includes('Decrypt'));
|
||||||
|
assert(supportedOperations.includes('Create'));
|
||||||
|
assert(supportedOperations.includes('Destroy'));
|
||||||
|
assert(supportedOperations.includes('Query'));
|
||||||
|
assert(supportedObjectTypes.includes('Symmetric Key'));
|
||||||
|
assert(protocolVersionMajor[0] >= 2 ||
|
||||||
|
(protocolVersionMajor[0] === 1 &&
|
||||||
|
protocolVersionMinor[0] >= 2));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return nothing with an empty search path', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const msg = kmip._decodeMessage(logger, ttlvFixtures[2].response);
|
||||||
|
|
||||||
|
const empty1 = msg.lookup('');
|
||||||
|
const empty2 = msg.lookup('////');
|
||||||
|
assert(empty1.length === 0);
|
||||||
|
assert(empty2.length === 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode/decode a bit mask', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const usageMask = ['Encrypt', 'Decrypt', 'Export'];
|
||||||
|
const decodedMask =
|
||||||
|
kmip.decodeMask('Cryptographic Usage Mask',
|
||||||
|
kmip.encodeMask('Cryptographic Usage Mask',
|
||||||
|
usageMask));
|
||||||
|
assert.deepStrictEqual(usageMask.sort(), decodedMask.sort());
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect invalid bit name', done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const usageMask = ['Encrypt', 'Decrypt', 'Exprot'];
|
||||||
|
try {
|
||||||
|
kmip.encodeMask('Cryptographic Usage Mask', usageMask);
|
||||||
|
done(Error('Must not succeed'));
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageFixtures.forEach((item, idx) => {
|
||||||
|
it(`should encode the KMIP message fixture[${idx}]`, done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
const encodedMessage = kmip._encodeMessage(item);
|
||||||
|
const decodedMessage = kmip._decodeMessage(logger, encodedMessage);
|
||||||
|
assert.deepStrictEqual(item.content, decodedMessage.content);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
badTtlvFixtures.forEach((rawMessage, idx) => {
|
||||||
|
it(`should fail to parse invalid TTLV message fixture[${idx}]`,
|
||||||
|
done => {
|
||||||
|
const kmip = newKMIP();
|
||||||
|
try {
|
||||||
|
kmip._decodeMessage(logger, rawMessage);
|
||||||
|
done(Error('Must not succeed'));
|
||||||
|
} catch (e) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -115,8 +115,29 @@ const testLifecycleConfiguration = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
// create a dummy bucket to test getters and setters
|
|
||||||
|
|
||||||
|
const testBucketPolicy = {
|
||||||
|
Version: '2012-10-17',
|
||||||
|
Statement: [
|
||||||
|
{
|
||||||
|
Effect: 'Allow',
|
||||||
|
Principal: '*',
|
||||||
|
Resource: 'arn:aws:s3:::examplebucket',
|
||||||
|
Action: 's3:*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const testobjectLockEnabled = false;
|
||||||
|
|
||||||
|
const testObjectLockConfiguration = {
|
||||||
|
rule: {
|
||||||
|
mode: 'GOVERNANCE',
|
||||||
|
days: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a dummy bucket to test getters and setters
|
||||||
Object.keys(acl).forEach(
|
Object.keys(acl).forEach(
|
||||||
aclObj => describe(`different acl configurations : ${aclObj}`, () => {
|
aclObj => describe(`different acl configurations : ${aclObj}`, () => {
|
||||||
const dummyBucket = new BucketInfo(
|
const dummyBucket = new BucketInfo(
|
||||||
|
@ -132,7 +153,10 @@ Object.keys(acl).forEach(
|
||||||
testWebsiteConfiguration,
|
testWebsiteConfiguration,
|
||||||
testCorsConfiguration,
|
testCorsConfiguration,
|
||||||
testReplicationConfiguration,
|
testReplicationConfiguration,
|
||||||
testLifecycleConfiguration);
|
testLifecycleConfiguration,
|
||||||
|
testBucketPolicy,
|
||||||
|
testobjectLockEnabled,
|
||||||
|
testObjectLockConfiguration);
|
||||||
|
|
||||||
describe('serialize/deSerialize on BucketInfo class', () => {
|
describe('serialize/deSerialize on BucketInfo class', () => {
|
||||||
const serialized = dummyBucket.serialize();
|
const serialized = dummyBucket.serialize();
|
||||||
|
@ -158,6 +182,10 @@ Object.keys(acl).forEach(
|
||||||
dummyBucket._replicationConfiguration,
|
dummyBucket._replicationConfiguration,
|
||||||
lifecycleConfiguration:
|
lifecycleConfiguration:
|
||||||
dummyBucket._lifecycleConfiguration,
|
dummyBucket._lifecycleConfiguration,
|
||||||
|
bucketPolicy: dummyBucket._bucketPolicy,
|
||||||
|
objectLockEnabled: dummyBucket._objectLockEnabled,
|
||||||
|
objectLockConfiguration:
|
||||||
|
dummyBucket._objectLockConfiguration,
|
||||||
};
|
};
|
||||||
assert.strictEqual(serialized, JSON.stringify(bucketInfos));
|
assert.strictEqual(serialized, JSON.stringify(bucketInfos));
|
||||||
done();
|
done();
|
||||||
|
@ -174,15 +202,16 @@ Object.keys(acl).forEach(
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('this should have the right BucketInfo types',
|
it('this should have the right BucketInfo types', () => {
|
||||||
() => {
|
assert.strictEqual(typeof dummyBucket.getName(), 'string');
|
||||||
assert.strictEqual(typeof dummyBucket.getName(), 'string');
|
assert.strictEqual(typeof dummyBucket.getOwner(), 'string');
|
||||||
assert.strictEqual(typeof dummyBucket.getOwner(), 'string');
|
assert.strictEqual(typeof dummyBucket.getOwnerDisplayName(),
|
||||||
assert.strictEqual(typeof dummyBucket.getOwnerDisplayName(),
|
'string');
|
||||||
'string');
|
assert.strictEqual(typeof dummyBucket.getCreationDate(),
|
||||||
assert.strictEqual(typeof dummyBucket.getCreationDate(),
|
'string');
|
||||||
'string');
|
assert.strictEqual(typeof dummyBucket.isObjectLockEnabled(),
|
||||||
});
|
'boolean');
|
||||||
|
});
|
||||||
it('this should have the right acl\'s types', () => {
|
it('this should have the right acl\'s types', () => {
|
||||||
assert.strictEqual(typeof dummyBucket.getAcl(), 'object');
|
assert.strictEqual(typeof dummyBucket.getAcl(), 'object');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
@ -257,6 +286,18 @@ Object.keys(acl).forEach(
|
||||||
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
||||||
testLifecycleConfiguration);
|
testLifecycleConfiguration);
|
||||||
});
|
});
|
||||||
|
it('getBucketPolicy should return policy', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
dummyBucket.getBucketPolicy(), testBucketPolicy);
|
||||||
|
});
|
||||||
|
it('object lock should be disabled by default', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
dummyBucket.isObjectLockEnabled(), false);
|
||||||
|
});
|
||||||
|
it('getObjectLockConfiguration should return configuration', () => {
|
||||||
|
assert.deepStrictEqual(dummyBucket.getObjectLockConfiguration(),
|
||||||
|
testObjectLockConfiguration);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setters on BucketInfo class', () => {
|
describe('setters on BucketInfo class', () => {
|
||||||
|
@ -378,6 +419,34 @@ Object.keys(acl).forEach(
|
||||||
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
||||||
newLifecycleConfig);
|
newLifecycleConfig);
|
||||||
});
|
});
|
||||||
|
it('setBucketPolicy should set bucket policy', () => {
|
||||||
|
const newBucketPolicy = {
|
||||||
|
Version: '2012-10-17',
|
||||||
|
Statement: [
|
||||||
|
{
|
||||||
|
Effect: 'Deny',
|
||||||
|
Principal: '*',
|
||||||
|
Resource: 'arn:aws:s3:::examplebucket',
|
||||||
|
Action: 's3:*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
dummyBucket.setBucketPolicy(newBucketPolicy);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
dummyBucket.getBucketPolicy(), newBucketPolicy);
|
||||||
|
});
|
||||||
|
it('setObjectLockConfiguration should set object lock ' +
|
||||||
|
'configuration', () => {
|
||||||
|
const newObjectLockConfig = {
|
||||||
|
rule: {
|
||||||
|
mode: 'COMPLIANCE',
|
||||||
|
years: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
dummyBucket.setObjectLockConfiguration(newObjectLockConfig);
|
||||||
|
assert.deepStrictEqual(dummyBucket.getObjectLockConfiguration(),
|
||||||
|
newObjectLockConfig);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const BucketPolicy = require('../../../lib/models/BucketPolicy');
|
||||||
|
|
||||||
|
const testBucketPolicy = {
|
||||||
|
Version: '2012-10-17',
|
||||||
|
Statement: [
|
||||||
|
{
|
||||||
|
Effect: 'Allow',
|
||||||
|
Principal: '*',
|
||||||
|
Resource: 'arn:aws:s3:::examplebucket',
|
||||||
|
Action: 's3:GetBucketLocation',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const mismatchErr = 'Action does not apply to any resource(s) in statement';
|
||||||
|
|
||||||
|
function createPolicy(key, value) {
|
||||||
|
const newPolicy = Object.assign({}, testBucketPolicy);
|
||||||
|
newPolicy.Statement[0][key] = value;
|
||||||
|
return newPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkErr(policy, err, message) {
|
||||||
|
assert.strictEqual(policy.error[err], true);
|
||||||
|
assert.strictEqual(policy.error.description, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BucketPolicy class getBucketPolicy', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
testBucketPolicy.Statement[0].Resource = 'arn:aws:s3:::examplebucket';
|
||||||
|
testBucketPolicy.Statement[0].Action = 's3:GetBucketLocation';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request json is empty', done => {
|
||||||
|
const bucketPolicy = new BucketPolicy('').getBucketPolicy();
|
||||||
|
const errMessage = 'request json is empty or undefined';
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', errMessage);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request action is for objects ' +
|
||||||
|
'but resource refers to bucket', done => {
|
||||||
|
const newPolicy = createPolicy('Action', 's3:GetObject');
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', mismatchErr);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request action is for objects ' +
|
||||||
|
'but does\'t include \'Object\' and resource refers to bucket', done => {
|
||||||
|
const newPolicy = createPolicy('Action', 's3:AbortMultipartUpload');
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', mismatchErr);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request action is for objects ' +
|
||||||
|
'(with wildcard) but resource refers to bucket', done => {
|
||||||
|
const newPolicy = createPolicy('Action', 's3:GetObject*');
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', mismatchErr);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request resource refers to ' +
|
||||||
|
'object but action is for buckets', done => {
|
||||||
|
const newPolicy = createPolicy('Resource',
|
||||||
|
'arn:aws:s3:::examplebucket/*');
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', mismatchErr);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return MalformedPolicy error if request resource refers to ' +
|
||||||
|
'object but action is for buckets (with wildcard)', done => {
|
||||||
|
const newPolicy = createPolicy('Resource',
|
||||||
|
'arn:aws:s3:::examplebucket/*');
|
||||||
|
newPolicy.Statement[0].Action = 's3:GetBucket*';
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
checkErr(bucketPolicy, 'MalformedPolicy', mismatchErr);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully get a valid policy', done => {
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(testBucketPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
assert.deepStrictEqual(bucketPolicy, testBucketPolicy);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully get a valid policy with wildcard in action',
|
||||||
|
done => {
|
||||||
|
const newPolicy = createPolicy('Action', 's3:Get*');
|
||||||
|
const bucketPolicy = new BucketPolicy(JSON.stringify(newPolicy))
|
||||||
|
.getBucketPolicy();
|
||||||
|
assert.deepStrictEqual(bucketPolicy, newPolicy);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,258 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const { parseString } = require('xml2js');
|
||||||
|
|
||||||
|
const ObjectLockConfiguration =
|
||||||
|
require('../../../lib/models/ObjectLockConfiguration.js');
|
||||||
|
|
||||||
|
function checkError(parsedXml, err, errMessage, cb) {
|
||||||
|
const config = new ObjectLockConfiguration(parsedXml).
|
||||||
|
getValidatedObjectLockConfiguration();
|
||||||
|
assert.strictEqual(config.error[err], true);
|
||||||
|
assert.strictEqual(config.error.description, errMessage);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRule(testParams) {
|
||||||
|
if (testParams.key === 'Rule') {
|
||||||
|
return `<Rule>${testParams.value}</Rule>`;
|
||||||
|
}
|
||||||
|
if (testParams.key === 'DefaultRetention') {
|
||||||
|
return `<Rule><DefaultRetention>${testParams.value} ` +
|
||||||
|
'</DefaultRetention></Rule>';
|
||||||
|
}
|
||||||
|
const mode = testParams.key === 'Mode' ?
|
||||||
|
`<Mode>${testParams.value}</Mode>` : '<Mode>GOVERNANCE</Mode>';
|
||||||
|
|
||||||
|
let time = '<Days>1</Days>';
|
||||||
|
if (testParams.key === 'Days') {
|
||||||
|
time = `<Days>${testParams.value}</Days>`;
|
||||||
|
}
|
||||||
|
if (testParams.key === 'Years') {
|
||||||
|
time = `<Years>${testParams.value}</Years>`;
|
||||||
|
}
|
||||||
|
return `<Rule><DefaultRetention>${mode}${time}</DefaultRetention></Rule>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateXml(testParams) {
|
||||||
|
const Enabled = testParams.key === 'ObjectLockEnabled' ?
|
||||||
|
`<ObjectLockEnabled>${testParams.value}</ObjectLockEnabled>` :
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>';
|
||||||
|
const Rule = generateRule(testParams);
|
||||||
|
const ObjectLock = testParams.key === 'ObjectLockConfiguration' ? '' :
|
||||||
|
`<ObjectLockConfiguration>${Enabled}${Rule}` +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
return ObjectLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateParsedXml(testParams, cb) {
|
||||||
|
const xml = generateXml(testParams);
|
||||||
|
parseString(xml, (err, parsedXml) => {
|
||||||
|
assert.equal(err, null, 'Error parsing xml');
|
||||||
|
cb(parsedXml);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedXml = (daysOrYears, time, mode) =>
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>' +
|
||||||
|
'<ObjectLockConfiguration ' +
|
||||||
|
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
|
||||||
|
'<Rule><DefaultRetention>' +
|
||||||
|
`<Mode>${mode}</Mode>` +
|
||||||
|
`<${daysOrYears}>${time}</${daysOrYears}>` +
|
||||||
|
'</DefaultRetention></Rule>' +
|
||||||
|
'</ObjectLockConfiguration>';
|
||||||
|
|
||||||
|
const failTests = [
|
||||||
|
{
|
||||||
|
name: 'fail with empty configuration',
|
||||||
|
params: { key: 'ObjectLockConfiguration' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml is undefined or empty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty ObjectLockEnabled',
|
||||||
|
params: { key: 'ObjectLockEnabled', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml does not include valid ObjectLockEnabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with invalid value for ObjectLockEnabled',
|
||||||
|
params: { key: 'ObjectLockEnabled', value: 'Disabled' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml does not include valid ObjectLockEnabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty rule',
|
||||||
|
params: { key: 'Rule', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'Rule request xml does not contain DefaultRetention',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty DefaultRetention',
|
||||||
|
params: { key: 'DefaultRetention', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'DefaultRetention request xml does not contain Mode or ' +
|
||||||
|
'retention period (Days or Years)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty mode',
|
||||||
|
params: { key: 'Mode', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml does not contain Mode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with invalid mode',
|
||||||
|
params: { key: 'Mode', value: 'COMPLOVERNANCE' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'Mode request xml must be one of "GOVERNANCE", ' +
|
||||||
|
'"COMPLIANCE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with lowercase mode',
|
||||||
|
params: { key: 'Mode', value: 'governance' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'Mode request xml must be one of "GOVERNANCE", ' +
|
||||||
|
'"COMPLIANCE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with empty retention period',
|
||||||
|
params: { key: 'Days', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml does not contain Days or Years',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with NaN retention period',
|
||||||
|
params: { key: 'Days', value: 'one' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errorMessage: 'request xml does not contain valid retention period',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with retention period less than 1',
|
||||||
|
params: { key: 'Days', value: 0 },
|
||||||
|
error: 'InvalidArgument',
|
||||||
|
errorMessage: 'retention period must be a positive integer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with Days retention period greater than 36500',
|
||||||
|
params: { key: 'Days', value: 36501 },
|
||||||
|
error: 'InvalidArgument',
|
||||||
|
errorMessage: 'retention period is too large',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fail with Years retention period great than 100',
|
||||||
|
params: { key: 'Years', value: 101 },
|
||||||
|
error: 'InvalidArgument',
|
||||||
|
errorMessage: 'retention period is too large',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const passTests = [
|
||||||
|
{
|
||||||
|
name: 'pass with GOVERNANCE retention mode and valid Days ' +
|
||||||
|
'retention period',
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pass with COMPLIANCE retention mode',
|
||||||
|
params: { key: 'Mode', value: 'COMPLIANCE' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pass with valid Years retention period',
|
||||||
|
params: { key: 'Years', value: 1 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const passTestsGetConfigXML = [
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
rule: {
|
||||||
|
mode: 'COMPLIANCE',
|
||||||
|
days: 90,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedXml: expectedXml('Days', 90, 'COMPLIANCE'),
|
||||||
|
description: 'with COMPLIANCE retention mode ' +
|
||||||
|
'and valid Days retention period',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
rule: {
|
||||||
|
mode: 'GOVERNANCE',
|
||||||
|
days: 30,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedXml: expectedXml('Days', 30, 'GOVERNANCE'),
|
||||||
|
description: 'with GOVERNANCE retention mode ' +
|
||||||
|
'and valid Days retention period',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
rule: {
|
||||||
|
mode: 'COMPLIANCE',
|
||||||
|
years: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedXml: expectedXml('Years', 1, 'COMPLIANCE'),
|
||||||
|
description: 'with COMPLIANCE retention mode ' +
|
||||||
|
'and valid Years retention period',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
rule: {
|
||||||
|
mode: 'GOVERNANCE',
|
||||||
|
years: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedXml: expectedXml('Years', 2, 'GOVERNANCE'),
|
||||||
|
description: 'with GOVERNANCE retention mode ' +
|
||||||
|
'and valid Years retention period',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: {},
|
||||||
|
expectedXml: '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||||
|
'<ObjectLockConfiguration ' +
|
||||||
|
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<ObjectLockEnabled>Enabled</ObjectLockEnabled>' +
|
||||||
|
'</ObjectLockConfiguration>',
|
||||||
|
description: 'without rule if object lock ' +
|
||||||
|
'configuration has not been set',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('ObjectLockConfiguration class getValidatedObjectLockConfiguration',
|
||||||
|
() => {
|
||||||
|
it('should return MalformedXML error if request xml is empty', done => {
|
||||||
|
const errMessage = 'request xml is undefined or empty';
|
||||||
|
checkError('', 'MalformedXML', errMessage, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
failTests.forEach(test => {
|
||||||
|
it(`should ${test.name}`, done => {
|
||||||
|
generateParsedXml(test.params, xml => {
|
||||||
|
checkError(xml, test.error, test.errorMessage, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
passTests.forEach(test => {
|
||||||
|
it(`should ${test.name}`, done => {
|
||||||
|
generateParsedXml(test.params, xml => {
|
||||||
|
const config = new ObjectLockConfiguration(xml).
|
||||||
|
getValidatedObjectLockConfiguration();
|
||||||
|
assert.ifError(config.error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ObjectLockConfiguration class getConfigXML', () => {
|
||||||
|
passTestsGetConfigXML.forEach(test => {
|
||||||
|
const { config, description, expectedXml } = test;
|
||||||
|
it(`should return correct XML ${description}`, () => {
|
||||||
|
const responseXml = ObjectLockConfiguration.getConfigXML(config);
|
||||||
|
assert.strictEqual(responseXml, expectedXml);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,9 @@ const assert = require('assert');
|
||||||
const ObjectMD = require('../../../lib/models/ObjectMD');
|
const ObjectMD = require('../../../lib/models/ObjectMD');
|
||||||
const constants = require('../../../lib/constants');
|
const constants = require('../../../lib/constants');
|
||||||
|
|
||||||
|
const retainDate = new Date();
|
||||||
|
retainDate.setDate(retainDate.getDate() + 1);
|
||||||
|
|
||||||
describe('ObjectMD class setters/getters', () => {
|
describe('ObjectMD class setters/getters', () => {
|
||||||
let md = null;
|
let md = null;
|
||||||
|
|
||||||
|
@ -99,6 +102,12 @@ describe('ObjectMD class setters/getters', () => {
|
||||||
dataStoreVersionId: '',
|
dataStoreVersionId: '',
|
||||||
}],
|
}],
|
||||||
['DataStoreName', null, ''],
|
['DataStoreName', null, ''],
|
||||||
|
['LegalHold', null, false],
|
||||||
|
['LegalHold', true],
|
||||||
|
['RetentionInfo', {
|
||||||
|
mode: 'GOVERNANCE',
|
||||||
|
retainUntilDate: retainDate.toISOString(),
|
||||||
|
}],
|
||||||
].forEach(test => {
|
].forEach(test => {
|
||||||
const property = test[0];
|
const property = test[0];
|
||||||
const testValue = test[1];
|
const testValue = test[1];
|
||||||
|
@ -192,6 +201,17 @@ describe('ObjectMD class setters/getters', () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
md.getReplicationSiteDataStoreVersionId('zenko'), 'a');
|
md.getReplicationSiteDataStoreVersionId('zenko'), 'a');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ObjectMD::set/getRetentionInfo', () => {
|
||||||
|
md.setRetentionInfo({
|
||||||
|
mode: 'COMPLIANCE',
|
||||||
|
retainUntilDate: retainDate.toISOString(),
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(md.getRetentionInfo(), {
|
||||||
|
mode: 'COMPLIANCE',
|
||||||
|
retainUntilDate: retainDate.toISOString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ObjectMD import from stored blob', () => {
|
describe('ObjectMD import from stored blob', () => {
|
||||||
|
|
|
@ -4,8 +4,9 @@ const assert = require('assert');
|
||||||
const policyValidator = require('../../../lib/policy/policyValidator');
|
const policyValidator = require('../../../lib/policy/policyValidator');
|
||||||
const errors = require('../../../lib/errors');
|
const errors = require('../../../lib/errors');
|
||||||
const validateUserPolicy = policyValidator.validateUserPolicy;
|
const validateUserPolicy = policyValidator.validateUserPolicy;
|
||||||
|
const validateResourcePolicy = policyValidator.validateResourcePolicy;
|
||||||
const successRes = { error: null, valid: true };
|
const successRes = { error: null, valid: true };
|
||||||
const samplePolicy = {
|
const sampleUserPolicy = {
|
||||||
Version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
Statement: {
|
Statement: {
|
||||||
Sid: 'FooBar1234',
|
Sid: 'FooBar1234',
|
||||||
|
@ -15,6 +16,19 @@ const samplePolicy = {
|
||||||
Condition: { NumericLessThanEquals: { 's3:max-keys': '10' } },
|
Condition: { NumericLessThanEquals: { 's3:max-keys': '10' } },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const sampleResourcePolicy = {
|
||||||
|
Version: '2012-10-17',
|
||||||
|
Statement: [
|
||||||
|
{
|
||||||
|
Sid: 'ResourcePolicy1',
|
||||||
|
Effect: 'Allow',
|
||||||
|
Action: 's3:ListBucket',
|
||||||
|
Resource: 'arn:aws:s3:::example-bucket',
|
||||||
|
Condition: { StringLike: { 's3:prefix': 'foo' } },
|
||||||
|
Principal: '*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const errDict = {
|
const errDict = {
|
||||||
required: {
|
required: {
|
||||||
|
@ -30,45 +44,84 @@ const errDict = {
|
||||||
Resource: 'Policy statement must contain resources.',
|
Resource: 'Policy statement must contain resources.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let policy;
|
|
||||||
|
|
||||||
function failRes(errDescription) {
|
function failRes(policyType, errDescription) {
|
||||||
const error = Object.assign({}, errors.MalformedPolicyDocument);
|
let error;
|
||||||
|
if (policyType === 'user') {
|
||||||
|
error = Object.assign({}, errors.MalformedPolicyDocument);
|
||||||
|
}
|
||||||
|
if (policyType === 'resource') {
|
||||||
|
error = Object.assign({}, errors.MalformedPolicy);
|
||||||
|
}
|
||||||
error.description = errDescription || error.description;
|
error.description = errDescription || error.description;
|
||||||
return { error, valid: false };
|
return { error, valid: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
function check(input, expected) {
|
function check(input, expected, policyType) {
|
||||||
const result = validateUserPolicy(JSON.stringify(input));
|
let result;
|
||||||
|
if (policyType === 'user') {
|
||||||
|
result = validateUserPolicy(JSON.stringify(input));
|
||||||
|
}
|
||||||
|
if (policyType === 'resource') {
|
||||||
|
result = validateResourcePolicy(JSON.stringify(input));
|
||||||
|
}
|
||||||
assert.deepStrictEqual(result, expected);
|
assert.deepStrictEqual(result, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let userPolicy;
|
||||||
|
let resourcePolicy;
|
||||||
|
const user = 'user';
|
||||||
|
const resource = 'resource';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
policy = JSON.parse(JSON.stringify(samplePolicy));
|
userPolicy = JSON.parse(JSON.stringify(sampleUserPolicy));
|
||||||
|
resourcePolicy = JSON.parse(JSON.stringify(sampleResourcePolicy));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Invalid JSON', () => {
|
describe('Policies validation - Invalid JSON', () => {
|
||||||
it('should return error for invalid JSON', () => {
|
it('should return error for invalid user policy JSON', () => {
|
||||||
const result = validateUserPolicy('{"Version":"2012-10-17",' +
|
const result = validateUserPolicy('{"Version":"2012-10-17",' +
|
||||||
'"Statement":{"Effect":"Allow""Action":"s3:PutObject",' +
|
'"Statement":{"Effect":"Allow""Action":"s3:PutObject",' +
|
||||||
'"Resource":"arn:aws:s3*"}}');
|
'"Resource":"arn:aws:s3*"}}');
|
||||||
assert.deepStrictEqual(result, failRes());
|
assert.deepStrictEqual(result, failRes(user));
|
||||||
|
});
|
||||||
|
it('should return error for invaild resource policy JSON', () => {
|
||||||
|
const result = validateResourcePolicy('{"Version":"2012-10-17",' +
|
||||||
|
'"Statement":{"Effect":"Allow""Action":"s3:PutObject",' +
|
||||||
|
'"Resource":"arn:aws:s3*"}}');
|
||||||
|
assert.deepStrictEqual(result, failRes(resource));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Version', () => {
|
describe('Policies validation - Version', () => {
|
||||||
it('should validate with version date 2012-10-17', () => {
|
it('should validate user policy with version date 2012-10-17', () => {
|
||||||
check(policy, successRes);
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error for other dates', () => {
|
it('should validate resource policy with version date 2012-10-17', () => {
|
||||||
policy.Version = '2012-11-17';
|
check(resourcePolicy, successRes, 'resource');
|
||||||
check(policy, failRes());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error if Version field is missing', () => {
|
it('user policy should return error for other dates', () => {
|
||||||
policy.Version = undefined;
|
userPolicy.Version = '2012-11-17';
|
||||||
check(policy, failRes(errDict.required.Version));
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should return error for other dates', () => {
|
||||||
|
resourcePolicy.Version = '2012-11-17';
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error if Version field in user policy is missing', () => {
|
||||||
|
userPolicy.Version = undefined;
|
||||||
|
check(userPolicy, failRes(user, errDict.required.Version), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error if Version field in resource policy is missing',
|
||||||
|
() => {
|
||||||
|
resourcePolicy.Version = undefined;
|
||||||
|
check(resourcePolicy, failRes(resource, errDict.required.Version),
|
||||||
|
resource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,20 +130,24 @@ describe('Policies validation - Principal', () => {
|
||||||
{
|
{
|
||||||
name: 'an account id',
|
name: 'an account id',
|
||||||
value: { AWS: '111111111111' },
|
value: { AWS: '111111111111' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'anonymous user AWS form',
|
name: 'anonymous user AWS form',
|
||||||
value: { AWS: '*' },
|
value: { AWS: '*' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'an account arn',
|
name: 'an account arn',
|
||||||
value: { AWS: 'arn:aws:iam::111111111111:root' },
|
value: { AWS: 'arn:aws:iam::111111111111:root' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple account id',
|
name: 'multiple account id',
|
||||||
value: {
|
value: {
|
||||||
AWS: ['111111111111', '111111111112'],
|
AWS: ['111111111111', '111111111112'],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple account arn',
|
name: 'multiple account arn',
|
||||||
|
@ -100,14 +157,17 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::111111111112:root',
|
'arn:aws:iam::111111111112:root',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'anonymous user as string',
|
name: 'anonymous user as string',
|
||||||
value: '*',
|
value: '*',
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user arn',
|
name: 'user arn',
|
||||||
value: { AWS: 'arn:aws:iam::111111111111:user/alex' },
|
value: { AWS: 'arn:aws:iam::111111111111:user/alex' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple user arns',
|
name: 'multiple user arns',
|
||||||
|
@ -117,12 +177,14 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::111111111111:user/thibault',
|
'arn:aws:iam::111111111111:user/thibault',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'role arn',
|
name: 'role arn',
|
||||||
value: {
|
value: {
|
||||||
AWS: 'arn:aws:iam::111111111111:role/dev',
|
AWS: 'arn:aws:iam::111111111111:role/dev',
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple role arn',
|
name: 'multiple role arn',
|
||||||
|
@ -132,6 +194,7 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::111111111111:role/prod',
|
'arn:aws:iam::111111111111:role/prod',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'saml provider',
|
name: 'saml provider',
|
||||||
|
@ -139,57 +202,84 @@ describe('Policies validation - Principal', () => {
|
||||||
Federated:
|
Federated:
|
||||||
'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
||||||
},
|
},
|
||||||
|
policyType: [user],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'with backbeat service',
|
name: 'with backbeat service',
|
||||||
value: { Service: 'backbeat' },
|
value: { Service: 'backbeat' },
|
||||||
|
policyType: [user, resource],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'with canonical user id',
|
||||||
|
value: { CanonicalUser:
|
||||||
|
'1examplecanonicalid12345678909876' +
|
||||||
|
'54321qwerty12345asdfg67890z1x2c' },
|
||||||
|
policyType: [resource],
|
||||||
},
|
},
|
||||||
].forEach(test => {
|
].forEach(test => {
|
||||||
it(`should allow principal field with ${test.name}`, () => {
|
if (test.policyType.includes(user)) {
|
||||||
policy.Statement.Principal = test.value;
|
it(`should allow user policy principal field with ${test.name}`,
|
||||||
delete policy.Statement.Resource;
|
() => {
|
||||||
check(policy, successRes);
|
userPolicy.Statement.Principal = test.value;
|
||||||
});
|
delete userPolicy.Statement.Resource;
|
||||||
|
check(userPolicy, successRes, user);
|
||||||
|
});
|
||||||
|
|
||||||
it(`shoud allow notPrincipal field with ${test.name}`, () => {
|
it(`should allow user policy notPrincipal field with ${test.name}`,
|
||||||
policy.Statement.NotPrincipal = test.value;
|
() => {
|
||||||
delete policy.Statement.Resource;
|
userPolicy.Statement.NotPrincipal = test.value;
|
||||||
check(policy, successRes);
|
delete userPolicy.Statement.Resource;
|
||||||
});
|
check(userPolicy, successRes, user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (test.policyType.includes(resource)) {
|
||||||
|
it(`should allow resource policy principal field with ${test.name}`,
|
||||||
|
() => {
|
||||||
|
resourcePolicy.Statement[0].Principal = test.value;
|
||||||
|
check(resourcePolicy, successRes, resource);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'wrong format account id',
|
name: 'wrong format account id',
|
||||||
value: { AWS: '11111111111z' },
|
value: { AWS: '11111111111z' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'empty string',
|
name: 'empty string',
|
||||||
value: '',
|
value: '',
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'anonymous user federated form',
|
name: 'anonymous user federated form',
|
||||||
value: { federated: '*' },
|
value: { federated: '*' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'wildcard in ressource',
|
name: 'wildcard in resource',
|
||||||
value: { AWS: 'arn:aws:iam::111111111111:user/*' },
|
value: { AWS: 'arn:aws:iam::111111111111:user/*' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'a malformed account arn',
|
name: 'a malformed account arn',
|
||||||
value: { AWS: 'arn:aws:iam::111111111111:' },
|
value: { AWS: 'arn:aws:iam::111111111111:' },
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple malformed account id',
|
name: 'multiple malformed account id',
|
||||||
value: {
|
value: {
|
||||||
AWS: ['1111111111z1', '1111z1111112'],
|
AWS: ['1111111111z1', '1111z1111112'],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple anonymous',
|
name: 'multiple anonymous',
|
||||||
value: {
|
value: {
|
||||||
AWS: ['*', '*'],
|
AWS: ['*', '*'],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple malformed account arn',
|
name: 'multiple malformed account arn',
|
||||||
|
@ -199,18 +289,22 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::111111111112:',
|
'arn:aws:iam::111111111112:',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'account id as a string',
|
name: 'account id as a string',
|
||||||
value: '111111111111',
|
value: '111111111111',
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'account arn as a string',
|
name: 'account arn as a string',
|
||||||
value: 'arn:aws:iam::111111111111:root',
|
value: 'arn:aws:iam::111111111111:root',
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user arn as a string',
|
name: 'user arn as a string',
|
||||||
value: 'arn:aws:iam::111111111111:user/alex',
|
value: 'arn:aws:iam::111111111111:user/alex',
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple malformed user arns',
|
name: 'multiple malformed user arns',
|
||||||
|
@ -220,12 +314,14 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::111111111111:user/',
|
'arn:aws:iam::111111111111:user/',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'malformed role arn',
|
name: 'malformed role arn',
|
||||||
value: {
|
value: {
|
||||||
AWS: 'arn:aws:iam::111111111111:role/',
|
AWS: 'arn:aws:iam::111111111111:role/',
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple malformed role arn',
|
name: 'multiple malformed role arn',
|
||||||
|
@ -235,36 +331,84 @@ describe('Policies validation - Principal', () => {
|
||||||
'arn:aws:iam::11111111z111:role/prod',
|
'arn:aws:iam::11111111z111:role/prod',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
policyType: [user, resource],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'saml provider as a string',
|
name: 'saml provider as a string',
|
||||||
value: 'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
value: 'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
||||||
|
policyType: [user],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'with other service than backbeat',
|
name: 'with other service than backbeat',
|
||||||
value: { Service: 'non-existent-service' },
|
value: { Service: 'non-existent-service' },
|
||||||
|
policyType: [user, resource],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invalid canonical user',
|
||||||
|
value: { CanonicalUser:
|
||||||
|
'12345invalid-canonical-id$$$//098' +
|
||||||
|
'7654321poiu1q2w3e4r5t6y7u8i9o0p' },
|
||||||
|
policyType: [resource],
|
||||||
},
|
},
|
||||||
].forEach(test => {
|
].forEach(test => {
|
||||||
it(`should fail with ${test.name}`, () => {
|
if (test.policyType.includes(user)) {
|
||||||
policy.Statement.Principal = test.value;
|
it(`user policy should fail with ${test.name}`, () => {
|
||||||
delete policy.Statement.Resource;
|
userPolicy.Statement.Principal = test.value;
|
||||||
check(policy, failRes());
|
delete userPolicy.Statement.Resource;
|
||||||
});
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (test.policyType.includes(resource)) {
|
||||||
|
it(`resource policy should fail with ${test.name}`, () => {
|
||||||
|
resourcePolicy.Statement[0].Principal = test.value;
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow Resource field', () => {
|
it('should not allow Resource field', () => {
|
||||||
policy.Statement.Principal = '*';
|
userPolicy.Statement.Principal = '*';
|
||||||
check(policy, failRes());
|
check(userPolicy, failRes(user), user);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement', () => {
|
describe('Policies validation - Statement', () => {
|
||||||
it('should succeed for a valid object', () => {
|
[
|
||||||
check(policy, successRes);
|
{
|
||||||
|
name: 'should return error for undefined',
|
||||||
|
value: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return an error for an empty list',
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return an error for an empty object',
|
||||||
|
value: {},
|
||||||
|
errMessage: errDict.required.Action,
|
||||||
|
},
|
||||||
|
].forEach(test => {
|
||||||
|
it(`user policy ${test.name}`, () => {
|
||||||
|
userPolicy.Statement = test.value;
|
||||||
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`resource policy ${test.name}`, () => {
|
||||||
|
resourcePolicy.Statement = test.value;
|
||||||
|
check(resourcePolicy, failRes(resource, test.errMessage), resource);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed for a valid array', () => {
|
it('user policy should succeed for a valid object', () => {
|
||||||
policy.Statement = [
|
check(userPolicy, successRes, user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should succeed for a valid object', () => {
|
||||||
|
check(resourcePolicy, successRes, resource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user policy should succeed for a valid object', () => {
|
||||||
|
userPolicy.Statement = [
|
||||||
{
|
{
|
||||||
Effect: 'Allow',
|
Effect: 'Allow',
|
||||||
Action: 's3:PutObject',
|
Action: 's3:PutObject',
|
||||||
|
@ -276,255 +420,373 @@ describe('Policies validation - Statement', () => {
|
||||||
Resource: 'arn:aws:s3:::my_bucket/uploads/widgetco/*',
|
Resource: 'arn:aws:s3:::my_bucket/uploads/widgetco/*',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
check(policy, successRes);
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error for undefined', () => {
|
it('resource policy should succeed for a valid object', () => {
|
||||||
policy.Statement = undefined;
|
resourcePolicy.Statement = [
|
||||||
check(policy, failRes());
|
{
|
||||||
|
Effect: 'Allow',
|
||||||
|
Action: 's3:PutObject',
|
||||||
|
Resource: 'arn:aws:s3:::my_bucket/uploads/widgetco/*',
|
||||||
|
Principal: '*',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Effect: 'Deny',
|
||||||
|
Action: 's3:DeleteObject',
|
||||||
|
Resource: 'arn:aws:s3:::my_bucket/uploads/widgetco/*',
|
||||||
|
Principal: '*',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
check(resourcePolicy, successRes, resource);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error for an empty list', () => {
|
[
|
||||||
policy.Statement = [];
|
{
|
||||||
check(policy, failRes());
|
name: 'should return error for missing a required field - Action',
|
||||||
});
|
toDelete: ['Action'],
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.required.Action,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return error for missing a required field - Effect',
|
||||||
|
toDelete: ['Effect'],
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return error for missing required field - Resource',
|
||||||
|
toDelete: ['Resource'],
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should return error for missing multiple required fields',
|
||||||
|
toDelete: ['Effect', 'Resource'],
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed w optional fields missing - Sid, Condition',
|
||||||
|
toDelete: ['Sid', 'Condition'],
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
].forEach(test => {
|
||||||
|
it(`user policy ${test.name}`, () => {
|
||||||
|
test.toDelete.forEach(p => delete userPolicy.Statement[p]);
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
} else {
|
||||||
|
check(userPolicy, test.expected, user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should return an error for an empty object', () => {
|
it(`resource policy ${test.name}`, () => {
|
||||||
policy.Statement = {};
|
test.toDelete.forEach(p => delete resourcePolicy.Statement[0][p]);
|
||||||
check(policy, failRes(errDict.required.Action));
|
if (test.expected === 'fail') {
|
||||||
});
|
check(resourcePolicy, failRes(resource, test.errMessage),
|
||||||
|
resource);
|
||||||
it('should return an error for missing a required field - Action', () => {
|
} else {
|
||||||
delete policy.Statement.Action;
|
check(resourcePolicy, test.expected, resource);
|
||||||
check(policy, failRes(errDict.required.Action));
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error for missing a required field - Effect', () => {
|
|
||||||
delete policy.Statement.Effect;
|
|
||||||
check(policy, failRes());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error for missing a required field - Resource', () => {
|
|
||||||
delete policy.Statement.Resource;
|
|
||||||
check(policy, failRes());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error for missing multiple required fields', () => {
|
|
||||||
delete policy.Statement.Effect;
|
|
||||||
delete policy.Statement.Resource;
|
|
||||||
check(policy, failRes());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed with optional fields missing - Sid, Condition', () => {
|
|
||||||
delete policy.Statement.Sid;
|
|
||||||
delete policy.Statement.Condition;
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement::Sid_block', () => {
|
describe('Policies validation - Statement::Sid_block', () => {
|
||||||
it('should succeed if Sid is any alphanumeric string', () => {
|
it('user policy should succeed if Sid is any alphanumeric string', () => {
|
||||||
check(policy, successRes);
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if Sid is not a valid format', () => {
|
it('resource policy should succeed if Sid is any alphanumeric string',
|
||||||
policy.Statement.Sid = 'foo bar()';
|
() => {
|
||||||
check(policy, failRes());
|
check(resourcePolicy, successRes, resource);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if Sid is not a string', () => {
|
it('user policy should fail if Sid is not a valid format', () => {
|
||||||
policy.Statement.Sid = 1234;
|
userPolicy.Statement.Sid = 'foo bar()';
|
||||||
check(policy, failRes());
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should fail if Sid is not a valid format', () => {
|
||||||
|
resourcePolicy.Statement[0].Sid = 'foo bar()';
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user policy should fail if Sid is not a string', () => {
|
||||||
|
userPolicy.Statement.Sid = 1234;
|
||||||
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should fail if Sid is not a string', () => {
|
||||||
|
resourcePolicy.Statement[0].Sid = 1234;
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement::Effect_block', () => {
|
describe('Policies validation - Statement::Effect_block', () => {
|
||||||
it('should succeed for Allow', () => {
|
it('user policy should succeed for Allow', () => {
|
||||||
check(policy, successRes);
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed for Deny', () => {
|
it('resource policy should succeed for Allow', () => {
|
||||||
policy.Statement.Effect = 'Deny';
|
check(resourcePolicy, successRes, resource);
|
||||||
check(policy, successRes);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for strings other than Allow/Deny', () => {
|
it('user policy should succeed for Deny', () => {
|
||||||
policy.Statement.Effect = 'Reject';
|
userPolicy.Statement.Effect = 'Deny';
|
||||||
check(policy, failRes());
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if Effect is not a string', () => {
|
it('resource policy should succeed for Deny', () => {
|
||||||
policy.Statement.Effect = 1;
|
resourcePolicy.Statement[0].Effect = 'Deny';
|
||||||
check(policy, failRes());
|
check(resourcePolicy, successRes, resource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user policy should fail for strings other than Allow/Deny', () => {
|
||||||
|
userPolicy.Statement.Effect = 'Reject';
|
||||||
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should fail for strings other than Allow/Deny', () => {
|
||||||
|
resourcePolicy.Statement[0].Effect = 'Reject';
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user policy should fail if Effect is not a string', () => {
|
||||||
|
userPolicy.Statement.Effect = 1;
|
||||||
|
check(userPolicy, failRes(user), user);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resource policy should fail if Effect is not a string', () => {
|
||||||
|
resourcePolicy.Statement[0].Effect = 1;
|
||||||
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement::Action_block/' +
|
const actionTests = [
|
||||||
|
{
|
||||||
|
name: 'should succeed for foo:bar',
|
||||||
|
value: 'foo:bar',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed for foo:*',
|
||||||
|
value: 'foo:*',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed for *',
|
||||||
|
value: '*',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for **',
|
||||||
|
value: '**',
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.pattern.Action,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for foobar',
|
||||||
|
value: 'foobar',
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.pattern.Action,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('User policies validation - Statement::Action_block/' +
|
||||||
'Statement::NotAction_block', () => {
|
'Statement::NotAction_block', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
policy.Statement.Action = undefined;
|
userPolicy.Statement.Action = undefined;
|
||||||
policy.Statement.NotAction = undefined;
|
userPolicy.Statement.NotAction = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed for foo:bar', () => {
|
actionTests.forEach(test => {
|
||||||
policy.Statement.Action = 'foo:bar';
|
it(`${test.name}`, () => {
|
||||||
check(policy, successRes);
|
userPolicy.Statement.Action = test.value;
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
} else {
|
||||||
|
check(userPolicy, test.expected, user);
|
||||||
|
}
|
||||||
|
|
||||||
policy.Statement.Action = undefined;
|
userPolicy.Statement.Action = undefined;
|
||||||
policy.Statement.NotAction = 'foo:bar';
|
userPolicy.Statement.NotAction = test.value;
|
||||||
check(policy, successRes);
|
if (test.expected === 'fail') {
|
||||||
});
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
} else {
|
||||||
it('should succeed for foo:*', () => {
|
check(userPolicy, test.expected, user);
|
||||||
policy.Statement.Action = 'foo:*';
|
}
|
||||||
check(policy, successRes);
|
});
|
||||||
|
|
||||||
policy.Statement.Action = undefined;
|
|
||||||
policy.Statement.NotAction = 'foo:*';
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for *', () => {
|
|
||||||
policy.Statement.Action = '*';
|
|
||||||
check(policy, successRes);
|
|
||||||
|
|
||||||
policy.Statement.Action = undefined;
|
|
||||||
policy.Statement.NotAction = '*';
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail for **', () => {
|
|
||||||
policy.Statement.Action = '**';
|
|
||||||
check(policy, failRes(errDict.pattern.Action));
|
|
||||||
|
|
||||||
policy.Statement.Action = undefined;
|
|
||||||
policy.Statement.NotAction = '**';
|
|
||||||
check(policy, failRes(errDict.pattern.Action));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail for foobar', () => {
|
|
||||||
policy.Statement.Action = 'foobar';
|
|
||||||
check(policy, failRes(errDict.pattern.Action));
|
|
||||||
|
|
||||||
policy.Statement.Action = undefined;
|
|
||||||
policy.Statement.NotAction = 'foobar';
|
|
||||||
check(policy, failRes(errDict.pattern.Action));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement::Resource_block' +
|
describe('Resource policies validation - Statement::Action_block', () => {
|
||||||
|
actionTests.forEach(test => {
|
||||||
|
it(`${test.name}`, () => {
|
||||||
|
resourcePolicy.Statement[0].Action = test.value;
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(resourcePolicy, failRes(resource, test.errMessage),
|
||||||
|
resource);
|
||||||
|
} else {
|
||||||
|
check(resourcePolicy, test.expected, resource);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const resourceTests = [
|
||||||
|
{
|
||||||
|
name: 'should succeed for arn:aws::s3:::*',
|
||||||
|
value: 'arn:aws:s3:::*',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed for arn:aws:s3:::test/home/${aws:username}',
|
||||||
|
value: 'arn:aws:s3:::test/home/${aws:username}',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed for arn:aws:ec2:us-west-1:1234567890:vol/*',
|
||||||
|
value: 'arn:aws:ec2:us-west-1:1234567890:vol/*',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should succeed for *',
|
||||||
|
value: '*',
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for arn:aws:ec2:us-west-1:vol/* - missing region',
|
||||||
|
value: 'arn:aws:ec2:us-west-1:vol/*',
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.pattern.Resource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for arn:aws:ec2:us-west-1:123456789:v/${} - ${}',
|
||||||
|
value: 'arn:aws:ec2:us-west-1:123456789:v/${}',
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.pattern.Resource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for ec2:us-west-1:qwerty:vol/* - missing arn:aws:',
|
||||||
|
value: 'ec2:us-west-1:123456789012:vol/*',
|
||||||
|
expected: 'fail',
|
||||||
|
errMessage: errDict.pattern.Resource,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('User policies validation - Statement::Resource_block' +
|
||||||
'Statement::NotResource_block', () => {
|
'Statement::NotResource_block', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
policy.Statement.Resource = undefined;
|
userPolicy.Statement.Resource = undefined;
|
||||||
policy.Statement.NotResource = undefined;
|
userPolicy.Statement.NotResource = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed for arn:aws:s3:::*', () => {
|
resourceTests.forEach(test => {
|
||||||
policy.Statement.Resource = 'arn:aws:s3:::*';
|
it(`${test.name}`, () => {
|
||||||
check(policy, successRes);
|
userPolicy.Statement.Resource = test.value;
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
} else {
|
||||||
|
check(userPolicy, test.expected, user);
|
||||||
|
}
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
userPolicy.Statement.Resource = undefined;
|
||||||
policy.Statement.NotResource = 'arn:aws:s3:::*';
|
userPolicy.Statement.NotResource = test.value;
|
||||||
check(policy, successRes);
|
if (test.expected === 'fail') {
|
||||||
});
|
check(userPolicy, failRes(user, test.errMessage), user);
|
||||||
|
} else {
|
||||||
it('should succeed for arn:aws:s3:::test/home/${aws:username}', () => {
|
check(userPolicy, test.expected, user);
|
||||||
policy.Statement.Resource = 'arn:aws:s3:::test/home/${aws:username}';
|
}
|
||||||
check(policy, successRes);
|
});
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = 'arn:aws:s3:::test/home/${aws:username}';
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for arn:aws:ec2:us-west-1:1234567890:vol/*', () => {
|
|
||||||
policy.Statement.Resource = 'arn:aws:ec2:us-west-1:1234567890:vol/*';
|
|
||||||
check(policy, successRes);
|
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = 'arn:aws:ec2:us-west-1:1234567890:vol/*';
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for *', () => {
|
|
||||||
policy.Statement.Resource = '*';
|
|
||||||
check(policy, successRes);
|
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = '*';
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail for arn:aws:ec2:us-west-1:vol/* - missing region', () => {
|
|
||||||
policy.Statement.Resource = 'arn:aws:ec2:1234567890:vol/*';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = 'arn:aws:ec2:1234567890:vol/*';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail for arn:aws:ec2:us-west-1:123456789:v/${} - ${}', () => {
|
|
||||||
policy.Statement.Resource = 'arn:aws:ec2:us-west-1:123456789:v/${}';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = 'arn:aws:ec2:us-west-1:123456789:v/${}';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail for ec2:us-west-1:qwerty:vol/* - missing arn:aws:', () => {
|
|
||||||
policy.Statement.Resource = 'ec2:us-west-1:123456789012:vol/*';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
|
|
||||||
policy.Statement.Resource = undefined;
|
|
||||||
policy.Statement.NotResource = 'ec2:us-west-1:123456789012:vol/*';
|
|
||||||
check(policy, failRes(errDict.pattern.Resource));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for empty list of resources', () => {
|
it('should fail for empty list of resources', () => {
|
||||||
policy.Statement.Resource = [];
|
userPolicy.Statement.Resource = [];
|
||||||
check(policy, failRes(errDict.minItems.Resource));
|
check(userPolicy, failRes(user, errDict.minItems.Resource), user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Resource policies validation - Statement::Resource_block', () => {
|
||||||
|
resourceTests.forEach(test => {
|
||||||
|
it(`${test.name}`, () => {
|
||||||
|
resourcePolicy.Statement[0].Resource = test.value;
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(resourcePolicy, failRes(resource, test.errMessage),
|
||||||
|
resource);
|
||||||
|
} else {
|
||||||
|
check(resourcePolicy, test.expected, resource);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail for empty list of resources', () => {
|
||||||
|
resourcePolicy.Statement[0].Resource = [];
|
||||||
|
check(resourcePolicy, failRes(resource, errDict.minItems.Resource),
|
||||||
|
resource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Policies validation - Statement::Condition_block', () => {
|
describe('Policies validation - Statement::Condition_block', () => {
|
||||||
it('should succeed for single Condition', () => {
|
it('user policy should succeed for single Condition', () => {
|
||||||
check(policy, successRes);
|
check(userPolicy, successRes, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed for multiple Conditions', () => {
|
it('resource policy should succeed for single Condition', () => {
|
||||||
policy.Statement.Condition = {
|
check(resourcePolicy, successRes, resource);
|
||||||
StringNotLike: { 's3:prefix': ['Development/*'] },
|
|
||||||
Null: { 's3:prefix': false },
|
|
||||||
};
|
|
||||||
check(policy, successRes);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail when Condition is not an Object', () => {
|
[
|
||||||
policy.Statement.Condition = 'NumericLessThanEquals';
|
{
|
||||||
check(policy, failRes());
|
name: 'should succeed for multiple Conditions',
|
||||||
});
|
value: {
|
||||||
|
StringNotLike: { 's3:prefix': ['Development/*'] },
|
||||||
|
Null: { 's3:prefix': false },
|
||||||
|
},
|
||||||
|
expected: successRes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail when Condition is not an Object',
|
||||||
|
value: 'NumericLessThanEquals',
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail for an invalid Condition',
|
||||||
|
value: {
|
||||||
|
SomethingLike: { 's3:prefix': ['Development/*'] },
|
||||||
|
},
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail when one of the multiple conditions is invalid',
|
||||||
|
value: {
|
||||||
|
Null: { 's3:prefix': false },
|
||||||
|
SomethingLike: { 's3:prefix': ['Development/*'] },
|
||||||
|
},
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail when invalid property is assigned',
|
||||||
|
value: {
|
||||||
|
SomethingLike: { 's3:prefix': ['Development/*'] },
|
||||||
|
},
|
||||||
|
expected: 'fail',
|
||||||
|
},
|
||||||
|
].forEach(test => {
|
||||||
|
it(`user policy ${test.name}`, () => {
|
||||||
|
userPolicy.Statement.Condition = test.value;
|
||||||
|
if (test.expected === 'fail') {
|
||||||
|
check(userPolicy, failRes(user), user);
|
||||||
|
} else {
|
||||||
|
check(userPolicy, test.expected, user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail for an invalid Condition', () => {
|
it(`resource policy ${test.name}`, () => {
|
||||||
policy.Statement.Condition = {
|
resourcePolicy.Statement[0].Condition = test.value;
|
||||||
SomethingLike: { 's3:prefix': ['Development/*'] },
|
if (test.expected === 'fail') {
|
||||||
};
|
check(resourcePolicy, failRes(resource), resource);
|
||||||
check(policy, failRes());
|
} else {
|
||||||
});
|
check(resourcePolicy, test.expected, resource);
|
||||||
|
}
|
||||||
it('should fail when one of the multiple conditions is invalid', () => {
|
});
|
||||||
policy.Statement.Condition = {
|
|
||||||
Null: { 's3:prefix': false },
|
|
||||||
SomethingLike: { 's3:prefix': ['Development/*'] },
|
|
||||||
};
|
|
||||||
check(policy, failRes());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail when invalid property is assigned', () => {
|
|
||||||
policy.Condition = {
|
|
||||||
SomethingLike: { 's3:prefix': ['Development/*'] },
|
|
||||||
};
|
|
||||||
check(policy, failRes());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const { convertToXml, parseLegalHoldXml } =
|
||||||
|
require('../../../lib/s3middleware/objectLegalHold');
|
||||||
|
const DummyRequestLogger = require('../helpers').DummyRequestLogger;
|
||||||
|
|
||||||
|
const log = new DummyRequestLogger();
|
||||||
|
|
||||||
|
const failTests = [
|
||||||
|
{
|
||||||
|
description: 'should fail with empty status',
|
||||||
|
params: { status: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'request xml does not contain Status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should fail with invalid status "on"',
|
||||||
|
params: { status: 'on' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'Status request xml must be one of "ON", "OFF"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should fail with invalid status "On"',
|
||||||
|
params: { status: 'On' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'Status request xml must be one of "ON", "OFF"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should fail with invalid status "off"',
|
||||||
|
params: { status: 'off' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'Status request xml must be one of "ON", "OFF"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'should fail with invalid status "Off"',
|
||||||
|
params: { status: 'Off' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'Status request xml must be one of "ON", "OFF"',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const generateXml = status =>
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||||
|
`<LegalHold><Status>${status}</Status></LegalHold>`;
|
||||||
|
|
||||||
|
describe('object legal hold helpers: parseLegalHoldXml', () => {
|
||||||
|
failTests.forEach(test => {
|
||||||
|
it(test.description, done => {
|
||||||
|
const status = test.params.status;
|
||||||
|
parseLegalHoldXml(generateXml(status), log, err => {
|
||||||
|
assert(err[test.error]);
|
||||||
|
assert.strictEqual(err.description, test.errMessage);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass with legal hold status "ON"', done => {
|
||||||
|
parseLegalHoldXml(generateXml('ON'), log, (err, result) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.strictEqual(result, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass with legal hold status "OFF"', done => {
|
||||||
|
parseLegalHoldXml(generateXml('OFF'), log, (err, result) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.strictEqual(result, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('object legal hold helpers: convertToXml', () => {
|
||||||
|
it('should return correct xml when legal hold status "ON"', () => {
|
||||||
|
const xml = convertToXml(true);
|
||||||
|
const expextedXml = generateXml('ON');
|
||||||
|
assert.strictEqual(xml, expextedXml);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct xml when legal hold status "OFF"', () => {
|
||||||
|
const xml = convertToXml(false);
|
||||||
|
const expextedXml = generateXml('OFF');
|
||||||
|
assert.strictEqual(xml, expextedXml);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty string when legal hold not set', () => {
|
||||||
|
const xml = convertToXml(undefined);
|
||||||
|
const expextedXml = '';
|
||||||
|
assert.strictEqual(xml, expextedXml);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,130 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const {
|
||||||
|
parseRetentionXml,
|
||||||
|
convertToXml,
|
||||||
|
} = require('../../../lib/s3middleware/objectRetention');
|
||||||
|
const DummyRequestLogger = require('../helpers').DummyRequestLogger;
|
||||||
|
|
||||||
|
const log = new DummyRequestLogger();
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
|
const failDate = new Date('05/01/2020');
|
||||||
|
const passDate = new Date();
|
||||||
|
passDate.setDate(passDate.getDate() + 2);
|
||||||
|
|
||||||
|
|
||||||
|
function buildXml(key, value) {
|
||||||
|
const mode = key === 'Mode' ?
|
||||||
|
`<Mode>${value}</Mode>` :
|
||||||
|
'<Mode>GOVERNANCE</Mode>';
|
||||||
|
const retainDate = key === 'RetainDate' ?
|
||||||
|
`<RetainUntilDate>${value}</RetainUntilDate>` :
|
||||||
|
`<RetainUntilDate>${date}</RetainUntilDate>`;
|
||||||
|
const retention = key === 'Retention' ?
|
||||||
|
`<Retention>${value}</Retention>` :
|
||||||
|
`<Retention>${mode}${retainDate}</Retention>`;
|
||||||
|
return retention;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedRetention = {
|
||||||
|
retention: {
|
||||||
|
mode: 'GOVERNANCE',
|
||||||
|
retainUntilDate: passDate.toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedXml =
|
||||||
|
'<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||||
|
'<Mode>GOVERNANCE</Mode>' +
|
||||||
|
`<RetainUntilDate>${passDate.toISOString()}</RetainUntilDate>` +
|
||||||
|
'</Retention>';
|
||||||
|
|
||||||
|
const failTests = [
|
||||||
|
{
|
||||||
|
name: 'should fail with empty retention',
|
||||||
|
params: { key: 'Retention', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'request xml does not contain Retention',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with empty mode',
|
||||||
|
params: { key: 'Mode', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'request xml does not contain Mode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with empty retain until date',
|
||||||
|
params: { key: 'RetainDate', value: '' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'request xml does not contain RetainUntilDate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with invalid mode',
|
||||||
|
params: { key: 'Mode', value: 'GOVERPLIANCE' },
|
||||||
|
error: 'MalformedXML',
|
||||||
|
errMessage: 'Mode request xml must be one of "GOVERNANCE", ' +
|
||||||
|
'"COMPLIANCE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with retain until date in UTC format',
|
||||||
|
params: { key: 'RetainDate', value: `${date.toUTCString()}` },
|
||||||
|
error: 'InvalidRequest',
|
||||||
|
errMessage: 'RetainUntilDate timestamp must be ISO-8601 format',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with retain until date in GMT format',
|
||||||
|
params: { key: 'RetainDate', value: `${date.toString()}` },
|
||||||
|
error: 'InvalidRequest',
|
||||||
|
errMessage: 'RetainUntilDate timestamp must be ISO-8601 format',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should fail with retain until date in past',
|
||||||
|
params: { key: 'RetainDate', value: failDate.toISOString() },
|
||||||
|
error: 'InvalidRequest',
|
||||||
|
errMessage: 'RetainUntilDate must be in the future',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('object Retention validation', () => {
|
||||||
|
failTests.forEach(t => {
|
||||||
|
it(t.name, done => {
|
||||||
|
parseRetentionXml(buildXml(t.params.key, t.params.value), log,
|
||||||
|
err => {
|
||||||
|
assert(err[t.error]);
|
||||||
|
assert.strictEqual(err.description, t.errMessage);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass with valid retention', done => {
|
||||||
|
parseRetentionXml(buildXml('RetainDate', passDate.toISOString()), log,
|
||||||
|
(err, result) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.deepStrictEqual(result, expectedRetention);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('object Retention xml', () => {
|
||||||
|
it('should return empty string if no retention info', done => {
|
||||||
|
const xml = convertToXml({});
|
||||||
|
assert.equal(xml, '');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty string if retention info is empty', done => {
|
||||||
|
const xml = convertToXml(
|
||||||
|
{ retention: { mode: '', retainUntilDate: '' } });
|
||||||
|
assert.equal(xml, '');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return xml string', done => {
|
||||||
|
const xml = convertToXml(expectedRetention);
|
||||||
|
assert.strictEqual(xml, expectedXml);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,372 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
/* eslint dot-notation: "off" */
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
|
const {
|
||||||
|
EchoChannel,
|
||||||
|
logger,
|
||||||
|
} = require('./ersatz.js');
|
||||||
|
|
||||||
|
const expectedObjectType = 'Symmetric Key';
|
||||||
|
const expectedAlgorithm = 'AES';
|
||||||
|
const expectedLength = 256;
|
||||||
|
const expectedBlockCipherMode = 'CBC';
|
||||||
|
const expectedPaddingMethod = 'PKCS5';
|
||||||
|
const expectedIV = Buffer.alloc(16).fill(0);
|
||||||
|
const versionMajor = 1;
|
||||||
|
const versionMinor = 4;
|
||||||
|
const vendorIdentification = 'Scality Loopback KMIP Server';
|
||||||
|
const serverExtensions = [
|
||||||
|
{
|
||||||
|
name: 'Security Level',
|
||||||
|
tag: 0x541976,
|
||||||
|
type: 7,
|
||||||
|
value: 'Gangsta Grade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Prefered Algorithm',
|
||||||
|
tag: 0x542008,
|
||||||
|
type: 7,
|
||||||
|
value: 'Kevin Bacon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yo Dawg',
|
||||||
|
tag: 0x542011,
|
||||||
|
type: 7,
|
||||||
|
value: 'I heard you like kmip, so I put a server in your client ' +
|
||||||
|
'so you can do both ends of the conversation while you are ' +
|
||||||
|
'talking about server side encryption',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
class DummyServerTransport {
|
||||||
|
registerHandshakeFunction() {
|
||||||
|
throw new Error('DummyServerTransport::registerHandshakeFunction: ' +
|
||||||
|
'Client side operations not implemented');
|
||||||
|
}
|
||||||
|
send() {
|
||||||
|
throw new Error('DummyServerTransport::send: ' +
|
||||||
|
'Client side operations not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LoopbackServerChannel extends EchoChannel {
|
||||||
|
constructor(KMIPClass, Codec, options) {
|
||||||
|
super();
|
||||||
|
this.options = options || {
|
||||||
|
kmip: {
|
||||||
|
codec: {},
|
||||||
|
transport: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.KMIP = KMIPClass;
|
||||||
|
this.kmip = new KMIPClass(Codec, DummyServerTransport,
|
||||||
|
this.options);
|
||||||
|
serverExtensions.forEach(extension => {
|
||||||
|
this.kmip.mapExtension(extension.name, extension.tag);
|
||||||
|
});
|
||||||
|
this.managedKeys = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
write(data) {
|
||||||
|
const request = this.kmip._decodeMessage(logger, data);
|
||||||
|
const requestOperation = request.lookup(
|
||||||
|
'Request Message/Batch Item/Operation')[0];
|
||||||
|
this.routeRequest(
|
||||||
|
requestOperation, request, (err, responsePayload) => {
|
||||||
|
const uniqueBatchItemID = request.lookup(
|
||||||
|
'Request Message/Batch Item/Unique Batch Item ID')[0];
|
||||||
|
const requestProtocolVersionMinor = request.lookup(
|
||||||
|
'Request Message/Request Header/Protocol Version/' +
|
||||||
|
'Protocol Version Minor')[0];
|
||||||
|
const requestProtocolVersionMajor = request.lookup(
|
||||||
|
'Request Message/Request Header/Protocol Version/' +
|
||||||
|
'Protocol Version Major')[0];
|
||||||
|
|
||||||
|
let result;
|
||||||
|
if (err) {
|
||||||
|
logger.error('Request processing failed', { error: err });
|
||||||
|
result = err;
|
||||||
|
} else {
|
||||||
|
result = [
|
||||||
|
this.KMIP.Enumeration('Result Status', 'Success'),
|
||||||
|
this.KMIP.Structure('Response Payload',
|
||||||
|
responsePayload),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = this.KMIP.Message([
|
||||||
|
this.KMIP.Structure('Response Message', [
|
||||||
|
this.KMIP.Structure('Response Header', [
|
||||||
|
this.KMIP.Structure('Protocol Version', [
|
||||||
|
this.KMIP.Integer('Protocol Version Major',
|
||||||
|
requestProtocolVersionMajor),
|
||||||
|
this.KMIP.Integer('Protocol Version Minor',
|
||||||
|
requestProtocolVersionMinor),
|
||||||
|
]),
|
||||||
|
this.KMIP.DateTime('Time Stamp', new Date),
|
||||||
|
this.KMIP.Integer('Batch Count', 1),
|
||||||
|
]),
|
||||||
|
this.KMIP.Structure('Batch Item', [
|
||||||
|
this.KMIP.Enumeration('Operation',
|
||||||
|
requestOperation),
|
||||||
|
this.KMIP.ByteString('Unique Batch Item ID',
|
||||||
|
uniqueBatchItemID),
|
||||||
|
...result,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
super.write(this.kmip._encodeMessage(response));
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse(reason, message) {
|
||||||
|
return [
|
||||||
|
this.KMIP.Enumeration('Result Status', 'Operation Failed'),
|
||||||
|
this.KMIP.Enumeration('Result Reason', reason),
|
||||||
|
this.KMIP.Enumeration('Result Message', message),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
routeRequest(operation, request, cb) {
|
||||||
|
switch (operation) {
|
||||||
|
case 'Query': return this.routeQuery(request, cb);
|
||||||
|
case 'Discover Versions':
|
||||||
|
return this.routeDiscoverVersions(request, cb);
|
||||||
|
case 'Create': return this.routeCreate(request, cb);
|
||||||
|
case 'Activate': return this.routeActivate(request, cb);
|
||||||
|
case 'Encrypt': return this.routeEncrypt(request, cb);
|
||||||
|
case 'Decrypt': return this.routeDecrypt(request, cb);
|
||||||
|
case 'Revoke': return this.routeRevoke(request, cb);
|
||||||
|
case 'Destroy': return this.routeDestroy(request, cb);
|
||||||
|
default: return cb(new Error(`Unknown Operation: ${operation}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routeQuery(request, cb) {
|
||||||
|
const queryFunctions = request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/Query Function');
|
||||||
|
const response = [];
|
||||||
|
if (queryFunctions.includes('Query Operations')) {
|
||||||
|
response.push(
|
||||||
|
this.KMIP.Enumeration('Operation', 'Query'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Discover Versions'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Create'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Activate'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Encrypt'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Decrypt'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Revoke'),
|
||||||
|
this.KMIP.Enumeration('Operation', 'Destroy'));
|
||||||
|
}
|
||||||
|
if (queryFunctions.includes('Query Objects')) {
|
||||||
|
response.push(
|
||||||
|
this.KMIP.Enumeration('Object Type', 'Symmetric Key'));
|
||||||
|
}
|
||||||
|
if (queryFunctions.includes('Query Server Information')) {
|
||||||
|
response.push(
|
||||||
|
this.KMIP.TextString('Vendor Identification',
|
||||||
|
vendorIdentification),
|
||||||
|
this.KMIP.Structure('Server Information',
|
||||||
|
serverExtensions.map(extension =>
|
||||||
|
this.KMIP.TextString(
|
||||||
|
extension.name,
|
||||||
|
extension.value)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if (queryFunctions.includes('Query Extension Map')) {
|
||||||
|
serverExtensions.forEach(extension => {
|
||||||
|
response.push(
|
||||||
|
this.KMIP.Structure('Extension Information', [
|
||||||
|
this.KMIP.TextString('Extension Name', extension.name),
|
||||||
|
this.KMIP.Integer('Extension Tag', extension.tag),
|
||||||
|
/* 7 is KMIP TextString, not really used anyway in
|
||||||
|
* this implenetation, it could be anything
|
||||||
|
* without changing the behavior of the client code. */
|
||||||
|
this.KMIP.Integer('Extension Type', 7),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeDiscoverVersions(request, cb) {
|
||||||
|
const response = [
|
||||||
|
this.KMIP.Structure('Protocol Version', [
|
||||||
|
this.KMIP.Integer('Protocol Version Major', versionMajor),
|
||||||
|
this.KMIP.Integer('Protocol Version Minor', versionMinor),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeCreate(request, cb) {
|
||||||
|
let cryptographicAlgorithm;
|
||||||
|
let cryptographicLength;
|
||||||
|
let cryptographicUsageMask;
|
||||||
|
let activationDate;
|
||||||
|
const attributes = request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/Template-Attribute')[0];
|
||||||
|
attributes.forEach(item => {
|
||||||
|
const attribute = item['Attribute'];
|
||||||
|
const attributeValue = attribute.value[1]['Attribute Value'];
|
||||||
|
const diversion = attributeValue.diversion;
|
||||||
|
const value = attributeValue.value;
|
||||||
|
switch (diversion) {
|
||||||
|
case 'Cryptographic Algorithm':
|
||||||
|
assert(!cryptographicAlgorithm);
|
||||||
|
cryptographicAlgorithm = value;
|
||||||
|
break;
|
||||||
|
case 'Cryptographic Length':
|
||||||
|
assert(!cryptographicLength);
|
||||||
|
cryptographicLength = value;
|
||||||
|
break;
|
||||||
|
case 'Cryptographic Usage Mask':
|
||||||
|
assert(!cryptographicUsageMask);
|
||||||
|
cryptographicUsageMask = value;
|
||||||
|
break;
|
||||||
|
case 'Activation Date':
|
||||||
|
assert(!activationDate);
|
||||||
|
activationDate = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const decodedUsageMask =
|
||||||
|
this.kmip.decodeMask('Cryptographic Usage Mask',
|
||||||
|
cryptographicUsageMask);
|
||||||
|
assert(cryptographicAlgorithm === expectedAlgorithm);
|
||||||
|
assert(cryptographicLength === expectedLength);
|
||||||
|
assert(decodedUsageMask.includes('Encrypt'));
|
||||||
|
assert(decodedUsageMask.includes('Decrypt'));
|
||||||
|
const key = Buffer.from(crypto.randomBytes(cryptographicLength / 8));
|
||||||
|
const keyIdentifier = uuidv4();
|
||||||
|
this.managedKeys[keyIdentifier] = {
|
||||||
|
key,
|
||||||
|
activationDate,
|
||||||
|
};
|
||||||
|
const response = [
|
||||||
|
this.KMIP.Enumeration('Object Type', expectedObjectType),
|
||||||
|
this.KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeActivate(request, cb) {
|
||||||
|
const keyIdentifier = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Unique Identifier') || [undefined])[0];
|
||||||
|
this.managedKeys[keyIdentifier].activationDate =
|
||||||
|
new Date;
|
||||||
|
const response = [
|
||||||
|
this.KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getIvCounterNonce(request) {
|
||||||
|
/* Having / in the path is not a good idea for the server side.
|
||||||
|
* Because of this, Message::lookup() cannot be directly used to
|
||||||
|
* extract the IV, hence this function */
|
||||||
|
const requestPayload = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload')
|
||||||
|
|| [undefined])[0];
|
||||||
|
let ivCounterNonce;
|
||||||
|
requestPayload.forEach(attr => {
|
||||||
|
const ivCounterNonceAttr = attr['IV/Counter/Nonce'];
|
||||||
|
if (ivCounterNonceAttr) {
|
||||||
|
ivCounterNonce = ivCounterNonceAttr.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ivCounterNonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(cipherFunc, request, cb) {
|
||||||
|
const keyIdentifier = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Unique Identifier') || [undefined])[0];
|
||||||
|
const blockCipherMode = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Cryptographic Parameters/Block Cipher Mode')
|
||||||
|
|| [undefined])[0];
|
||||||
|
const paddingMethod = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Cryptographic Parameters/Padding Method')
|
||||||
|
|| [undefined])[0];
|
||||||
|
const cryptographicAlgorithm = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Cryptographic Parameters/Cryptographic Algorithm')
|
||||||
|
|| [undefined])[0];
|
||||||
|
const ivCounterNonce = this._getIvCounterNonce(request);
|
||||||
|
const data = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Data')
|
||||||
|
|| [undefined])[0];
|
||||||
|
assert(blockCipherMode === expectedBlockCipherMode);
|
||||||
|
assert(paddingMethod === expectedPaddingMethod);
|
||||||
|
assert(cryptographicAlgorithm === expectedAlgorithm);
|
||||||
|
assert(expectedIV.compare(ivCounterNonce) === 0);
|
||||||
|
const keySpec = this.managedKeys[keyIdentifier];
|
||||||
|
const now = new Date;
|
||||||
|
assert(keySpec.activationDate && keySpec.activationDate <= now);
|
||||||
|
const cipher = cipherFunc('aes-256-cbc', keySpec.key, ivCounterNonce);
|
||||||
|
let cipheredData = cipher.update(data);
|
||||||
|
const final = cipher.final();
|
||||||
|
if (final.length !== 0) {
|
||||||
|
cipheredData = Buffer.concat([cipheredData, final]);
|
||||||
|
}
|
||||||
|
const response = [
|
||||||
|
this.KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
this.KMIP.ByteString('Data', cipheredData),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeEncrypt(request, cb) {
|
||||||
|
return this._transform(crypto.createCipheriv, request, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeDecrypt(request, cb) {
|
||||||
|
return this._transform(crypto.createDecipheriv, request, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeRevoke(request, cb) {
|
||||||
|
const keyIdentifier = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Unique Identifier') || [undefined])[0];
|
||||||
|
this.managedKeys[keyIdentifier].activationDate = null;
|
||||||
|
const response = [
|
||||||
|
this.KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeDestroy(request, cb) {
|
||||||
|
const keyIdentifier = (
|
||||||
|
request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload/' +
|
||||||
|
'Unique Identifier') || [undefined])[0];
|
||||||
|
assert(!this.managedKeys[keyIdentifier].activationDate);
|
||||||
|
this.managedKeys[keyIdentifier] = null;
|
||||||
|
const response = [
|
||||||
|
this.KMIP.TextString('Unique Identifier', keyIdentifier),
|
||||||
|
];
|
||||||
|
return cb(null, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LoopbackServerChannel;
|
|
@ -0,0 +1,127 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
|
||||||
|
/* Invalid type */
|
||||||
|
|
||||||
|
Buffer.from('2100000000000000', 'hex'),
|
||||||
|
Buffer.from('2100000b00000000', 'hex'),
|
||||||
|
|
||||||
|
/* Structure */
|
||||||
|
// too short
|
||||||
|
Buffer.from('42', 'hex'),
|
||||||
|
Buffer.from('4200', 'hex'),
|
||||||
|
Buffer.from('420078', 'hex'),
|
||||||
|
Buffer.from('42007801', 'hex'),
|
||||||
|
Buffer.from('4200780100', 'hex'),
|
||||||
|
Buffer.from('420078010000', 'hex'),
|
||||||
|
Buffer.from('4200780100000001', 'hex'),
|
||||||
|
Buffer.from('420078010000000100', 'hex'),
|
||||||
|
Buffer.from('4200780100000008', 'hex'),
|
||||||
|
Buffer.from('420078010000000800', 'hex'),
|
||||||
|
Buffer.from('42007801000000080000', 'hex'),
|
||||||
|
Buffer.from('4200780100000008000000', 'hex'),
|
||||||
|
Buffer.from('420078010000000800000000', 'hex'),
|
||||||
|
Buffer.from('4200780100000010', 'hex'),
|
||||||
|
|
||||||
|
/* Integer */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200780200000004', 'hex'),
|
||||||
|
Buffer.from('420078020000000400', 'hex'),
|
||||||
|
Buffer.from('42007802000000040000', 'hex'),
|
||||||
|
Buffer.from('4200780200000004000000', 'hex'),
|
||||||
|
// invalid length for the type
|
||||||
|
Buffer.from('42007802000000080000000000000000', 'hex'),
|
||||||
|
Buffer.from('42007802000000020000000000000000', 'hex'),
|
||||||
|
Buffer.from('42007802000000000000000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* Long Integer */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200780300000008', 'hex'),
|
||||||
|
Buffer.from('420078030000000810', 'hex'),
|
||||||
|
Buffer.from('42007803000000081000', 'hex'),
|
||||||
|
Buffer.from('4200780300000008100000', 'hex'),
|
||||||
|
Buffer.from('420078030000000810000000', 'hex'),
|
||||||
|
Buffer.from('42007803000000081000000000', 'hex'),
|
||||||
|
Buffer.from('4200780300000008100000000000', 'hex'),
|
||||||
|
Buffer.from('420078030000000810000000000000', 'hex'),
|
||||||
|
// 53bit overflow
|
||||||
|
Buffer.from('42007803000000081000000000000000', 'hex'),
|
||||||
|
Buffer.from('4200780300000008ffffffffffffffff', 'hex'),
|
||||||
|
// invalid length for the type
|
||||||
|
Buffer.from('420078030000000400000001', 'hex'),
|
||||||
|
Buffer.from('42007803000000100000000000000000100000000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* Big Integer */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200780400000001', 'hex'),
|
||||||
|
Buffer.from('420078040000000200', 'hex'),
|
||||||
|
|
||||||
|
/* Enumeration */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200740500000004', 'hex'),
|
||||||
|
Buffer.from('4200740500000004000000', 'hex'),
|
||||||
|
// invalid length for the type
|
||||||
|
Buffer.from('42007405000000020000', 'hex'),
|
||||||
|
Buffer.from('4200740500000006000000000000', 'hex'),
|
||||||
|
// non existing tag and enum value with invalid length
|
||||||
|
Buffer.from('45007405000000020000', 'hex'),
|
||||||
|
Buffer.from('4500740500000006000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* Boolean */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200740600000008', 'hex'),
|
||||||
|
Buffer.from('420074060000000800', 'hex'),
|
||||||
|
Buffer.from('42007406000000080000', 'hex'),
|
||||||
|
Buffer.from('4200740600000008000000', 'hex'),
|
||||||
|
Buffer.from('420074060000000800000000', 'hex'),
|
||||||
|
Buffer.from('42007406000000080000000000', 'hex'),
|
||||||
|
Buffer.from('4200740600000008000000000000', 'hex'),
|
||||||
|
// invalid length
|
||||||
|
Buffer.from('420074060000000400000000', 'hex'),
|
||||||
|
Buffer.from('420074060000001000000000000000000000000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* TextString */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200740700000008', 'hex'),
|
||||||
|
Buffer.from('420074070000000800', 'hex'),
|
||||||
|
Buffer.from('42007407000000080000', 'hex'),
|
||||||
|
Buffer.from('4200740700000008000000', 'hex'),
|
||||||
|
Buffer.from('420074070000000800000000', 'hex'),
|
||||||
|
Buffer.from('42007407000000080000000000', 'hex'),
|
||||||
|
Buffer.from('4200740700000008000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* ByteString */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200740800000008', 'hex'),
|
||||||
|
Buffer.from('420074080000000800', 'hex'),
|
||||||
|
Buffer.from('42007408000000080000', 'hex'),
|
||||||
|
Buffer.from('4200740800000008000000', 'hex'),
|
||||||
|
Buffer.from('420074080000000800000000', 'hex'),
|
||||||
|
Buffer.from('42007408000000080000000000', 'hex'),
|
||||||
|
Buffer.from('4200740800000008000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* Date-Time */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200740900000008', 'hex'),
|
||||||
|
Buffer.from('420074090000000800', 'hex'),
|
||||||
|
Buffer.from('42007409000000080000', 'hex'),
|
||||||
|
Buffer.from('4200740900000008000000', 'hex'),
|
||||||
|
Buffer.from('420074090000000800000000', 'hex'),
|
||||||
|
Buffer.from('42007409000000080000000000', 'hex'),
|
||||||
|
Buffer.from('4200740900000008000000000000', 'hex'),
|
||||||
|
// invalid length
|
||||||
|
Buffer.from('420074090000000400000000', 'hex'),
|
||||||
|
Buffer.from('420074090000001000000000000000000000000000000000', 'hex'),
|
||||||
|
|
||||||
|
/* Interval */
|
||||||
|
// too short
|
||||||
|
Buffer.from('4200780a00000004', 'hex'),
|
||||||
|
Buffer.from('4200780a0000000400', 'hex'),
|
||||||
|
Buffer.from('4200780a000000040000', 'hex'),
|
||||||
|
Buffer.from('4200780a00000004000000', 'hex'),
|
||||||
|
// invalid length for the type
|
||||||
|
Buffer.from('4200780a000000080000000000000000', 'hex'),
|
||||||
|
Buffer.from('4200780a000000020000000000000000', 'hex'),
|
||||||
|
Buffer.from('4200780a000000000000000000000000', 'hex'),
|
||||||
|
];
|
|
@ -0,0 +1,125 @@
|
||||||
|
'use strict'; // eslint-disable-line
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
info: () => {},
|
||||||
|
debug: () => {},
|
||||||
|
warn: () => {},
|
||||||
|
error: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Fake tls AND socket objects, duck type */
|
||||||
|
class EchoChannel {
|
||||||
|
constructor() {
|
||||||
|
this.clogged = false;
|
||||||
|
this.eventHandler = {};
|
||||||
|
this.deferedSignal = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tls object members substitutes */
|
||||||
|
|
||||||
|
connect(port, options, cb) {
|
||||||
|
process.nextTick(cb);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, cb) {
|
||||||
|
this.eventHandler[event] = cb;
|
||||||
|
if (this.deferedSignal[event] &&
|
||||||
|
this.deferedSignal[event].length > 0) {
|
||||||
|
this.deferedSignal[event].forEach(this.eventHandler[event]);
|
||||||
|
this.deferedSignal[event] = undefined;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* socket object members substitutes */
|
||||||
|
|
||||||
|
cork() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uncork() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(data) {
|
||||||
|
if (!this.clogged) {
|
||||||
|
return this.emit('data', data);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
return this.emit('end');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Instrumentation member functions */
|
||||||
|
|
||||||
|
emit(event, data) {
|
||||||
|
if (this.eventHandler[event]) {
|
||||||
|
this.eventHandler[event](data);
|
||||||
|
} else {
|
||||||
|
if (!this.deferedSignal[event]) {
|
||||||
|
this.deferedSignal[event] = [];
|
||||||
|
}
|
||||||
|
this.deferedSignal[event].push(data);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
clog() {
|
||||||
|
this.clogged = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MirrorChannel extends EchoChannel {
|
||||||
|
constructor(KMIPClass, Codec) {
|
||||||
|
super();
|
||||||
|
this.codec = new Codec({});
|
||||||
|
this.KMIP = KMIPClass;
|
||||||
|
}
|
||||||
|
write(data) {
|
||||||
|
const request = this.codec.decode(logger, data);
|
||||||
|
const uniqueBatchItemID = request.lookup(
|
||||||
|
'Request Message/Batch Item/Unique Batch Item ID')[0];
|
||||||
|
const requestPayload = request.lookup(
|
||||||
|
'Request Message/Batch Item/Request Payload')[0];
|
||||||
|
const requestProtocolVersionMinor = request.lookup(
|
||||||
|
'Request Message/Request Header/Protocol Version/' +
|
||||||
|
'Protocol Version Minor')[0];
|
||||||
|
const requestProtocolVersionMajor = request.lookup(
|
||||||
|
'Request Message/Request Header/Protocol Version/' +
|
||||||
|
'Protocol Version Major')[0];
|
||||||
|
const requestOperation = request.lookup(
|
||||||
|
'Request Message/Batch Item/Operation')[0];
|
||||||
|
const response = this.KMIP.Message([
|
||||||
|
this.KMIP.Structure('Response Message', [
|
||||||
|
this.KMIP.Structure('Response Header', [
|
||||||
|
this.KMIP.Structure('Protocol Version', [
|
||||||
|
this.KMIP.Integer('Protocol Version Major',
|
||||||
|
requestProtocolVersionMajor),
|
||||||
|
this.KMIP.Integer('Protocol Version Minor',
|
||||||
|
requestProtocolVersionMinor),
|
||||||
|
]),
|
||||||
|
this.KMIP.DateTime('Time Stamp', new Date),
|
||||||
|
this.KMIP.Integer('Batch Count', 1),
|
||||||
|
]),
|
||||||
|
this.KMIP.Structure('Batch Item', [
|
||||||
|
this.KMIP.Enumeration('Operation', requestOperation),
|
||||||
|
this.KMIP.ByteString('Unique Batch Item ID',
|
||||||
|
uniqueBatchItemID),
|
||||||
|
this.KMIP.Enumeration('Result Status', 'Success'),
|
||||||
|
this.KMIP.Structure('Response Payload', requestPayload),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
super.write(this.codec.encode(response));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { logger, EchoChannel, MirrorChannel };
|
|
@ -0,0 +1,82 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
const KMIP = require('../../../lib/network/kmip');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
operation: 'Query',
|
||||||
|
payload: () => [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operation: 'Query',
|
||||||
|
payload: () => [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Server Information'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Profiles'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Capabilities'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Application Namespaces'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension List'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension Map'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Attestation Types'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query RNGs'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Validations'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Client Registration Methods'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operation: 'Discover Versions',
|
||||||
|
payload: () => [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 2),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 0),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 4),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 3),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 2),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 1),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 0),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operation: 'Create',
|
||||||
|
payload: kmip => [
|
||||||
|
KMIP.Enumeration('Object Type', 'Symmetric Key'),
|
||||||
|
KMIP.Structure('Template-Attribute', [
|
||||||
|
KMIP.Attribute('TextString', 'x-Name', 's3-thekey'),
|
||||||
|
KMIP.Attribute('Enumeration', 'Cryptographic Algorithm',
|
||||||
|
'AES'),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Length', 256),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Usage Mask',
|
||||||
|
kmip.encodeMask(
|
||||||
|
'Cryptographic Usage Mask',
|
||||||
|
['Encrypt', 'Decrypt'])),
|
||||||
|
KMIP.Attribute('Date-Time', 'Activation Date',
|
||||||
|
new Date),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,120 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
|
const TTLVCodec = require('../../../lib/network/kmip/codec/ttlv.js');
|
||||||
|
const KMIP = require('../../../lib/network/kmip');
|
||||||
|
|
||||||
|
const kmip = new KMIP(TTLVCodec,
|
||||||
|
class DummyTransport {},
|
||||||
|
{ kmip: {} }, () => {});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
KMIP.Message([
|
||||||
|
KMIP.Structure('Request Message', [
|
||||||
|
KMIP.Structure('Request Header', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 3),
|
||||||
|
]),
|
||||||
|
KMIP.Integer('Maximum Response Size', 256),
|
||||||
|
KMIP.Integer('Batch Count', 1),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', 'Query'),
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
KMIP.Message([
|
||||||
|
KMIP.Structure('Request Message', [
|
||||||
|
KMIP.Structure('Request Header', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 2),
|
||||||
|
]),
|
||||||
|
KMIP.Integer('Maximum Response Size', 2048),
|
||||||
|
KMIP.Boolean('Asynchronous Indicator', false),
|
||||||
|
KMIP.Integer('Batch Count', 3),
|
||||||
|
KMIP.ByteString('Asynchronous Correlation Value',
|
||||||
|
Buffer.from('Arrggg...', 'utf8')),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', 'Query'),
|
||||||
|
KMIP.ByteString('Unique Batch Item ID',
|
||||||
|
Buffer.from('foo', 'utf8')),
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Operations'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Objects'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Server Information'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Profiles'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Capabilities'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Application Namespaces'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension List'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Extension Map'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Attestation Types'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query RNGs'),
|
||||||
|
KMIP.Enumeration('Query Function', 'Query Validations'),
|
||||||
|
KMIP.Enumeration('Query Function',
|
||||||
|
'Query Client Registration Methods'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', 'Discover Versions'),
|
||||||
|
KMIP.ByteString('Unique Batch Item ID',
|
||||||
|
Buffer.from('bar', 'utf8')),
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 2),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 0),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 4),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 3),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 2),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 1),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Protocol Version', [
|
||||||
|
KMIP.Integer('Protocol Version Major', 1),
|
||||||
|
KMIP.Integer('Protocol Version Minor', 0),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
KMIP.Structure('Batch Item', [
|
||||||
|
KMIP.Enumeration('Operation', 'Create'),
|
||||||
|
KMIP.ByteString('Unique Batch Item ID',
|
||||||
|
Buffer.from('baz', 'utf8')),
|
||||||
|
KMIP.Structure('Request Payload', [
|
||||||
|
KMIP.Enumeration('Object Type', 'Symmetric Key'),
|
||||||
|
KMIP.Structure('Template-Attribute', [
|
||||||
|
KMIP.Attribute('TextString', 'x-Name', 's3-thekey'),
|
||||||
|
KMIP.Attribute('Enumeration', 'Cryptographic Algorithm',
|
||||||
|
'AES'),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Length', 256),
|
||||||
|
KMIP.Attribute('Integer', 'Cryptographic Usage Mask',
|
||||||
|
kmip.encodeMask(
|
||||||
|
'Cryptographic Usage Mask',
|
||||||
|
['Encrypt', 'Decrypt'])),
|
||||||
|
KMIP.Attribute('Date-Time', 'Activation Date',
|
||||||
|
new Date),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
];
|
|
@ -0,0 +1,247 @@
|
||||||
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007801000000904200770100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200500200000004' +
|
||||||
|
'000001000000000042000d0200000004' +
|
||||||
|
'000000010000000042000f0100000038' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007901000000204200740500000004' +
|
||||||
|
'00000001000000004200740500000004' +
|
||||||
|
'0000000200000000', 'hex'),
|
||||||
|
response: Buffer.from('42007b01000000a042007a0100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200920900000008' +
|
||||||
|
'00000000568a5be242000d0200000004' +
|
||||||
|
'000000010000000042000f0100000048' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007f05000000040000000100000000' +
|
||||||
|
'42007e05000000040000000200000000' +
|
||||||
|
'42007d0700000009544f4f5f4c415247' +
|
||||||
|
'4500000000000000', 'hex'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007801000000904200770100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200500200000004' +
|
||||||
|
'000008000000000042000d0200000004' +
|
||||||
|
'000000010000000042000f0100000038' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007901000000204200740500000004' +
|
||||||
|
'00000001000000004200740500000004' +
|
||||||
|
'0000000200000000', 'hex'),
|
||||||
|
response: Buffer.from('42007b010000038042007a0100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200920900000008' +
|
||||||
|
'00000000568a5be242000d0200000004' +
|
||||||
|
'000000010000000042000f0100000328' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007f05000000040000000000000000' +
|
||||||
|
'42007c010000030042005c0500000004' +
|
||||||
|
'000000180000000042005c0500000004' +
|
||||||
|
'000000080000000042005c0500000004' +
|
||||||
|
'000000140000000042005c0500000004' +
|
||||||
|
'0000000a0000000042005c0500000004' +
|
||||||
|
'000000010000000042005c0500000004' +
|
||||||
|
'000000030000000042005c0500000004' +
|
||||||
|
'0000000b0000000042005c0500000004' +
|
||||||
|
'0000000c0000000042005c0500000004' +
|
||||||
|
'0000000d0000000042005c0500000004' +
|
||||||
|
'0000000e0000000042005c0500000004' +
|
||||||
|
'0000000f0000000042005c0500000004' +
|
||||||
|
'000000120000000042005c0500000004' +
|
||||||
|
'000000130000000042005c0500000004' +
|
||||||
|
'0000001a0000000042005c0500000004' +
|
||||||
|
'000000190000000042005c0500000004' +
|
||||||
|
'000000090000000042005c0500000004' +
|
||||||
|
'000000110000000042005c0500000004' +
|
||||||
|
'000000020000000042005c0500000004' +
|
||||||
|
'000000040000000042005c0500000004' +
|
||||||
|
'000000150000000042005c0500000004' +
|
||||||
|
'000000160000000042005c0500000004' +
|
||||||
|
'000000100000000042005c0500000004' +
|
||||||
|
'0000001d0000000042005c0500000004' +
|
||||||
|
'000000060000000042005c0500000004' +
|
||||||
|
'000000070000000042005c0500000004' +
|
||||||
|
'0000001e0000000042005c0500000004' +
|
||||||
|
'0000001b0000000042005c0500000004' +
|
||||||
|
'0000001c0000000042005c0500000004' +
|
||||||
|
'000000250000000042005c0500000004' +
|
||||||
|
'000000260000000042005c0500000004' +
|
||||||
|
'0000001f0000000042005c0500000004' +
|
||||||
|
'000000200000000042005c0500000004' +
|
||||||
|
'000000210000000042005c0500000004' +
|
||||||
|
'000000220000000042005c0500000004' +
|
||||||
|
'000000230000000042005c0500000004' +
|
||||||
|
'000000240000000042005c0500000004' +
|
||||||
|
'000000270000000042005c0500000004' +
|
||||||
|
'000000280000000042005c0500000004' +
|
||||||
|
'00000029000000004200570500000004' +
|
||||||
|
'00000001000000004200570500000004' +
|
||||||
|
'00000002000000004200570500000004' +
|
||||||
|
'00000007000000004200570500000004' +
|
||||||
|
'00000003000000004200570500000004' +
|
||||||
|
'00000004000000004200570500000004' +
|
||||||
|
'00000006000000004200570500000004' +
|
||||||
|
'00000008000000004200570500000004' +
|
||||||
|
'00000005000000004200570500000004' +
|
||||||
|
'0000000900000000', 'hex'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007801000000d04200770100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200500200000004' +
|
||||||
|
'000008000000000042000d0200000004' +
|
||||||
|
'000000020000000042000f0100000048' +
|
||||||
|
'4200930800000003666f6f0000000000' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007901000000204200740500000004' +
|
||||||
|
'00000001000000004200740500000004' +
|
||||||
|
'000000020000000042000f0100000028' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42007901000000104200740500000004' +
|
||||||
|
'0000000300000000', 'hex'),
|
||||||
|
response: Buffer.from('42007b010000028042007a0100000048' +
|
||||||
|
'420069010000002042006a0200000004' +
|
||||||
|
'000000010000000042006b0200000004' +
|
||||||
|
'00000003000000004200920900000008' +
|
||||||
|
'000000005c2d3df242000d0200000004' +
|
||||||
|
'000000020000000042000f0100000188' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'4200930800000003666f6f0000000000' +
|
||||||
|
'42007f05000000040000000000000000' +
|
||||||
|
'42007c010000015042005c0500000004' +
|
||||||
|
'000000010000000042005c0500000004' +
|
||||||
|
'000000020000000042005c0500000004' +
|
||||||
|
'000000030000000042005c0500000004' +
|
||||||
|
'000000080000000042005c0500000004' +
|
||||||
|
'0000000a0000000042005c0500000004' +
|
||||||
|
'0000000b0000000042005c0500000004' +
|
||||||
|
'0000000c0000000042005c0500000004' +
|
||||||
|
'0000000d0000000042005c0500000004' +
|
||||||
|
'0000000e0000000042005c0500000004' +
|
||||||
|
'0000000f0000000042005c0500000004' +
|
||||||
|
'000000120000000042005c0500000004' +
|
||||||
|
'000000130000000042005c0500000004' +
|
||||||
|
'000000140000000042005c0500000004' +
|
||||||
|
'000000180000000042005c0500000004' +
|
||||||
|
'0000001e000000004200570500000004' +
|
||||||
|
'00000001000000004200570500000004' +
|
||||||
|
'00000002000000004200570500000004' +
|
||||||
|
'00000003000000004200570500000004' +
|
||||||
|
'00000004000000004200570500000004' +
|
||||||
|
'00000006000000004200570500000004' +
|
||||||
|
'000000070000000042000f0100000098' +
|
||||||
|
'42005c05000000040000001800000000' +
|
||||||
|
'42009308000000036261720000000000' +
|
||||||
|
'42007f05000000040000000000000000' +
|
||||||
|
'42007c010000006042009d070000000c' +
|
||||||
|
'4b4b4b4b4b4b4b4b4b4b4b4b00000000' +
|
||||||
|
'4200880100000040420088070000000d' +
|
||||||
|
'4b4b4b4b4b4b4b4b4b4b4b4b4b000000' +
|
||||||
|
'420088070000000c4b4b4b4b4b4b4b4b' +
|
||||||
|
'4b4b4b4b000000004200880700000005' +
|
||||||
|
'4b4b4b4b4b000000', 'hex'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('', 'hex'),
|
||||||
|
response: Buffer.from('', 'hex'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('4200780100000000', 'hex'),
|
||||||
|
response: Buffer.from('4200780100000000', 'hex'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* Valid message with unknown tag */
|
||||||
|
request: Buffer.from('56000002000000040000000100000000', 'hex'),
|
||||||
|
response: Buffer.from('56000006000000080000000000000001', 'hex'),
|
||||||
|
degenerated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* Valid message with unknown enum */
|
||||||
|
/* on a non-enumeration tag */
|
||||||
|
request: Buffer.from('42007805000000040000000100000000', 'hex'),
|
||||||
|
/* on an enumeration tag */
|
||||||
|
response: Buffer.from('42007405000000040000000000000000', 'hex'),
|
||||||
|
degenerated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* padding is missing at the end of the message */
|
||||||
|
request: Buffer.from('42000f080000000400000001', 'hex'),
|
||||||
|
response: Buffer.from('42000f0a0000000400000001', 'hex'),
|
||||||
|
degenerated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42000f08000000040000000100000000', 'hex'),
|
||||||
|
response: Buffer.from('42000f0a000000040000000100000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* chained message, shouldn't happen but validate the structure loop */
|
||||||
|
request: Buffer.from('42000f08000000040000000100000000' +
|
||||||
|
'42000f08000000040000000100000000' +
|
||||||
|
'42000f08000000040000000100000000' +
|
||||||
|
'42000f08000000040000000100000000'
|
||||||
|
, 'hex'),
|
||||||
|
response: Buffer.from('42000f0a000000040000000100000000' +
|
||||||
|
'42000f0a000000040000000100000000' +
|
||||||
|
'42000f0a000000040000000100000000' +
|
||||||
|
'42000f0a000000040000000100000000'
|
||||||
|
, 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* zero-length payload */
|
||||||
|
request: Buffer.from('4200780400000000', 'hex'),
|
||||||
|
response: Buffer.from('4200780400000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
/* no padding */
|
||||||
|
request: Buffer.from('420078040000000100', 'hex'),
|
||||||
|
response: Buffer.from('420078040000000100', 'hex'),
|
||||||
|
degenerated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007406000000080000000000000001', 'hex'),
|
||||||
|
response: Buffer.from('42007406000000080000000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007406000000081111111111111111', 'hex'),
|
||||||
|
response: Buffer.from('42007406000000080101010101010101', 'hex'),
|
||||||
|
degenerated: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('4200740700000000', 'hex'),
|
||||||
|
response: Buffer.from('42007407000000010100000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007407000000010000000000000000', 'hex'),
|
||||||
|
response: Buffer.from('42007407000000020100000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('4200740800000000', 'hex'),
|
||||||
|
response: Buffer.from('42007408000000010100000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007408000000010000000000000000', 'hex'),
|
||||||
|
response: Buffer.from('42007408000000020100000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
request: Buffer.from('42007409000000080000000000000001', 'hex'),
|
||||||
|
response: Buffer.from('42007409000000080000000000000000', 'hex'),
|
||||||
|
degenerated: false,
|
||||||
|
},
|
||||||
|
];
|
152
yarn.lock
152
yarn.lock
|
@ -39,6 +39,42 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "8.x.x"
|
"@hapi/hoek" "8.x.x"
|
||||||
|
|
||||||
|
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
|
||||||
|
integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==
|
||||||
|
dependencies:
|
||||||
|
type-detect "4.0.8"
|
||||||
|
|
||||||
|
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
|
||||||
|
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/commons" "^1.7.0"
|
||||||
|
|
||||||
|
"@sinonjs/formatio@^5.0.1":
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
|
||||||
|
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/commons" "^1"
|
||||||
|
"@sinonjs/samsam" "^5.0.2"
|
||||||
|
|
||||||
|
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.0.3":
|
||||||
|
version "5.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938"
|
||||||
|
integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/commons" "^1.6.0"
|
||||||
|
lodash.get "^4.4.2"
|
||||||
|
type-detect "^4.0.8"
|
||||||
|
|
||||||
|
"@sinonjs/text-encoding@^0.7.1":
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||||
|
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||||
|
|
||||||
JSONStream@^1.0.0:
|
JSONStream@^1.0.0:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||||
|
@ -286,7 +322,7 @@ cli-width@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||||
|
|
||||||
cluster-key-slot@^1.0.6:
|
cluster-key-slot@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||||
|
@ -397,10 +433,10 @@ debug@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.1.0:
|
debug@^4.1.1, debug@~4.1.0:
|
||||||
version "3.2.6"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
|
@ -411,13 +447,6 @@ debug@~3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@~4.1.0:
|
|
||||||
version "4.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
|
||||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
|
||||||
dependencies:
|
|
||||||
ms "^2.1.1"
|
|
||||||
|
|
||||||
deep-is@~0.1.3:
|
deep-is@~0.1.3:
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||||
|
@ -448,6 +477,11 @@ diff@1.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
|
||||||
integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8=
|
integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8=
|
||||||
|
|
||||||
|
diff@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||||
|
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||||
|
|
||||||
diskusage@^1.1.1:
|
diskusage@^1.1.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b"
|
resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.1.3.tgz#680d7dbf1b679168a195c9240eb3552cbd2c067b"
|
||||||
|
@ -822,7 +856,7 @@ glob@3.2.11:
|
||||||
inherits "2"
|
inherits "2"
|
||||||
minimatch "0.3"
|
minimatch "0.3"
|
||||||
|
|
||||||
glob@^7.0.3, glob@^7.1.2, glob@^7.1.3:
|
glob@^7.0.3, glob@^7.1.2:
|
||||||
version "7.1.6"
|
version "7.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||||
|
@ -834,6 +868,18 @@ glob@^7.0.3, glob@^7.1.2, glob@^7.1.3:
|
||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
glob@^7.1.3:
|
||||||
|
version "7.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||||
|
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
globals@^9.2.0:
|
globals@^9.2.0:
|
||||||
version "9.18.0"
|
version "9.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||||
|
@ -875,6 +921,11 @@ has-cors@1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
|
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
|
||||||
integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
|
integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
|
||||||
|
|
||||||
|
has-flag@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
ignore@^3.1.2:
|
ignore@^3.1.2:
|
||||||
version "3.3.10"
|
version "3.3.10"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
||||||
|
@ -935,17 +986,17 @@ ioctl@2.0.0:
|
||||||
bindings "^1.1.1"
|
bindings "^1.1.1"
|
||||||
nan "^2.3.2"
|
nan "^2.3.2"
|
||||||
|
|
||||||
ioredis@4.9.5:
|
ioredis@4.17.3:
|
||||||
version "4.9.5"
|
version "4.17.3"
|
||||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.9.5.tgz#0bbba0a9faae93485d3231e1b819d2d4e23271d9"
|
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.3.tgz#9938c60e4ca685f75326337177bdc2e73ae9c9dc"
|
||||||
integrity sha512-L9MVfvX4F3LScTMEgriCGixzqinJsYy7Mt0NPX8RyuOTmx5JW0744pM4Ze2KVQcP3J0zvKYZ1LywAB6KIq7PYg==
|
integrity sha512-iRvq4BOYzNFkDnSyhx7cmJNOi1x/HWYe+A4VXHBu4qpwJaGT1Mp+D2bVGJntH9K/Z/GeOM/Nprb8gB3bmitz1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
cluster-key-slot "^1.0.6"
|
cluster-key-slot "^1.1.0"
|
||||||
debug "^3.1.0"
|
debug "^4.1.1"
|
||||||
denque "^1.1.0"
|
denque "^1.1.0"
|
||||||
lodash.defaults "^4.2.0"
|
lodash.defaults "^4.2.0"
|
||||||
lodash.flatten "^4.4.0"
|
lodash.flatten "^4.4.0"
|
||||||
redis-commands "1.4.0"
|
redis-commands "1.5.0"
|
||||||
redis-errors "^1.2.0"
|
redis-errors "^1.2.0"
|
||||||
redis-parser "^3.0.0"
|
redis-parser "^3.0.0"
|
||||||
standard-as-callback "^2.0.1"
|
standard-as-callback "^2.0.1"
|
||||||
|
@ -1056,6 +1107,11 @@ jsonpointer@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
||||||
integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
|
integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
|
||||||
|
|
||||||
|
just-extend@^4.0.2:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||||
|
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||||
|
|
||||||
keypress@0.1.x:
|
keypress@0.1.x:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a"
|
resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a"
|
||||||
|
@ -1199,6 +1255,11 @@ lodash.flatten@^4.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||||
|
|
||||||
|
lodash.get@^4.4.2:
|
||||||
|
version "4.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
|
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||||
|
|
||||||
lodash.union@^4.6.0:
|
lodash.union@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
|
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
|
||||||
|
@ -1379,6 +1440,17 @@ next-tick@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||||
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
|
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
|
||||||
|
|
||||||
|
nise@^4.0.1:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.3.tgz#9f79ff02fa002ed5ffbc538ad58518fa011dc913"
|
||||||
|
integrity sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/commons" "^1.7.0"
|
||||||
|
"@sinonjs/fake-timers" "^6.0.0"
|
||||||
|
"@sinonjs/text-encoding" "^0.7.1"
|
||||||
|
just-extend "^4.0.2"
|
||||||
|
path-to-regexp "^1.7.0"
|
||||||
|
|
||||||
node-forge@^0.7.1:
|
node-forge@^0.7.1:
|
||||||
version "0.7.6"
|
version "0.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
||||||
|
@ -1484,6 +1556,13 @@ path-is-inside@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||||
|
|
||||||
|
path-to-regexp@^1.7.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||||
|
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||||
|
dependencies:
|
||||||
|
isarray "0.0.1"
|
||||||
|
|
||||||
pluralize@^1.2.1:
|
pluralize@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
|
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
|
||||||
|
@ -1603,10 +1682,10 @@ readline2@^1.0.1:
|
||||||
is-fullwidth-code-point "^1.0.0"
|
is-fullwidth-code-point "^1.0.0"
|
||||||
mute-stream "0.0.5"
|
mute-stream "0.0.5"
|
||||||
|
|
||||||
redis-commands@1.4.0:
|
redis-commands@1.5.0:
|
||||||
version "1.4.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f"
|
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
|
||||||
integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==
|
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
|
||||||
|
|
||||||
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
@ -1705,6 +1784,19 @@ simple-glob@^0.2:
|
||||||
lodash.flatten "^4.4.0"
|
lodash.flatten "^4.4.0"
|
||||||
lodash.union "^4.6.0"
|
lodash.union "^4.6.0"
|
||||||
|
|
||||||
|
sinon@^9.0.2:
|
||||||
|
version "9.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
|
||||||
|
integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/commons" "^1.7.2"
|
||||||
|
"@sinonjs/fake-timers" "^6.0.1"
|
||||||
|
"@sinonjs/formatio" "^5.0.1"
|
||||||
|
"@sinonjs/samsam" "^5.0.3"
|
||||||
|
diff "^4.0.2"
|
||||||
|
nise "^4.0.1"
|
||||||
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
slice-ansi@0.0.4:
|
slice-ansi@0.0.4:
|
||||||
version "0.0.4"
|
version "0.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
|
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
|
||||||
|
@ -1870,6 +1962,13 @@ supports-color@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||||
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
|
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
|
||||||
|
|
||||||
|
supports-color@^7.1.0:
|
||||||
|
version "7.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||||
|
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
|
||||||
|
dependencies:
|
||||||
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
table@^3.7.8:
|
table@^3.7.8:
|
||||||
version "3.8.3"
|
version "3.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
|
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
|
||||||
|
@ -1917,6 +2016,11 @@ type-check@~0.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
|
|
||||||
|
type-detect@4.0.8, type-detect@^4.0.8:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||||
|
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||||
|
|
||||||
type@^1.0.1:
|
type@^1.0.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||||
|
|
Loading…
Reference in New Issue