Compare commits
No commits in common. "2886222e5884614e7dd299615d3d242a8eb66dfa" and "04581abbf68d041b8a1d4a35239809ac707673f0" have entirely different histories.
2886222e58
...
04581abbf6
|
@ -80,53 +80,3 @@ this._lifecycleConfiguration = lifecycleConfiguration || null;
|
|||
### Usage
|
||||
|
||||
Used to store the bucket lifecycle configuration info
|
||||
|
||||
## Model version 7
|
||||
|
||||
### Properties Added
|
||||
|
||||
```javascript
|
||||
this._objectLockEnabled = objectLockEnabled || false;
|
||||
this._objectLockConfiguration = objectLockConfiguration || null;
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Used to determine whether object lock capabilities are enabled on a bucket and
|
||||
to store the object lock configuration of the bucket
|
||||
|
||||
## Model version 8
|
||||
|
||||
### Properties Added
|
||||
|
||||
```javascript
|
||||
this._notificationConfiguration = notificationConfiguration || null;
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Used to store the bucket notification configuration info
|
||||
|
||||
## Model version 9
|
||||
|
||||
### Properties Added
|
||||
|
||||
```javascript
|
||||
this._serverSideEncryption.configuredMasterKeyId = configuredMasterKeyId || undefined;
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Used to store the users configured KMS key id
|
||||
|
||||
## Model version 10
|
||||
|
||||
### Properties Added
|
||||
|
||||
```javascript
|
||||
this._uid = uid || uuid();
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Used to set a unique identifier on a bucket
|
||||
|
|
|
@ -252,10 +252,6 @@
|
|||
"code": 404,
|
||||
"description": "The lifecycle configuration does not exist."
|
||||
},
|
||||
"NoSuchObjectLockConfiguration": {
|
||||
"code": 404,
|
||||
"description": "The specified object does not have a ObjectLock configuration."
|
||||
},
|
||||
"NoSuchWebsiteConfiguration": {
|
||||
"code": 404,
|
||||
"description": "The specified bucket does not have a website configuration"
|
||||
|
@ -272,14 +268,6 @@
|
|||
"code": 404,
|
||||
"description": "The replication configuration was not found"
|
||||
},
|
||||
"ObjectLockConfigurationNotFoundError": {
|
||||
"code": 404,
|
||||
"description": "The object lock configuration was not found"
|
||||
},
|
||||
"ServerSideEncryptionConfigurationNotFoundError" : {
|
||||
"code": 404,
|
||||
"description": "The server side encryption configuration was not found"
|
||||
},
|
||||
"NotImplemented": {
|
||||
"code": 501,
|
||||
"description": "A header you provided implies functionality that is not implemented."
|
||||
|
@ -475,22 +463,6 @@
|
|||
"code": 400,
|
||||
"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"
|
||||
},
|
||||
"ReportExpired": {
|
||||
"code": 410,
|
||||
"description": "The request was rejected because the most recent credential report has expired. To generate a new credential report, use GenerateCredentialReport."
|
||||
},
|
||||
"ReportInProgress": {
|
||||
"code": 404,
|
||||
"description": "The request was rejected because the credential report is still being generated."
|
||||
},
|
||||
"ReportNotPresent": {
|
||||
"code": 410,
|
||||
"description": "The request was rejected because the credential report does not exist. To generate a credential report, use GenerateCredentialReport."
|
||||
},
|
||||
"_comment": "-------------- Special non-AWS S3 errors --------------",
|
||||
"MPUinProgress": {
|
||||
"code": 409,
|
||||
|
|
9
index.js
9
index.js
|
@ -64,8 +64,6 @@ module.exports = {
|
|||
ProbeServer: require('./lib/network/probe/ProbeServer'),
|
||||
},
|
||||
RoundRobin: require('./lib/network/RoundRobin'),
|
||||
kmip: require('./lib/network/kmip'),
|
||||
kmipClient: require('./lib/network/kmip/Client'),
|
||||
},
|
||||
s3routes: {
|
||||
routes: require('./lib/s3routes/routes'),
|
||||
|
@ -75,7 +73,6 @@ module.exports = {
|
|||
userMetadata: require('./lib/s3middleware/userMetadata'),
|
||||
convertToXml: require('./lib/s3middleware/convertToXml'),
|
||||
escapeForXml: require('./lib/s3middleware/escapeForXml'),
|
||||
objectLegalHold: require('./lib/s3middleware/objectLegalHold'),
|
||||
tagging: require('./lib/s3middleware/tagging'),
|
||||
validateConditionalHeaders:
|
||||
require('./lib/s3middleware/validateConditionalHeaders')
|
||||
|
@ -91,7 +88,6 @@ module.exports = {
|
|||
SubStreamInterface:
|
||||
require('./lib/s3middleware/azureHelpers/SubStreamInterface'),
|
||||
},
|
||||
retention: require('./lib/s3middleware/objectRetention'),
|
||||
},
|
||||
storage: {
|
||||
metadata: {
|
||||
|
@ -120,11 +116,6 @@ module.exports = {
|
|||
require('./lib/models/ReplicationConfiguration'),
|
||||
LifecycleConfiguration:
|
||||
require('./lib/models/LifecycleConfiguration'),
|
||||
BucketPolicy: require('./lib/models/BucketPolicy'),
|
||||
ObjectLockConfiguration:
|
||||
require('./lib/models/ObjectLockConfiguration'),
|
||||
NotificationConfiguration:
|
||||
require('./lib/models/NotificationConfiguration'),
|
||||
},
|
||||
metrics: {
|
||||
StatsClient: require('./lib/metrics/StatsClient'),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const Extension = require('./Extension').default;
|
||||
|
||||
const { checkLimit, FILTER_END, FILTER_ACCEPT, FILTER_SKIP } = require('./tools');
|
||||
const { checkLimit, FILTER_END, FILTER_ACCEPT } = require('./tools');
|
||||
const DEFAULT_MAX_KEYS = 10000;
|
||||
|
||||
/**
|
||||
|
@ -21,8 +21,6 @@ class List extends Extension {
|
|||
this.res = [];
|
||||
if (parameters) {
|
||||
this.maxKeys = checkLimit(parameters.maxKeys, DEFAULT_MAX_KEYS);
|
||||
this.filterKey = parameters.filterKey;
|
||||
this.filterKeyStartsWith = parameters.filterKeyStartsWith;
|
||||
} else {
|
||||
this.maxKeys = DEFAULT_MAX_KEYS;
|
||||
}
|
||||
|
@ -46,43 +44,6 @@ class List extends Extension {
|
|||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters customAttributes sub-object if present
|
||||
*
|
||||
* @param {String} value - The JSON value of a listing item
|
||||
*
|
||||
* @return {Boolean} Returns true if matches, else false.
|
||||
*/
|
||||
customFilter(value) {
|
||||
let _value;
|
||||
try {
|
||||
_value = JSON.parse(value);
|
||||
} catch (e) {
|
||||
// Prefer returning an unfiltered data rather than
|
||||
// stopping the service in case of parsing failure.
|
||||
// The risk of this approach is a potential
|
||||
// reproduction of MD-692, where too much memory is
|
||||
// used by repd.
|
||||
this.logger.warn(
|
||||
'Could not parse Object Metadata while listing',
|
||||
{ err: e.toString() });
|
||||
return false;
|
||||
}
|
||||
if (_value.customAttributes !== undefined) {
|
||||
for (const key of Object.keys(_value.customAttributes)) {
|
||||
if (this.filterKey !== undefined &&
|
||||
key === this.filterKey) {
|
||||
return true;
|
||||
}
|
||||
if (this.filterKeyStartsWith !== undefined &&
|
||||
key.startsWith(this.filterKeyStartsWith)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function apply on each element
|
||||
* Just add it to the array
|
||||
|
@ -95,12 +56,6 @@ class List extends Extension {
|
|||
if (this.keys >= this.maxKeys) {
|
||||
return FILTER_END;
|
||||
}
|
||||
if ((this.filterKey !== undefined ||
|
||||
this.filterKeyStartsWith !== undefined) &&
|
||||
typeof elem === 'object' &&
|
||||
!this.customFilter(elem.value)) {
|
||||
return FILTER_SKIP;
|
||||
}
|
||||
if (typeof elem === 'object') {
|
||||
this.res.push({
|
||||
key: elem.key,
|
||||
|
|
|
@ -222,40 +222,6 @@ class Vault {
|
|||
});
|
||||
}
|
||||
|
||||
/** getAccountIds -- call Vault to get accountIds based on
|
||||
* canonicalIDs
|
||||
* @param {array} canonicalIDs - list of canonicalIDs
|
||||
* @param {object} log - log object
|
||||
* @param {function} callback - callback with either error or an object
|
||||
* with canonicalID keys and accountId values
|
||||
* @return {undefined}
|
||||
*/
|
||||
getAccountIds(canonicalIDs, log, callback) {
|
||||
log.trace('getting accountIds from Vault based on canonicalIDs',
|
||||
{ canonicalIDs });
|
||||
this.client.getAccountIds(canonicalIDs,
|
||||
{ reqUid: log.getSerializedUids() },
|
||||
(err, info) => {
|
||||
if (err) {
|
||||
log.debug('received error message from vault',
|
||||
{ errorMessage: err });
|
||||
return callback(err);
|
||||
}
|
||||
const infoFromVault = info.message.body;
|
||||
log.trace('info received from vault', { infoFromVault });
|
||||
const result = {};
|
||||
/* If the accountId was not found in Vault, do not
|
||||
send the canonicalID back to the API */
|
||||
Object.keys(infoFromVault).forEach(key => {
|
||||
if (infoFromVault[key] !== 'NotFound' &&
|
||||
infoFromVault[key] !== 'WrongFormat') {
|
||||
result[key] = infoFromVault[key];
|
||||
}
|
||||
});
|
||||
return callback(null, result);
|
||||
});
|
||||
}
|
||||
|
||||
/** checkPolicies -- call Vault to evaluate policies
|
||||
* @param {object} requestContextParams - parameters needed to construct
|
||||
* requestContext in Vault
|
||||
|
|
|
@ -157,34 +157,6 @@ class Backend {
|
|||
};
|
||||
return cb(null, vaultReturnObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets accountIds for a list of accounts based on
|
||||
* the canonical IDs associated with the account
|
||||
* @param {array} canonicalIDs - list of canonicalIDs
|
||||
* @param {object} options - to send log id to vault
|
||||
* @param {function} cb - callback to calling function
|
||||
* @returns {function} callback with either error or
|
||||
* an object from Vault containing account canonicalID
|
||||
* as each object key and an accountId as the value (or "NotFound")
|
||||
*/
|
||||
getAccountIds(canonicalIDs, options, cb) {
|
||||
const results = {};
|
||||
canonicalIDs.forEach(canonicalID => {
|
||||
const foundEntity = this.indexer.getEntityByCanId(canonicalID);
|
||||
if (!foundEntity || !foundEntity.shortid) {
|
||||
results[canonicalID] = 'Not Found';
|
||||
} else {
|
||||
results[canonicalID] = foundEntity.shortid;
|
||||
}
|
||||
});
|
||||
const vaultReturnObject = {
|
||||
message: {
|
||||
body: results,
|
||||
},
|
||||
};
|
||||
return cb(null, vaultReturnObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -34,13 +34,8 @@ function check(request, log, data) {
|
|||
}
|
||||
|
||||
const currentTime = Date.now();
|
||||
|
||||
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) {
|
||||
// 604800000 ms (seven days).
|
||||
if (expirationTime > currentTime + 604800000) {
|
||||
log.debug('expires parameter too far in future',
|
||||
{ expires: request.query.Expires });
|
||||
return { err: errors.AccessDenied };
|
||||
|
|
|
@ -72,22 +72,6 @@ module.exports = {
|
|||
permittedCapitalizedBuckets: {
|
||||
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/,
|
||||
supportedNotificationEvents: new Set([
|
||||
's3:ObjectCreated:*',
|
||||
's3:ObjectCreated:Put',
|
||||
's3:ObjectCreated:Copy',
|
||||
's3:ObjectCreated:CompleteMultipartUpload',
|
||||
's3:ObjectRemoved:*',
|
||||
's3:ObjectRemoved:Delete',
|
||||
's3:ObjectRemoved:DeleteMarkerCreated',
|
||||
]),
|
||||
notificationArnPrefix: 'arn:scality:bucketnotif',
|
||||
// HTTP server keep-alive timeout is set to a higher value than
|
||||
// client's free sockets timeout to avoid the risk of triggering
|
||||
// ECONNRESET errors if the server closes the connection at the
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
const assert = require('assert');
|
||||
const uuid = require('uuid/v4');
|
||||
|
||||
const { WebsiteConfiguration } = require('./WebsiteConfiguration');
|
||||
const ReplicationConfiguration = require('./ReplicationConfiguration');
|
||||
const LifecycleConfiguration = require('./LifecycleConfiguration');
|
||||
const ObjectLockConfiguration = require('./ObjectLockConfiguration');
|
||||
const BucketPolicy = require('./BucketPolicy');
|
||||
const NotificationConfiguration = require('./NotificationConfiguration');
|
||||
|
||||
// WHEN UPDATING THIS NUMBER, UPDATE BucketInfoModelVersion.md CHANGELOG
|
||||
// BucketInfoModelVersion.md can be found in the root of this repository
|
||||
const modelVersion = 10;
|
||||
// WHEN UPDATING THIS NUMBER, UPDATE MODELVERSION.MD CHANGELOG
|
||||
const modelVersion = 6;
|
||||
|
||||
class BucketInfo {
|
||||
/**
|
||||
|
@ -33,8 +27,6 @@ class BucketInfo {
|
|||
* algorithm to use
|
||||
* @param {string} serverSideEncryption.masterKeyId -
|
||||
* key to get master key
|
||||
* @param {string} serverSideEncryption.configuredMasterKeyId -
|
||||
* custom KMS key id specified by user
|
||||
* @param {boolean} serverSideEncryption.mandatory -
|
||||
* true for mandatory encryption
|
||||
* bucket has been made
|
||||
|
@ -55,19 +47,12 @@ class BucketInfo {
|
|||
* @param {string[]} [cors[].exposeHeaders] - headers expose to applications
|
||||
* @param {object} [replicationConfiguration] - replication configuration
|
||||
* @param {object} [lifecycleConfiguration] - lifecycle configuration
|
||||
* @param {object} [bucketPolicy] - bucket policy
|
||||
* @param {string} [uid] - unique identifier for the bucket, necessary
|
||||
* @param {boolean} [objectLockEnabled] - true when object lock enabled
|
||||
* @param {object} [objectLockConfiguration] - object lock configuration
|
||||
* @param {object} [notificationConfiguration] - bucket notification configuration
|
||||
*/
|
||||
constructor(name, owner, ownerDisplayName, creationDate,
|
||||
mdBucketModelVersion, acl, transient, deleted,
|
||||
serverSideEncryption, versioningConfiguration,
|
||||
locationConstraint, websiteConfiguration, cors,
|
||||
replicationConfiguration, lifecycleConfiguration,
|
||||
bucketPolicy, uid, objectLockEnabled, objectLockConfiguration,
|
||||
notificationConfiguration) {
|
||||
replicationConfiguration, lifecycleConfiguration) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof owner, 'string');
|
||||
assert.strictEqual(typeof ownerDisplayName, 'string');
|
||||
|
@ -85,15 +70,12 @@ class BucketInfo {
|
|||
}
|
||||
if (serverSideEncryption) {
|
||||
assert.strictEqual(typeof serverSideEncryption, 'object');
|
||||
const { cryptoScheme, algorithm, masterKeyId,
|
||||
configuredMasterKeyId, mandatory } = serverSideEncryption;
|
||||
const { cryptoScheme, algorithm, masterKeyId, mandatory } =
|
||||
serverSideEncryption;
|
||||
assert.strictEqual(typeof cryptoScheme, 'number');
|
||||
assert.strictEqual(typeof algorithm, 'string');
|
||||
assert.strictEqual(typeof masterKeyId, 'string');
|
||||
assert.strictEqual(typeof mandatory, 'boolean');
|
||||
if (configuredMasterKeyId !== undefined) {
|
||||
assert.strictEqual(typeof configuredMasterKeyId, 'string');
|
||||
}
|
||||
}
|
||||
if (versioningConfiguration) {
|
||||
assert.strictEqual(typeof versioningConfiguration, 'object');
|
||||
|
@ -130,19 +112,6 @@ class BucketInfo {
|
|||
if (lifecycleConfiguration) {
|
||||
LifecycleConfiguration.validateConfig(lifecycleConfiguration);
|
||||
}
|
||||
if (bucketPolicy) {
|
||||
BucketPolicy.validatePolicy(bucketPolicy);
|
||||
}
|
||||
if (uid) {
|
||||
assert.strictEqual(typeof uid, 'string');
|
||||
assert.strictEqual(uid.length, 36);
|
||||
}
|
||||
if (objectLockConfiguration) {
|
||||
ObjectLockConfiguration.validateConfig(objectLockConfiguration);
|
||||
}
|
||||
if (notificationConfiguration) {
|
||||
NotificationConfiguration.validateConfig(notificationConfiguration);
|
||||
}
|
||||
const aclInstance = acl || {
|
||||
Canned: 'private',
|
||||
FULL_CONTROL: [],
|
||||
|
@ -168,11 +137,6 @@ class BucketInfo {
|
|||
this._replicationConfiguration = replicationConfiguration || null;
|
||||
this._cors = cors || null;
|
||||
this._lifecycleConfiguration = lifecycleConfiguration || null;
|
||||
this._bucketPolicy = bucketPolicy || null;
|
||||
this._uid = uid || uuid();
|
||||
this._objectLockEnabled = objectLockEnabled || false;
|
||||
this._objectLockConfiguration = objectLockConfiguration || null;
|
||||
this._notificationConfiguration = notificationConfiguration || null;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
|
@ -196,11 +160,6 @@ class BucketInfo {
|
|||
cors: this._cors,
|
||||
replicationConfiguration: this._replicationConfiguration,
|
||||
lifecycleConfiguration: this._lifecycleConfiguration,
|
||||
bucketPolicy: this._bucketPolicy,
|
||||
uid: this._uid,
|
||||
objectLockEnabled: this._objectLockEnabled,
|
||||
objectLockConfiguration: this._objectLockConfiguration,
|
||||
notificationConfiguration: this._notificationConfiguration,
|
||||
};
|
||||
if (this._websiteConfiguration) {
|
||||
bucketInfos.websiteConfiguration =
|
||||
|
@ -221,9 +180,7 @@ class BucketInfo {
|
|||
obj.creationDate, obj.mdBucketModelVersion, obj.acl,
|
||||
obj.transient, obj.deleted, obj.serverSideEncryption,
|
||||
obj.versioningConfiguration, obj.locationConstraint, websiteConfig,
|
||||
obj.cors, obj.replicationConfiguration, obj.lifecycleConfiguration,
|
||||
obj.bucketPolicy, obj.uid, obj.objectLockEnabled,
|
||||
obj.objectLockConfiguration, obj.notificationConfiguration);
|
||||
obj.cors, obj.replicationConfiguration, obj.lifecycleConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,9 +203,7 @@ class BucketInfo {
|
|||
data._transient, data._deleted, data._serverSideEncryption,
|
||||
data._versioningConfiguration, data._locationConstraint,
|
||||
data._websiteConfiguration, data._cors,
|
||||
data._replicationConfiguration, data._lifecycleConfiguration,
|
||||
data._bucketPolicy, data._uid, data._objectLockEnabled,
|
||||
data._objectLockConfiguration, data._notificationConfiguration);
|
||||
data._replicationConfiguration, data._lifecycleConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -376,57 +331,6 @@ class BucketInfo {
|
|||
this._lifecycleConfiguration = lifecycleConfiguration;
|
||||
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 notification configuration
|
||||
* @return {object|null} notification configuration information or 'null' if
|
||||
* the bucket does not have a notification configuration
|
||||
*/
|
||||
getNotificationConfiguration() {
|
||||
return this._notificationConfiguration;
|
||||
}
|
||||
/**
|
||||
* Set notification configuraiton
|
||||
* @param {object} notificationConfiguration - bucket notification information
|
||||
* @return {BucketInfo} - bucket info instance
|
||||
*/
|
||||
setNotificationConfiguration(notificationConfiguration) {
|
||||
this._notificationConfiguration = notificationConfiguration;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Get cors resource
|
||||
* @return {object[]} cors
|
||||
|
@ -617,38 +521,6 @@ class BucketInfo {
|
|||
return this._versioningConfiguration &&
|
||||
this._versioningConfiguration.Status === 'Enabled';
|
||||
}
|
||||
/**
|
||||
* Get unique id of bucket.
|
||||
* @return {string} - unique id
|
||||
*/
|
||||
getUid() {
|
||||
return this._uid;
|
||||
}
|
||||
/**
|
||||
* Set unique id of bucket.
|
||||
* @param {string} uid - unique identifier for the bucket
|
||||
* @return {BucketInfo} - bucket info instance
|
||||
*/
|
||||
setUid(uid) {
|
||||
this._uid = uid;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Check if object lock is enabled.
|
||||
* @return {boolean} - depending on whether object lock is enabled
|
||||
*/
|
||||
isObjectLockEnabled() {
|
||||
return !!this._objectLockEnabled;
|
||||
}
|
||||
/**
|
||||
* Set the value of objectLockEnabled field.
|
||||
* @param {boolean} enabled - true if object lock enabled else false.
|
||||
* @return {BucketInfo} - bucket info instance
|
||||
*/
|
||||
setObjectLockEnabled(enabled) {
|
||||
this._objectLockEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BucketInfo;
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
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;
|
|
@ -148,36 +148,6 @@ class LifecycleConfiguration {
|
|||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the prefix is valid
|
||||
* @param {string} prefix - The prefix to check
|
||||
* @return {object|null} - The error or null
|
||||
*/
|
||||
_checkPrefix(prefix) {
|
||||
if (prefix.length > 1024) {
|
||||
const msg = 'The maximum size of a prefix is 1024';
|
||||
return errors.InvalidRequest.customizeDescription(msg);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the prefix of the config
|
||||
* @param {string} prefix - The prefix to parse
|
||||
* @return {object} - Contains error if parsing returned an error, otherwise
|
||||
* it contains the parsed rule object
|
||||
*/
|
||||
_parsePrefix(prefix) {
|
||||
const error = this._checkPrefix(prefix);
|
||||
if (error) {
|
||||
return { error };
|
||||
}
|
||||
return {
|
||||
propName: 'prefix',
|
||||
prefix,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that each xml rule is valid
|
||||
* @param {object} rule - a rule object from Rule array from this._parsedXml
|
||||
|
@ -231,18 +201,14 @@ class LifecycleConfiguration {
|
|||
customizeDescription('Rule xml does not include Status');
|
||||
return ruleObj;
|
||||
}
|
||||
const subFilter = rule.Filter ? rule.Filter[0] : rule.Prefix;
|
||||
|
||||
const id = this._parseID(rule.ID);
|
||||
const status = this._parseStatus(rule.Status[0]);
|
||||
const filter = this._parseFilter(subFilter);
|
||||
const actions = this._parseAction(rule);
|
||||
const rulePropArray = [id, status, actions];
|
||||
if (rule.Prefix) {
|
||||
// Backward compatibility with deprecated top-level prefix.
|
||||
const prefix = this._parsePrefix(rule.Prefix[0]);
|
||||
rulePropArray.push(prefix);
|
||||
} else if (rule.Filter) {
|
||||
const filter = this._parseFilter(rule.Filter[0]);
|
||||
rulePropArray.push(filter);
|
||||
}
|
||||
|
||||
const rulePropArray = [id, status, filter, actions];
|
||||
for (let i = 0; i < rulePropArray.length; i++) {
|
||||
const prop = rulePropArray[i];
|
||||
if (prop.error) {
|
||||
|
@ -252,11 +218,7 @@ class LifecycleConfiguration {
|
|||
const propName = prop.propName;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete prop.propName;
|
||||
if (prop[propName] !== undefined) {
|
||||
ruleObj[propName] = prop[propName];
|
||||
} else {
|
||||
ruleObj[propName] = prop;
|
||||
}
|
||||
ruleObj[propName] = prop[propName] || prop;
|
||||
}
|
||||
}
|
||||
return ruleObj;
|
||||
|
@ -288,14 +250,12 @@ class LifecycleConfiguration {
|
|||
_parseFilter(filter) {
|
||||
const filterObj = {};
|
||||
filterObj.propName = 'filter';
|
||||
// if no Rule Prefix or Filter, rulePrefix is empty string
|
||||
filterObj.rulePrefix = '';
|
||||
if (Array.isArray(filter)) {
|
||||
// if Prefix was included, not Filter, filter will be Prefix array
|
||||
// if more than one Prefix is included, we ignore all but the last
|
||||
filterObj.rulePrefix = filter[filter.length - 1];
|
||||
const error = this._checkPrefix(filterObj.rulePrefix);
|
||||
if (error) {
|
||||
filterObj.error = error;
|
||||
}
|
||||
filterObj.rulePrefix = filter.pop();
|
||||
return filterObj;
|
||||
}
|
||||
if (filter.And && (filter.Prefix || filter.Tag) ||
|
||||
|
@ -305,15 +265,11 @@ class LifecycleConfiguration {
|
|||
return filterObj;
|
||||
}
|
||||
if (filter.Prefix) {
|
||||
filterObj.rulePrefix = filter.Prefix[filter.Prefix.length - 1];
|
||||
const error = this._checkPrefix(filterObj.rulePrefix);
|
||||
if (error) {
|
||||
filterObj.error = error;
|
||||
}
|
||||
filterObj.rulePrefix = filter.Prefix.pop();
|
||||
return filterObj;
|
||||
}
|
||||
if (filter.Tag) {
|
||||
const tagObj = this._parseTags(filter.Tag);
|
||||
const tagObj = this._parseTags(filter.Tag[0]);
|
||||
if (tagObj.error) {
|
||||
filterObj.error = tagObj.error;
|
||||
return filterObj;
|
||||
|
@ -329,14 +285,9 @@ class LifecycleConfiguration {
|
|||
return filterObj;
|
||||
}
|
||||
if (andF.Prefix && andF.Prefix.length >= 1) {
|
||||
filterObj.rulePrefix = andF.Prefix[andF.Prefix.length - 1];
|
||||
const error = this._checkPrefix(filterObj.rulePrefix);
|
||||
if (error) {
|
||||
filterObj.error = error;
|
||||
return filterObj;
|
||||
}
|
||||
filterObj.rulePrefix = andF.Prefix.pop();
|
||||
}
|
||||
const tagObj = this._parseTags(andF.Tag);
|
||||
const tagObj = this._parseTags(andF.Tag[0]);
|
||||
if (tagObj.error) {
|
||||
filterObj.error = tagObj.error;
|
||||
return filterObj;
|
||||
|
@ -369,33 +320,31 @@ class LifecycleConfiguration {
|
|||
// reset _tagKeys to empty because keys cannot overlap within a rule,
|
||||
// but different rules can have the same tag keys
|
||||
this._tagKeys = [];
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
if (!tags[i].Key || !tags[i].Value) {
|
||||
tagObj.error =
|
||||
errors.MissingRequiredParameter.customizeDescription(
|
||||
'Tag XML does not contain both Key and Value');
|
||||
break;
|
||||
}
|
||||
|
||||
if (tags[i].Key[0].length < 1 || tags[i].Key[0].length > 128) {
|
||||
if (!tags.Key || !tags.Value) {
|
||||
tagObj.error = errors.MissingRequiredParameter.customizeDescription(
|
||||
'Tag XML does not contain both Key and Value');
|
||||
return tagObj;
|
||||
}
|
||||
if (tags.Key.length !== tags.Value.length) {
|
||||
tagObj.error = errors.MalformedXML.customizeDescription(
|
||||
'Tag XML should contain same number of Keys and Values');
|
||||
return tagObj;
|
||||
}
|
||||
for (let i = 0; i < tags.Key.length; i++) {
|
||||
if (tags.Key[i].length < 1 || tags.Key[i].length > 128) {
|
||||
tagObj.error = errors.InvalidRequest.customizeDescription(
|
||||
'A Tag\'s Key must be a length between 1 and 128');
|
||||
'Tag Key must be a length between 1 and 128 char');
|
||||
break;
|
||||
}
|
||||
if (tags[i].Value[0].length < 0 || tags[i].Value[0].length > 256) {
|
||||
tagObj.error = errors.InvalidRequest.customizeDescription(
|
||||
'A Tag\'s Value must be a length between 0 and 256');
|
||||
break;
|
||||
}
|
||||
if (this._tagKeys.includes(tags[i].Key[0])) {
|
||||
if (this._tagKeys.includes(tags.Key[i])) {
|
||||
tagObj.error = errors.InvalidRequest.customizeDescription(
|
||||
'Tag Keys must be unique');
|
||||
break;
|
||||
}
|
||||
this._tagKeys.push(tags[i].Key[0]);
|
||||
this._tagKeys.push(tags.Key[i]);
|
||||
const tag = {
|
||||
key: tags[i].Key[0],
|
||||
val: tags[i].Value[0],
|
||||
key: tags.Key[i],
|
||||
val: tags.Value[i],
|
||||
};
|
||||
tagObj.tags.push(tag);
|
||||
}
|
||||
|
@ -682,24 +631,20 @@ class LifecycleConfiguration {
|
|||
const rules = config.rules;
|
||||
assert.strictEqual(Array.isArray(rules), true);
|
||||
rules.forEach(rule => {
|
||||
const { ruleID, ruleStatus, prefix, filter, actions } = rule;
|
||||
const { ruleID, ruleStatus, filter, actions } = rule;
|
||||
assert.strictEqual(typeof ruleID, 'string');
|
||||
assert.strictEqual(typeof ruleStatus, 'string');
|
||||
if (prefix !== undefined) {
|
||||
assert.strictEqual(typeof prefix, 'string');
|
||||
} else {
|
||||
assert.strictEqual(typeof filter, 'object');
|
||||
assert.strictEqual(Array.isArray(actions), true);
|
||||
if (filter.rulePrefix) {
|
||||
assert.strictEqual(typeof filter.rulePrefix, 'string');
|
||||
}
|
||||
if (filter.tags) {
|
||||
assert.strictEqual(Array.isArray(filter.tags), true);
|
||||
filter.tags.forEach(t => {
|
||||
assert.strictEqual(typeof t.key, 'string');
|
||||
assert.strictEqual(typeof t.val, 'string');
|
||||
});
|
||||
}
|
||||
assert.strictEqual(typeof filter, 'object');
|
||||
assert.strictEqual(Array.isArray(actions), true);
|
||||
if (filter.rulePrefix) {
|
||||
assert.strictEqual(typeof filter.rulePrefix, 'string');
|
||||
}
|
||||
if (filter.tags) {
|
||||
assert.strictEqual(Array.isArray(filter.tags), true);
|
||||
filter.tags.forEach(t => {
|
||||
assert.strictEqual(typeof t.key, 'string');
|
||||
assert.strictEqual(typeof t.val, 'string');
|
||||
});
|
||||
}
|
||||
actions.forEach(a => {
|
||||
assert.strictEqual(typeof a.actionName, 'string');
|
||||
|
@ -724,36 +669,26 @@ class LifecycleConfiguration {
|
|||
static getConfigXml(config) {
|
||||
const rules = config.rules;
|
||||
const rulesXML = rules.map(rule => {
|
||||
const { ruleID, ruleStatus, filter, actions, prefix } = rule;
|
||||
const { ruleID, ruleStatus, filter, actions } = rule;
|
||||
const ID = `<ID>${ruleID}</ID>`;
|
||||
const Status = `<Status>${ruleStatus}</Status>`;
|
||||
let rulePrefix;
|
||||
if (prefix !== undefined) {
|
||||
rulePrefix = prefix;
|
||||
} else {
|
||||
rulePrefix = filter.rulePrefix;
|
||||
}
|
||||
const tags = filter && filter.tags;
|
||||
const Prefix = rulePrefix !== undefined ?
|
||||
`<Prefix>${rulePrefix}</Prefix>` : '';
|
||||
|
||||
const { rulePrefix, tags } = filter;
|
||||
const Prefix = rulePrefix ? `<Prefix>${rulePrefix}</Prefix>` : '';
|
||||
let tagXML = '';
|
||||
if (tags) {
|
||||
tagXML = tags.map(t => {
|
||||
const keysVals = tags.map(t => {
|
||||
const { key, val } = t;
|
||||
const Tag = `<Tag><Key>${key}</Key>` +
|
||||
`<Value>${val}</Value></Tag>`;
|
||||
const Tag = `<Key>${key}</Key>` +
|
||||
`<Value>${val}</Value>`;
|
||||
return Tag;
|
||||
}).join('');
|
||||
tagXML = `<Tag>${keysVals}</Tag>`;
|
||||
}
|
||||
let Filter;
|
||||
if (prefix !== undefined) {
|
||||
// Prefix is in the top-level of the config, so we can skip the
|
||||
// filter property.
|
||||
if (rulePrefix && !tags) {
|
||||
Filter = Prefix;
|
||||
} else if (filter.rulePrefix !== undefined && !tags) {
|
||||
Filter = `<Filter>${Prefix}</Filter>`;
|
||||
} else if (tags &&
|
||||
(filter.rulePrefix !== undefined || tags.length > 1)) {
|
||||
} else if (tags && (rulePrefix || tags.length > 1)) {
|
||||
Filter = `<Filter><And>${Prefix}${tagXML}</And></Filter>`;
|
||||
} else {
|
||||
// remaining condition is if only one or no tag
|
||||
|
|
|
@ -1,311 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const UUID = require('uuid');
|
||||
|
||||
const {
|
||||
supportedNotificationEvents,
|
||||
notificationArnPrefix,
|
||||
} = require('../constants');
|
||||
const errors = require('../errors');
|
||||
|
||||
/**
|
||||
* Format of xml request:
|
||||
*
|
||||
* <ONotificationConfiguration>
|
||||
* <QueueConfiguration>
|
||||
* <Event>array</Event>
|
||||
* <Filter>
|
||||
* <S3Key>
|
||||
* <FilterRule>
|
||||
* <Name>string</Name>
|
||||
* <Value>string</Value>
|
||||
* </FilterRule>
|
||||
* </S3Key>
|
||||
* </Filter>
|
||||
* <Id>string</Id>
|
||||
* <Queue>string</Queue>
|
||||
* </QueueConfiguration>
|
||||
* </NotificationConfiguration>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Format of config:
|
||||
*
|
||||
* config = {
|
||||
* queueConfig: [
|
||||
* {
|
||||
* events: array,
|
||||
* queueArn: string,
|
||||
* filterRules: [
|
||||
* {
|
||||
* name: string,
|
||||
* value: string
|
||||
* },
|
||||
* {
|
||||
* name: string,
|
||||
* value:string
|
||||
* },
|
||||
* ],
|
||||
* id: string
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
class NotificationConfiguration {
|
||||
/**
|
||||
* Create a Notification Configuration instance
|
||||
* @param {string} xml - parsed configuration xml
|
||||
* @return {object} - NotificationConfiguration instance
|
||||
*/
|
||||
constructor(xml) {
|
||||
this._parsedXml = xml;
|
||||
this._config = {};
|
||||
this._ids = new Set([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification configuration
|
||||
* @return {object} - contains error if parsing failed
|
||||
*/
|
||||
getValidatedNotificationConfiguration() {
|
||||
const validationError = this._parseNotificationConfig();
|
||||
if (validationError) {
|
||||
this._config.error = validationError;
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that notification configuration is valid
|
||||
* @return {error | null} - error if parsing failed, else undefined
|
||||
*/
|
||||
_parseNotificationConfig() {
|
||||
if (!this._parsedXml || this._parsedXml === '') {
|
||||
return errors.MalformedXML.customizeDescription(
|
||||
'request xml is undefined or empty');
|
||||
}
|
||||
const notificationConfig = this._parsedXml.NotificationConfiguration;
|
||||
if (!notificationConfig || notificationConfig === '') {
|
||||
return errors.MalformedXML.customizeDescription(
|
||||
'request xml does not include NotificationConfiguration');
|
||||
}
|
||||
const queueConfig = notificationConfig.QueueConfiguration;
|
||||
if (!queueConfig || !queueConfig[0]) {
|
||||
// if undefined or empty QueueConfiguration, notif configuration is deleted
|
||||
return null;
|
||||
}
|
||||
this._config.queueConfig = [];
|
||||
let parseError;
|
||||
for (let i = 0; i < queueConfig.length; i++) {
|
||||
const eventObj = this._parseEvents(queueConfig[i].Event);
|
||||
const filterObj = this._parseFilter(queueConfig[i].Filter);
|
||||
const idObj = this._parseId(queueConfig[i].Id);
|
||||
const arnObj = this._parseArn(queueConfig[i].Queue);
|
||||
|
||||
if (eventObj.error) {
|
||||
parseError = eventObj.error;
|
||||
this._config = {};
|
||||
break;
|
||||
}
|
||||
if (filterObj.error) {
|
||||
parseError = filterObj.error;
|
||||
this._config = {};
|
||||
break;
|
||||
}
|
||||
if (idObj.error) {
|
||||
parseError = idObj.error;
|
||||
this._config = {};
|
||||
break;
|
||||
}
|
||||
if (arnObj.error) {
|
||||
parseError = arnObj.error;
|
||||
this._config = {};
|
||||
break;
|
||||
}
|
||||
this._config.queueConfig.push({
|
||||
events: eventObj.events,
|
||||
queueArn: arnObj.arn,
|
||||
id: idObj.id,
|
||||
filterRules: filterObj.filterRules,
|
||||
});
|
||||
}
|
||||
return parseError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that events array is valid
|
||||
* @param {array} events - event array
|
||||
* @return {object} - contains error if parsing failed or events array
|
||||
*/
|
||||
_parseEvents(events) {
|
||||
const eventsObj = {
|
||||
events: [],
|
||||
};
|
||||
if (!events || !events[0]) {
|
||||
eventsObj.error = errors.MalformedXML.customizeDescription(
|
||||
'each queue configuration must contain an event');
|
||||
return eventsObj;
|
||||
}
|
||||
events.forEach(e => {
|
||||
if (!supportedNotificationEvents.has(e)) {
|
||||
eventsObj.error = errors.MalformedXML.customizeDescription(
|
||||
'event array contains invalid or unsupported event');
|
||||
} else {
|
||||
eventsObj.events.push(e);
|
||||
}
|
||||
});
|
||||
return eventsObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that filter array is valid
|
||||
* @param {array} filter - filter array
|
||||
* @return {object} - contains error if parsing failed or filter array
|
||||
*/
|
||||
_parseFilter(filter) {
|
||||
if (!filter || !filter[0]) {
|
||||
return {};
|
||||
}
|
||||
if (!filter[0].S3Key || !filter[0].S3Key[0]) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'if included, queue configuration filter must contain S3Key') };
|
||||
}
|
||||
const filterRules = filter[0].S3Key[0];
|
||||
if (!filterRules.FilterRule || !filterRules.FilterRule[0]) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'if included, queue configuration filter must contain a rule') };
|
||||
}
|
||||
const filterObj = {
|
||||
filterRules: [],
|
||||
};
|
||||
const ruleArray = filterRules.FilterRule;
|
||||
for (let i = 0; i < ruleArray.length; i++) {
|
||||
if (!ruleArray[i].Name
|
||||
|| !ruleArray[i].Name[0]
|
||||
|| !ruleArray[i].Value
|
||||
|| !ruleArray[i].Value[0]) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'each included filter must contain a name and value') };
|
||||
}
|
||||
if (!['Prefix', 'Suffix'].includes(ruleArray[i].Name[0])) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'filter Name must be one of Prefix or Suffix') };
|
||||
}
|
||||
filterObj.filterRules.push({
|
||||
name: ruleArray[i].Name[0],
|
||||
value: ruleArray[i].Value[0],
|
||||
});
|
||||
}
|
||||
return filterObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that id string is valid
|
||||
* @param {string} id - id string (optional)
|
||||
* @return {object} - contains error if parsing failed or id
|
||||
*/
|
||||
_parseId(id) {
|
||||
if (id && id[0].length > 255) {
|
||||
return { error: errors.InvalidArgument.customizeDescription(
|
||||
'queue configuration ID is greater than 255 characters long') };
|
||||
}
|
||||
let validId;
|
||||
if (!id || !id[0]) {
|
||||
// id is optional property, so create one if not provided or is ''
|
||||
// We generate 48-character alphanumeric, unique id for rule
|
||||
validId = Buffer.from(UUID.v4()).toString('base64');
|
||||
} else {
|
||||
validId = id[0];
|
||||
}
|
||||
// Each ID in a list of rules must be unique.
|
||||
if (this._ids.has(validId)) {
|
||||
return { error: errors.InvalidRequest.customizeDescription(
|
||||
'queue configuration ID must be unique') };
|
||||
}
|
||||
this._ids.add(validId);
|
||||
return { id: validId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that arn string is valid
|
||||
* @param {string} arn - queue arn
|
||||
* @return {object} - contains error if parsing failed or queue arn
|
||||
*/
|
||||
_parseArn(arn) {
|
||||
if (!arn || !arn[0]) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'each queue configuration must contain a queue arn'),
|
||||
};
|
||||
}
|
||||
const splitArn = arn[0].split(':');
|
||||
const slicedArn = arn[0].slice(0, 23);
|
||||
if (splitArn.length !== 6 || slicedArn !== notificationArnPrefix) {
|
||||
return { error: errors.MalformedXML.customizeDescription(
|
||||
'queue arn is invalid') };
|
||||
}
|
||||
// remaining 3 parts of arn are evaluated in cloudserver
|
||||
return { arn: arn[0] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XML representation of notification configuration object
|
||||
* @param {object} config - notification configuration object
|
||||
* @return {string} - XML representation of config
|
||||
*/
|
||||
static getConfigXML(config) {
|
||||
const xmlArray = [];
|
||||
if (config && config.queueConfig) {
|
||||
config.queueConfig.forEach(c => {
|
||||
xmlArray.push('<QueueConfiguration>');
|
||||
xmlArray.push(`<Id>${c.id}</Id>`);
|
||||
xmlArray.push(`<Queue>${c.queueArn}</Queue>`);
|
||||
c.events.forEach(e => {
|
||||
xmlArray.push(`<Event>${e}</Event>`);
|
||||
});
|
||||
if (c.filterRules) {
|
||||
xmlArray.push('<Filter><S3Key>');
|
||||
c.filterRules.forEach(r => {
|
||||
xmlArray.push(`<FilterRule><Name>${r.name}</Name>` +
|
||||
`<Value>${r.value}</Value></FilterRule>`);
|
||||
});
|
||||
xmlArray.push('</S3Key></Filter>');
|
||||
}
|
||||
xmlArray.push('</QueueConfiguration>');
|
||||
});
|
||||
}
|
||||
const queueConfigXML = xmlArray.join('');
|
||||
return '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
'<NotificationConfiguration ' +
|
||||
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' +
|
||||
`${queueConfigXML}` +
|
||||
'</NotificationConfiguration>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the bucket metadata notification configuration structure and
|
||||
* value types
|
||||
* @param {object} config - The notificationconfiguration to validate
|
||||
* @return {undefined}
|
||||
*/
|
||||
static validateConfig(config) {
|
||||
assert.strictEqual(typeof config, 'object');
|
||||
if (!config.queueConfig) {
|
||||
return;
|
||||
}
|
||||
config.queueConfig.forEach(q => {
|
||||
const { events, queueArn, filterRules, id } = q;
|
||||
events.forEach(e => assert.strictEqual(typeof e, 'string'));
|
||||
assert.strictEqual(typeof queueArn, 'string');
|
||||
if (filterRules) {
|
||||
filterRules.forEach(f => {
|
||||
assert.strictEqual(typeof f.name, 'string');
|
||||
assert.strictEqual(typeof f.value, 'string');
|
||||
});
|
||||
}
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotificationConfiguration;
|
|
@ -1,238 +0,0 @@
|
|||
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) {
|
||||
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;
|
||||
if (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;
|
|
@ -125,7 +125,6 @@ class ObjectMD {
|
|||
dataStoreVersionId: '',
|
||||
},
|
||||
'dataStoreName': '',
|
||||
'originOp': '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -905,78 +904,6 @@ class ObjectMD {
|
|||
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 mode
|
||||
* @param {string} mode - should be one of 'GOVERNANCE', 'COMPLIANCE'
|
||||
* @return {ObjectMD} itself
|
||||
*/
|
||||
setRetentionMode(mode) {
|
||||
this._data.retentionMode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object retention retain until date
|
||||
* @param {string} date - date in ISO-8601 format
|
||||
* @return {ObjectMD} itself
|
||||
*/
|
||||
setRetentionDate(date) {
|
||||
this._data.retentionDate = date;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object retention mode
|
||||
* @return {string} retention mode string
|
||||
*/
|
||||
getRetentionMode() {
|
||||
return this._data.retentionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object retention retain until date
|
||||
* @return {string} retention date string
|
||||
*/
|
||||
getRetentionDate() {
|
||||
return this._data.retentionDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set origin operation for object
|
||||
* @param {string} op - name of origin operation
|
||||
* @return {ObjectMD} itself
|
||||
*/
|
||||
setOriginOp(op) {
|
||||
this._data.originOp = op;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns origin operation of object
|
||||
* @return {string} origin operation string
|
||||
*/
|
||||
getOriginOp() {
|
||||
return this._data.originOp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata object
|
||||
*
|
||||
|
|
|
@ -14,10 +14,6 @@ class ObjectMDLocation {
|
|||
* @param {string} locationObj.dataStoreName - type of data store
|
||||
* @param {string} locationObj.dataStoreETag - internal ETag of
|
||||
* data part
|
||||
* @param {number} [location.cryptoScheme] - if location data is
|
||||
* encrypted: the encryption scheme version
|
||||
* @param {string} [location.cipheredDataKey] - if location data
|
||||
* is encrypted: the base64-encoded ciphered data key
|
||||
*/
|
||||
constructor(locationObj) {
|
||||
this._data = {
|
||||
|
@ -27,10 +23,6 @@ class ObjectMDLocation {
|
|||
dataStoreName: locationObj.dataStoreName,
|
||||
dataStoreETag: locationObj.dataStoreETag,
|
||||
};
|
||||
if (locationObj.cryptoScheme) {
|
||||
this._data.cryptoScheme = locationObj.cryptoScheme;
|
||||
this._data.cipheredDataKey = locationObj.cipheredDataKey;
|
||||
}
|
||||
}
|
||||
|
||||
getKey() {
|
||||
|
@ -41,31 +33,9 @@ class ObjectMDLocation {
|
|||
return this._data.dataStoreName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data location with new info
|
||||
*
|
||||
* @param {object} location - single data location info
|
||||
* @param {string} location.key - data backend key
|
||||
* @param {string} location.dataStoreName - type of data store
|
||||
* @param {number} [location.cryptoScheme] - if location data is
|
||||
* encrypted: the encryption scheme version
|
||||
* @param {string} [location.cipheredDataKey] - if location data
|
||||
* is encrypted: the base64-encoded ciphered data key
|
||||
* @return {ObjectMDLocation} return this
|
||||
*/
|
||||
setDataLocation(location) {
|
||||
[
|
||||
'key',
|
||||
'dataStoreName',
|
||||
'cryptoScheme',
|
||||
'cipheredDataKey',
|
||||
].forEach(attrName => {
|
||||
if (location[attrName] !== undefined) {
|
||||
this._data[attrName] = location[attrName];
|
||||
} else {
|
||||
delete this._data[attrName];
|
||||
}
|
||||
});
|
||||
this._data.key = location.key;
|
||||
this._data.dataStoreName = location.dataStoreName;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -94,14 +64,6 @@ class ObjectMDLocation {
|
|||
return this;
|
||||
}
|
||||
|
||||
getCryptoScheme() {
|
||||
return this._data.cryptoScheme;
|
||||
}
|
||||
|
||||
getCipheredDataKey() {
|
||||
return this._data.cipheredDataKey;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this._data;
|
||||
}
|
||||
|
|
|
@ -1,603 +0,0 @@
|
|||
'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);
|
||||
});
|
||||
}
|
||||
|
||||
healthcheck(logger, cb) {
|
||||
// the bucket does not have to exist, just passing a common bucket name here
|
||||
this.createBucketKey('kmip-healthcheck-test-bucket', logger, (err, bucketKeyId) => {
|
||||
if (err) {
|
||||
logger.error('KMIP::healthcheck: failure to create a test bucket key', {
|
||||
error: err,
|
||||
});
|
||||
return cb(err);
|
||||
}
|
||||
logger.debug('KMIP::healthcheck: success creating a test bucket key');
|
||||
this.destroyBucketKey(bucketKeyId, logger, err => {
|
||||
if (err) {
|
||||
logger.error('KMIP::healthcheck: failure to remove the test bucket key', {
|
||||
bucketKeyId,
|
||||
error: err,
|
||||
});
|
||||
}
|
||||
});
|
||||
// no need to have the healthcheck wait until the
|
||||
// destroyBucketKey() call finishes, as the healthcheck
|
||||
// already succeeded by creating the bucket key
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Client;
|
|
@ -1,54 +0,0 @@
|
|||
'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;
|
|
@ -1,105 +0,0 @@
|
|||
# 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');
|
||||
});
|
||||
```
|
|
@ -1,367 +0,0 @@
|
|||
# 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)
|
|
@ -1,434 +0,0 @@
|
|||
'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;
|
|
@ -1,350 +0,0 @@
|
|||
'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 send 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;
|
|
@ -1,579 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
'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,
|
||||
() => {
|
||||
if (this.handshakeFunction) {
|
||||
this.handshakeFunction(logger, readyCallback);
|
||||
} else {
|
||||
readyCallback(null);
|
||||
}
|
||||
});
|
||||
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.socket = null;
|
||||
this._drainQueuesWithError(error);
|
||||
});
|
||||
socket.on('error', err => {
|
||||
this._drainQueuesWithError(err);
|
||||
});
|
||||
this.socket = socket;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
this._drainQueuesWithError(err);
|
||||
readyCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
_doSend(logger, encodedMessage, cb) {
|
||||
this.callbackPipeline.push(cb);
|
||||
if (this.socket === null || this.socket.destroyed) {
|
||||
this._createConversation(logger, () => {});
|
||||
}
|
||||
const socket = this.socket;
|
||||
if (socket) {
|
||||
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);
|
||||
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;
|
|
@ -1,12 +0,0 @@
|
|||
'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,14 +2,12 @@
|
|||
|
||||
const Ajv = require('ajv');
|
||||
const userPolicySchema = require('./userPolicySchema');
|
||||
const resourcePolicySchema = require('./resourcePolicySchema');
|
||||
const errors = require('../errors');
|
||||
|
||||
const ajValidate = new Ajv({ allErrors: true });
|
||||
ajValidate.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
|
||||
// compiles schema to functions and caches them for all cases
|
||||
const userPolicyValidate = ajValidate.compile(userPolicySchema);
|
||||
const resourcePolicyValidate = ajValidate.compile(resourcePolicySchema);
|
||||
|
||||
const errDict = {
|
||||
required: {
|
||||
|
@ -27,38 +25,33 @@ const errDict = {
|
|||
};
|
||||
|
||||
// parse ajv errors and return early with the first relevant error
|
||||
function _parseErrors(ajvErrors, policyType) {
|
||||
let parsedErr;
|
||||
if (policyType === 'user') {
|
||||
// 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);
|
||||
}
|
||||
function _parseErrors(ajvErrors) {
|
||||
// deep copy is needed as we have to assign custom error description
|
||||
const parsedErr = Object.assign({}, errors.MalformedPolicyDocument);
|
||||
parsedErr.description = 'Syntax errors in policy.';
|
||||
ajvErrors.some(err => {
|
||||
const resource = err.dataPath;
|
||||
const field = err.params ? err.params.missingProperty : undefined;
|
||||
const errType = err.keyword;
|
||||
if (errType === 'type' && (resource === '.Statement' ||
|
||||
resource.includes('.Resource') ||
|
||||
resource.includes('.NotResource'))) {
|
||||
resource === '.Statement.Resource' ||
|
||||
resource === '.Statement.NotResource')) {
|
||||
// skip this as this doesn't have enough error context
|
||||
return false;
|
||||
}
|
||||
if (err.keyword === 'required' && field && errDict.required[field]) {
|
||||
parsedErr.description = errDict.required[field];
|
||||
} else if (err.keyword === 'pattern' &&
|
||||
(resource.includes('.Action') ||
|
||||
resource.includes('.NotAction'))) {
|
||||
(resource === '.Statement.Action' ||
|
||||
resource === '.Statement.NotAction')) {
|
||||
parsedErr.description = errDict.pattern.Action;
|
||||
} else if (err.keyword === 'pattern' &&
|
||||
(resource.includes('.Resource') ||
|
||||
resource.includes('.NotResource'))) {
|
||||
(resource === '.Statement.Resource' ||
|
||||
resource === '.Statement.NotResource')) {
|
||||
parsedErr.description = errDict.pattern.Resource;
|
||||
} else if (err.keyword === 'minItems' &&
|
||||
(resource.includes('.Resource') ||
|
||||
resource.includes('.NotResource'))) {
|
||||
(resource === '.Statement.Resource' ||
|
||||
resource === '.Statement.NotResource')) {
|
||||
parsedErr.description = errDict.minItems.Resource;
|
||||
}
|
||||
return true;
|
||||
|
@ -85,24 +78,12 @@ function _validatePolicy(type, policy) {
|
|||
}
|
||||
userPolicyValidate(parseRes);
|
||||
if (userPolicyValidate.errors) {
|
||||
return { error: _parseErrors(userPolicyValidate.errors, 'user'),
|
||||
return { error: _parseErrors(userPolicyValidate.errors),
|
||||
valid: false };
|
||||
}
|
||||
return { error: null, valid: true };
|
||||
}
|
||||
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 };
|
||||
}
|
||||
// TODO: add support for resource policies
|
||||
return { error: errors.NotImplemented, valid: false };
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -1,491 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-06/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",
|
||||
"const": "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": true,
|
||||
"aws:EpochTime": true,
|
||||
"aws:MultiFactorAuthAge": true,
|
||||
"aws:MultiFactorAuthPresent": true,
|
||||
"aws:PrincipalArn": true,
|
||||
"aws:PrincipalOrgId": true,
|
||||
"aws:PrincipalTag/${TagKey}": true,
|
||||
"aws:PrincipalType": true,
|
||||
"aws:Referer": true,
|
||||
"aws:RequestTag/${TagKey}": true,
|
||||
"aws:RequestedRegion": true,
|
||||
"aws:SecureTransport": true,
|
||||
"aws:SourceAccount": true,
|
||||
"aws:SourceArn": true,
|
||||
"aws:SourceIp": true,
|
||||
"aws:SourceVpc": true,
|
||||
"aws:SourceVpce": true,
|
||||
"aws:TagKeys": true,
|
||||
"aws:TokenIssueTime": true,
|
||||
"aws:UserAgent": true,
|
||||
"aws:userid": true,
|
||||
"aws:username": true,
|
||||
"s3:ExistingJobOperation": true,
|
||||
"s3:ExistingJobPriority": true,
|
||||
"s3:ExistingObjectTag/<key>": true,
|
||||
"s3:JobSuspendedCause": true,
|
||||
"s3:LocationConstraint": true,
|
||||
"s3:RequestJobOperation": true,
|
||||
"s3:RequestJobPriority": true,
|
||||
"s3:RequestObjectTag/<key>": true,
|
||||
"s3:RequestObjectTagKeys": true,
|
||||
"s3:VersionId": true,
|
||||
"s3:authtype": true,
|
||||
"s3:delimiter": true,
|
||||
"s3:locationconstraint": true,
|
||||
"s3:max-keys": true,
|
||||
"s3:object-lock-legal-hold": true,
|
||||
"s3:object-lock-mode": true,
|
||||
"s3:object-lock-remaining-retention-days": true,
|
||||
"s3:object-lock-retain-until-date": true,
|
||||
"s3:prefix": true,
|
||||
"s3:signatureage": true,
|
||||
"s3:signatureversion": true,
|
||||
"s3:versionid": true,
|
||||
"s3:x-amz-acl": true,
|
||||
"s3:x-amz-content-sha256": true,
|
||||
"s3:x-amz-copy-source": true,
|
||||
"s3:x-amz-grant-full-control": true,
|
||||
"s3:x-amz-grant-read": true,
|
||||
"s3:x-amz-grant-read-acp": true,
|
||||
"s3:x-amz-grant-write": true,
|
||||
"s3:x-amz-grant-write-acp": true,
|
||||
"s3:x-amz-metadata-directive": true,
|
||||
"s3:x-amz-server-side-encryption": true,
|
||||
"s3:x-amz-server-side-encryption-aws-kms-key-id": true,
|
||||
"s3:x-amz-storage-class": true,
|
||||
"s3:x-amz-website-redirect-location": true
|
||||
},
|
||||
"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",
|
||||
"const": "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-06/schema#",
|
||||
"type": "object",
|
||||
"title": "AWS User Policy schema.",
|
||||
"title": "AWS Policy schema.",
|
||||
"description": "This schema describes a user policy per AWS policy grammar rules",
|
||||
"definitions": {
|
||||
"principalService": {
|
||||
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
"principalAWSUserArn": {
|
||||
"type": "string",
|
||||
"pattern": "^arn:aws:iam::[0-9]{12}:user/(?!\\*)[\\w+=,.@ -/]{1,64}$"
|
||||
"pattern": "^arn:aws:iam::[0-9]{12}:user/[\\w+=,.@ -]{1,64}$"
|
||||
},
|
||||
"principalAWSRoleArn": {
|
||||
"type": "string",
|
||||
|
@ -566,4 +566,4 @@
|
|||
"Statement"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
|
@ -134,7 +134,7 @@ class RequestContext {
|
|||
requesterIp, sslEnabled, apiMethod,
|
||||
awsService, locationConstraint, requesterInfo,
|
||||
signatureVersion, authType, signatureAge, securityToken, policyArn,
|
||||
action, postXml) {
|
||||
action) {
|
||||
this._headers = headers;
|
||||
this._query = query;
|
||||
this._requesterIp = requesterIp;
|
||||
|
@ -163,10 +163,6 @@ class RequestContext {
|
|||
this._policyArn = policyArn;
|
||||
this._action = action;
|
||||
this._needQuota = _actionNeedQuotaCheck[apiMethod] === true;
|
||||
this._postXml = postXml;
|
||||
this._requestObjTags = null;
|
||||
this._existingObjTag = null;
|
||||
this._needTagEval = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -195,10 +191,6 @@ class RequestContext {
|
|||
securityToken: this._securityToken,
|
||||
policyArn: this._policyArn,
|
||||
action: this._action,
|
||||
postXml: this._postXml,
|
||||
requestObjTags: this._requestObjTags,
|
||||
existingObjTag: this._existingObjTag,
|
||||
needTagEval: this._needTagEval,
|
||||
};
|
||||
return JSON.stringify(requestInfo);
|
||||
}
|
||||
|
@ -224,7 +216,7 @@ class RequestContext {
|
|||
obj.apiMethod, obj.awsService, obj.locationConstraint,
|
||||
obj.requesterInfo, obj.signatureVersion,
|
||||
obj.authType, obj.signatureAge, obj.securityToken, obj.policyArn,
|
||||
obj.action, obj.postXml);
|
||||
obj.action);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -567,86 +559,6 @@ class RequestContext {
|
|||
isQuotaCheckNeeded() {
|
||||
return this._needQuota;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request post
|
||||
*
|
||||
* @param {string} postXml - request post
|
||||
* @return {RequestContext} itself
|
||||
*/
|
||||
setPostXml(postXml) {
|
||||
this._postXml = postXml;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request post
|
||||
*
|
||||
* @return {string} request post
|
||||
*/
|
||||
getPostXml() {
|
||||
return this._postXml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request object tags
|
||||
*
|
||||
* @param {string} requestObjTags - object tag(s) included in request in query string form
|
||||
* @return {RequestContext} itself
|
||||
*/
|
||||
setRequestObjTags(requestObjTags) {
|
||||
this._requestObjTags = requestObjTags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request object tags
|
||||
*
|
||||
* @return {string} request object tag(s)
|
||||
*/
|
||||
getRequestObjTags() {
|
||||
return this._requestObjTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set info on existing tag on object included in request
|
||||
*
|
||||
* @param {string} existingObjTag - existing object tag in query string form
|
||||
* @return {RequestContext} itself
|
||||
*/
|
||||
setExistingObjTag(existingObjTag) {
|
||||
this._existingObjTag = existingObjTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing object tag
|
||||
*
|
||||
* @return {string} existing object tag
|
||||
*/
|
||||
getExistingObjTag() {
|
||||
return this._existingObjTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether IAM policy tag condition keys should be evaluated
|
||||
*
|
||||
* @param {boolean} needTagEval - whether to evaluate tags
|
||||
* @return {RequestContext} itself
|
||||
*/
|
||||
setNeedTagEval(needTagEval) {
|
||||
this._needTagEval = needTagEval;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get needTagEval param
|
||||
*
|
||||
* @return {boolean} needTagEval - whether IAM policy tags condition keys should be evaluated
|
||||
*/
|
||||
getNeedTagEval() {
|
||||
return this._needTagEval;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestContext;
|
||||
|
|
|
@ -6,7 +6,6 @@ const conditions = require('./utils/conditions.js');
|
|||
const findConditionKey = conditions.findConditionKey;
|
||||
const convertConditionOperator = conditions.convertConditionOperator;
|
||||
const checkArnMatch = require('./utils/checkArnMatch.js');
|
||||
const { transformTagKeyValue } = require('./utils/objectTags');
|
||||
|
||||
const evaluators = {};
|
||||
|
||||
|
@ -17,7 +16,6 @@ const operatorsWithVariables = ['StringEquals', 'StringNotEquals',
|
|||
const operatorsWithNegation = ['StringNotEquals',
|
||||
'StringNotEqualsIgnoreCase', 'StringNotLike', 'ArnNotEquals',
|
||||
'ArnNotLike', 'NumericNotEquals'];
|
||||
const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey', 's3:RequestObjectTagKeys']);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -28,7 +26,7 @@ const tagConditions = new Set(['s3:ExistingObjectTag', 's3:RequestObjectTagKey',
|
|||
* @param {object} log - logger
|
||||
* @return {boolean} true if applicable, false if not
|
||||
*/
|
||||
evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
|
||||
function isResourceApplicable(requestContext, statementResource, log) {
|
||||
const resource = requestContext.getResource();
|
||||
if (!Array.isArray(statementResource)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
@ -59,7 +57,7 @@ evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
|
|||
{ requestResource: resource });
|
||||
// If no match found, no resource is applicable
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether action in policy statement applies to request
|
||||
|
@ -69,7 +67,7 @@ evaluators.isResourceApplicable = (requestContext, statementResource, log) => {
|
|||
* @param {Object} log - logger
|
||||
* @return {boolean} true if applicable, false if not
|
||||
*/
|
||||
evaluators.isActionApplicable = (requestAction, statementAction, log) => {
|
||||
function isActionApplicable(requestAction, statementAction, log) {
|
||||
if (!Array.isArray(statementAction)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
statementAction = [statementAction];
|
||||
|
@ -91,34 +89,27 @@ evaluators.isActionApplicable = (requestAction, statementAction, log) => {
|
|||
{ requestAction });
|
||||
// If no match found, return false
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether request meets policy conditions
|
||||
* @param {RequestContext} requestContext - info about request
|
||||
* @param {Object} statementCondition - Condition statement from policy
|
||||
* @param {Object} log - logger
|
||||
* @return {Object} contains whether conditions are allowed and whether they
|
||||
* contain any tag condition keys
|
||||
* @return {boolean} true if meet conditions, false if not
|
||||
*/
|
||||
evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
||||
// The Condition portion of a policy is an object with different
|
||||
// operators as keys
|
||||
const conditionEval = {};
|
||||
const operators = Object.keys(statementCondition);
|
||||
const length = operators.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const operator = operators[i];
|
||||
const hasPrefix = operator.includes(':');
|
||||
const hasIfExistsCondition = operator.endsWith('IfExists');
|
||||
// If has "IfExists" added to operator name, or operator has "ForAnyValue" or
|
||||
// "For All Values" prefix, find operator name without "IfExists" or prefix
|
||||
let bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
|
||||
// If has "IfExists" added to operator name, find operator name
|
||||
// without "IfExists"
|
||||
const bareOperator = hasIfExistsCondition ? operator.slice(0, -8) :
|
||||
operator;
|
||||
let prefix;
|
||||
if (hasPrefix) {
|
||||
[prefix, bareOperator] = bareOperator.split(':');
|
||||
}
|
||||
const operatorCanHaveVariables =
|
||||
operatorsWithVariables.indexOf(bareOperator) > -1;
|
||||
const isNegationOperator =
|
||||
|
@ -127,9 +118,6 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
|||
// Note: this should be the actual operator name, not the bareOperator
|
||||
const conditionsWithSameOperator = statementCondition[operator];
|
||||
const conditionKeys = Object.keys(conditionsWithSameOperator);
|
||||
if (conditionKeys.some(key => tagConditions.has(key)) && !requestContext.getNeedTagEval()) {
|
||||
conditionEval.tagConditions = true;
|
||||
}
|
||||
const conditionKeysLength = conditionKeys.length;
|
||||
for (let j = 0; j < conditionKeysLength; j++) {
|
||||
const key = conditionKeys[j];
|
||||
|
@ -142,18 +130,14 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
|||
value = value.map(item =>
|
||||
substituteVariables(item, requestContext));
|
||||
}
|
||||
// if condition key is RequestObjectTag or ExistingObjectTag,
|
||||
// tag key is included in condition key and needs to be
|
||||
// moved to value for evaluation, otherwise key/value are unchanged
|
||||
const [transformedKey, transformedValue] = transformTagKeyValue(key, value);
|
||||
// Pull key using requestContext
|
||||
// TODO: If applicable to S3, handle policy set operations
|
||||
// where a keyBasedOnRequestContext returns multiple values and
|
||||
// condition has "ForAnyValue" or "ForAllValues".
|
||||
// (see http://docs.aws.amazon.com/IAM/latest/UserGuide/
|
||||
// reference_policies_multi-value-conditions.html)
|
||||
let keyBasedOnRequestContext =
|
||||
findConditionKey(transformedKey, requestContext);
|
||||
const keyBasedOnRequestContext =
|
||||
findConditionKey(key, requestContext);
|
||||
// Handle IfExists and negation operators
|
||||
if ((keyBasedOnRequestContext === undefined ||
|
||||
keyBasedOnRequestContext === null) &&
|
||||
|
@ -170,27 +154,22 @@ evaluators.meetConditions = (requestContext, statementCondition, log) => {
|
|||
bareOperator !== 'Null') {
|
||||
log.trace('condition not satisfied due to ' +
|
||||
'missing info', { operator,
|
||||
conditionKey: transformedKey, policyValue: transformedValue });
|
||||
return { allow: false };
|
||||
}
|
||||
// If condition operator prefix is included, the key should be an array
|
||||
if (prefix && !Array.isArray(keyBasedOnRequestContext)) {
|
||||
keyBasedOnRequestContext = [keyBasedOnRequestContext];
|
||||
conditionKey: key, policyValue: value });
|
||||
return false;
|
||||
}
|
||||
// Transalate operator into function using bareOperator
|
||||
const operatorFunction = convertConditionOperator(bareOperator);
|
||||
// Note: Wildcards are handled in the comparison operator function
|
||||
// itself since StringLike, StringNotLike, ArnLike and ArnNotLike
|
||||
// are the only operators where wildcards are allowed
|
||||
if (!operatorFunction(keyBasedOnRequestContext, transformedValue, prefix)) {
|
||||
if (!operatorFunction(keyBasedOnRequestContext, value)) {
|
||||
log.trace('did not satisfy condition', { operator: bareOperator,
|
||||
keyBasedOnRequestContext, policyValue: transformedValue });
|
||||
return { allow: false };
|
||||
keyBasedOnRequestContext, policyValue: value });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
conditionEval.allow = true;
|
||||
return conditionEval;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -216,36 +195,35 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
|
|||
const currentStatement = policy.Statement[i];
|
||||
// If affirmative resource is in policy and request resource is
|
||||
// not applicable, move on to next statement
|
||||
if (currentStatement.Resource && !evaluators.isResourceApplicable(requestContext,
|
||||
if (currentStatement.Resource && !isResourceApplicable(requestContext,
|
||||
currentStatement.Resource, log)) {
|
||||
continue;
|
||||
}
|
||||
// If NotResource is in policy and resource matches NotResource
|
||||
// in policy, move on to next statement
|
||||
if (currentStatement.NotResource &&
|
||||
evaluators.isResourceApplicable(requestContext,
|
||||
isResourceApplicable(requestContext,
|
||||
currentStatement.NotResource, log)) {
|
||||
continue;
|
||||
}
|
||||
// If affirmative action is in policy and request action is not
|
||||
// applicable, move on to next statement
|
||||
if (currentStatement.Action &&
|
||||
!evaluators.isActionApplicable(requestContext.getAction(),
|
||||
!isActionApplicable(requestContext.getAction(),
|
||||
currentStatement.Action, log)) {
|
||||
continue;
|
||||
}
|
||||
// If NotAction is in policy and action matches NotAction in policy,
|
||||
// move on to next statement
|
||||
if (currentStatement.NotAction &&
|
||||
evaluators.isActionApplicable(requestContext.getAction(),
|
||||
isActionApplicable(requestContext.getAction(),
|
||||
currentStatement.NotAction, log)) {
|
||||
continue;
|
||||
}
|
||||
const conditionEval = currentStatement.Condition ?
|
||||
evaluators.meetConditions(requestContext, currentStatement.Condition, log) :
|
||||
null;
|
||||
// If do not meet conditions move on to next statement
|
||||
if (conditionEval && !conditionEval.allow) {
|
||||
if (currentStatement.Condition &&
|
||||
!evaluators.meetConditions(requestContext,
|
||||
currentStatement.Condition, log)) {
|
||||
continue;
|
||||
}
|
||||
if (currentStatement.Effect === 'Deny') {
|
||||
|
@ -257,9 +235,6 @@ evaluators.evaluatePolicy = (requestContext, policy, log) => {
|
|||
// If statement is applicable, conditions are met and Effect is
|
||||
// to Allow, set verdict to Allow
|
||||
verdict = 'Allow';
|
||||
if (conditionEval && conditionEval.tagConditions) {
|
||||
verdict = 'NeedTagConditionEval';
|
||||
}
|
||||
}
|
||||
log.trace('result of evaluating single policy', { verdict });
|
||||
return verdict;
|
||||
|
|
|
@ -35,8 +35,7 @@ class Principal {
|
|||
// In case of anonymous NotPrincipal, this will neutral everyone
|
||||
return 'Neutral';
|
||||
}
|
||||
const conditionEval = Principal._evaluateCondition(params, statement);
|
||||
if (!conditionEval || conditionEval.allow === false) {
|
||||
if (!Principal._evaluateCondition(params, statement)) {
|
||||
return 'Neutral';
|
||||
}
|
||||
return statement.Effect;
|
||||
|
@ -66,8 +65,7 @@ class Principal {
|
|||
if (reverse) {
|
||||
return 'Neutral';
|
||||
}
|
||||
const conditionEval = Principal._evaluateCondition(params, statement);
|
||||
if (!conditionEval || conditionEval.allow === false) {
|
||||
if (!Principal._evaluateCondition(params, statement)) {
|
||||
return 'Neutral';
|
||||
}
|
||||
return statement.Effect;
|
||||
|
@ -78,8 +76,7 @@ class Principal {
|
|||
if (reverse) {
|
||||
return 'Neutral';
|
||||
}
|
||||
const conditionEval = Principal._evaluateCondition(params, statement);
|
||||
if (!conditionEval || conditionEval.allow === false) {
|
||||
if (!Principal._evaluateCondition(params, statement)) {
|
||||
return 'Neutral';
|
||||
}
|
||||
return statement.Effect;
|
||||
|
@ -143,7 +140,6 @@ class Principal {
|
|||
AWS: [
|
||||
account,
|
||||
accountArn,
|
||||
requesterArn,
|
||||
],
|
||||
};
|
||||
checkAction = true;
|
||||
|
|
|
@ -1,33 +1,21 @@
|
|||
const sharedActionMap = {
|
||||
bucketDelete: 's3:DeleteBucket',
|
||||
// the "s3:PutEncryptionConfiguration" action also governs DELETE
|
||||
bucketDeleteEncryption: 's3:PutEncryptionConfiguration',
|
||||
bucketDeletePolicy: 's3:DeleteBucketPolicy',
|
||||
bucketDeleteWebsite: 's3:DeleteBucketWebsite',
|
||||
bucketGet: 's3:ListBucket',
|
||||
bucketGetACL: 's3:GetBucketAcl',
|
||||
bucketGetCors: 's3:GetBucketCORS',
|
||||
bucketGetEncryption: 's3:GetEncryptionConfiguration',
|
||||
bucketGetLifecycle: 's3:GetLifecycleConfiguration',
|
||||
bucketGetLocation: 's3:GetBucketLocation',
|
||||
bucketGetNotification: 's3:GetBucketNotificationConfiguration',
|
||||
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
|
||||
bucketGetPolicy: 's3:GetBucketPolicy',
|
||||
bucketGetReplication: 's3:GetReplicationConfiguration',
|
||||
bucketGetVersioning: 's3:GetBucketVersioning',
|
||||
bucketGetWebsite: 's3:GetBucketWebsite',
|
||||
bucketHead: 's3:ListBucket',
|
||||
bucketPutACL: 's3:PutBucketAcl',
|
||||
bucketPutCors: 's3:PutBucketCORS',
|
||||
bucketPutEncryption: 's3:PutEncryptionConfiguration',
|
||||
bucketPutLifecycle: 's3:PutLifecycleConfiguration',
|
||||
bucketPutNotification: 's3:PutBucketNotificationConfiguration',
|
||||
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
|
||||
bucketPutPolicy: 's3:PutBucketPolicy',
|
||||
bucketPutReplication: 's3:PutReplicationConfiguration',
|
||||
bucketPutVersioning: 's3:PutBucketVersioning',
|
||||
bucketPutWebsite: 's3:PutBucketWebsite',
|
||||
bypassGovernanceRetention: 's3:BypassGovernanceRetention',
|
||||
listMultipartUploads: 's3:ListBucketMultipartUploads',
|
||||
listParts: 's3:ListMultipartUploadParts',
|
||||
multipartDelete: 's3:AbortMultipartUpload',
|
||||
|
@ -35,13 +23,9 @@ const sharedActionMap = {
|
|||
objectDeleteTagging: 's3:DeleteObjectTagging',
|
||||
objectGet: 's3:GetObject',
|
||||
objectGetACL: 's3:GetObjectAcl',
|
||||
objectGetLegalHold: 's3:GetObjectLegalHold',
|
||||
objectGetRetention: 's3:GetObjectRetention',
|
||||
objectGetTagging: 's3:GetObjectTagging',
|
||||
objectPut: 's3:PutObject',
|
||||
objectPutACL: 's3:PutObjectAcl',
|
||||
objectPutLegalHold: 's3:PutObjectLegalHold',
|
||||
objectPutRetention: 's3:PutObjectRetention',
|
||||
objectPutTagging: 's3:PutObjectTagging',
|
||||
};
|
||||
|
||||
|
@ -67,12 +51,20 @@ const actionMapRQ = Object.assign({
|
|||
objectPutTaggingVersion: 's3:PutObjectVersionTagging',
|
||||
serviceGet: 's3:ListAllMyBuckets',
|
||||
objectReplicate: 's3:ReplicateObject',
|
||||
objectPutRetentionVersion: 's3:PutObjectVersionRetention',
|
||||
objectPutLegalHoldVersion: 's3:PutObjectVersionLegalHold',
|
||||
}, sharedActionMap);
|
||||
|
||||
// action map used for bucket policies
|
||||
const actionMapBP = Object.assign({}, sharedActionMap);
|
||||
const actionMapBP = Object.assign({
|
||||
bucketDeletePolicy: 's3:DeleteBucketPolicy',
|
||||
bucketGetObjectLock: 's3:GetBucketObjectLockConfiguration',
|
||||
bucketGetPolicy: 's3:GetBucketPolicy',
|
||||
bucketPutObjectLock: 's3:PutBucketObjectLockConfiguration',
|
||||
bucketPutPolicy: 's3:PutBucketPolicy',
|
||||
objectGetLegalHold: 's3:GetObjectLegalHold',
|
||||
objectGetRetention: 's3:GetObjectRetention',
|
||||
objectPutLegalHold: 's3:PutObjectLegalHold',
|
||||
objectPutRetention: 's3:PutObjectRetention',
|
||||
}, sharedActionMap);
|
||||
|
||||
// action map for all relevant s3 actions
|
||||
const actionMapS3 = Object.assign({
|
||||
|
@ -83,9 +75,7 @@ const actionMapS3 = Object.assign({
|
|||
const actionMonitoringMapS3 = {
|
||||
bucketDelete: 'DeleteBucket',
|
||||
bucketDeleteCors: 'DeleteBucketCors',
|
||||
bucketDeleteEncryption: 'DeleteBucketEncryption',
|
||||
bucketDeleteLifecycle: 'DeleteBucketLifecycle',
|
||||
bucketDeletePolicy: 'DeleteBucketPolicy',
|
||||
bucketDeleteReplication: 'DeleteBucketReplication',
|
||||
bucketDeleteWebsite: 'DeleteBucketWebsite',
|
||||
bucketGet: 'ListObjects',
|
||||
|
@ -93,24 +83,16 @@ const actionMonitoringMapS3 = {
|
|||
bucketGetCors: 'GetBucketCors',
|
||||
bucketGetLifecycle: 'GetBucketLifecycleConfiguration',
|
||||
bucketGetLocation: 'GetBucketLocation',
|
||||
bucketGetNotification: 'GetBucketNotificationConfiguration',
|
||||
bucketGetObjectLock: 'GetObjectLockConfiguration',
|
||||
bucketGetPolicy: 'GetBucketPolicy',
|
||||
bucketGetReplication: 'GetBucketReplication',
|
||||
bucketGetVersioning: 'GetBucketVersioning',
|
||||
bucketGetEncryption: 'GetBucketEncryption',
|
||||
bucketGetWebsite: 'GetBucketWebsite',
|
||||
bucketHead: 'HeadBucket',
|
||||
bucketPut: 'CreateBucket',
|
||||
bucketPutACL: 'PutBucketAcl',
|
||||
bucketPutCors: 'PutBucketCors',
|
||||
bucketPutLifecycle: 'PutBucketLifecycleConfiguration',
|
||||
bucketPutNotification: 'PutBucketNotificationConfiguration',
|
||||
bucketPutObjectLock: 'PutObjectLockConfiguration',
|
||||
bucketPutPolicy: 'PutBucketPolicy',
|
||||
bucketPutReplication: 'PutBucketReplication',
|
||||
bucketPutVersioning: 'PutBucketVersioning',
|
||||
bucketPutEncryption: 'PutBucketEncryption',
|
||||
bucketPutWebsite: 'PutBucketWebsite',
|
||||
completeMultipartUpload: 'CompleteMultipartUpload',
|
||||
initiateMultipartUpload: 'CreateMultipartUpload',
|
||||
|
@ -123,16 +105,12 @@ const actionMonitoringMapS3 = {
|
|||
objectDeleteTagging: 'DeleteObjectTagging',
|
||||
objectGet: 'GetObject',
|
||||
objectGetACL: 'GetObjectAcl',
|
||||
objectGetLegalHold: 'GetObjectLegalHold',
|
||||
objectGetRetention: 'GetObjectRetention',
|
||||
objectGetTagging: 'GetObjectTagging',
|
||||
objectHead: 'HeadObject',
|
||||
objectPut: 'PutObject',
|
||||
objectPutACL: 'PutObjectAcl',
|
||||
objectPutCopyPart: 'UploadPartCopy',
|
||||
objectPutLegalHold: 'PutObjectLegalHold',
|
||||
objectPutPart: 'UploadPart',
|
||||
objectPutRetention: 'PutObjectRetention',
|
||||
objectPutTagging: 'PutObjectTagging',
|
||||
serviceGet: 'ListBuckets',
|
||||
};
|
||||
|
@ -167,12 +145,6 @@ const actionMapIAM = {
|
|||
listUsers: 'iam:ListUsers',
|
||||
putGroupPolicy: 'iam:PutGroupPolicy',
|
||||
removeUserFromGroup: 'iam:RemoveUserFromGroup',
|
||||
updateAccessKey: 'iam:UpdateAccessKey',
|
||||
updateGroup: 'iam:UpdateGroup',
|
||||
updateUser: 'iam:UpdateUser',
|
||||
getAccessKeyLastUsed: 'iam:GetAccessKeyLastUsed',
|
||||
generateCredentialReport: 'iam:GenerateCredentialReport',
|
||||
getCredentialReport: 'iam:GetCredentialReport',
|
||||
};
|
||||
|
||||
const actionMapSSO = {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
const checkIPinRangeOrMatch = require('../../ipCheck').checkIPinRangeOrMatch;
|
||||
const handleWildcards = require('./wildcards.js').handleWildcards;
|
||||
const checkArnMatch = require('./checkArnMatch.js');
|
||||
const { getTagKeys } = require('./objectTags');
|
||||
const conditions = {};
|
||||
|
||||
/**
|
||||
|
@ -147,25 +146,6 @@ conditions.findConditionKey = (key, requestContext) => {
|
|||
headers['x-amz-meta-scal-location-constraint']);
|
||||
map.set('sts:ExternalId', requestContext.getRequesterExternalId());
|
||||
map.set('iam:PolicyArn', requestContext.getPolicyArn());
|
||||
// s3:ExistingObjectTag - Used to check that existing object tag has
|
||||
// specific tag key and value. Extraction of correct tag key is done in CloudServer.
|
||||
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||
// so evaluation should be skipped
|
||||
map.set('s3:ExistingObjectTag', requestContext.getNeedTagEval() ? requestContext.getExistingObjTag() : undefined);
|
||||
// s3:RequestObjectTag - Used to limit putting object tags to specific
|
||||
// tag key and value. N/A here.
|
||||
// Requires information from CloudServer
|
||||
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||
// so evaluation should be skipped
|
||||
map.set('s3:RequestObjectTagKey', requestContext.getNeedTagEval() ? requestContext.getRequestObjTags() : undefined);
|
||||
// s3:RequestObjectTagKeys - Used to limit putting object tags specific tag keys.
|
||||
// Requires information from CloudServer.
|
||||
// On first pass of policy evaluation, CloudServer information will not be included,
|
||||
// so evaluation should be skipped
|
||||
map.set('s3:RequestObjectTagKeys',
|
||||
requestContext.getNeedTagEval() && requestContext.getRequestObjTags()
|
||||
? getTagKeys(requestContext.getRequestObjTags())
|
||||
: undefined);
|
||||
return map.get(key);
|
||||
};
|
||||
|
||||
|
@ -252,21 +232,12 @@ conditions.convertConditionOperator = operator => {
|
|||
// eslint-disable-next-line new-cap
|
||||
return !operatorMap.StringEqualsIgnoreCase(key, value);
|
||||
},
|
||||
StringLike: function stringLike(key, value, prefix) {
|
||||
function policyValRegex(testKey) {
|
||||
return value.some(item => {
|
||||
const wildItem = handleWildcards(item);
|
||||
const wildRegEx = new RegExp(wildItem);
|
||||
return wildRegEx.test(testKey);
|
||||
});
|
||||
}
|
||||
if (prefix === 'ForAnyValue') {
|
||||
return key.some(policyValRegex);
|
||||
}
|
||||
if (prefix === 'ForAllValues') {
|
||||
return key.every(policyValRegex);
|
||||
}
|
||||
return policyValRegex(key);
|
||||
StringLike: function stringLike(key, value) {
|
||||
return value.some(item => {
|
||||
const wildItem = handleWildcards(item);
|
||||
const wildRegEx = new RegExp(wildItem);
|
||||
return wildRegEx.test(key);
|
||||
});
|
||||
},
|
||||
StringNotLike: function stringNotLike(key, value) {
|
||||
// eslint-disable-next-line new-cap
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Removes tag key value from condition key and adds it to value if needed
|
||||
* @param {string} key - condition key
|
||||
* @param {string} value - condition value
|
||||
* @return {array} key/value pair to use
|
||||
*/
|
||||
function transformTagKeyValue(key, value) {
|
||||
const patternKeys = ['s3:ExistingObjectTag/', 's3:RequestObjectTagKey/'];
|
||||
if (!patternKeys.some(k => key.includes(k))) {
|
||||
return [key, value];
|
||||
}
|
||||
// if key is RequestObjectTag or ExistingObjectTag,
|
||||
// remove tag key from condition key and add to value
|
||||
// and transform value into query string
|
||||
const [conditionKey, tagKey] = key.split('/');
|
||||
const transformedValue = [tagKey, value].join('=');
|
||||
return [conditionKey, [transformedValue]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets array of tag key names from request tag query string
|
||||
* @param {string} tagQuery - request tags in query string format
|
||||
* @return {array} array of tag key names
|
||||
*/
|
||||
function getTagKeys(tagQuery) {
|
||||
return tagQuery.split('&')
|
||||
.map(tag => tag.split('=')[0]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
transformTagKeyValue,
|
||||
getTagKeys,
|
||||
};
|
|
@ -15,7 +15,7 @@ azureMpuUtils.overviewMpuKey = 'azure_mpu';
|
|||
azureMpuUtils.maxSubPartSize = 104857600;
|
||||
azureMpuUtils.zeroByteETag = crypto.createHash('md5').update('').digest('hex');
|
||||
|
||||
// TODO: S3C-4657
|
||||
|
||||
azureMpuUtils.padString = (str, category) => {
|
||||
const _padFn = {
|
||||
left: (str, padString) =>
|
||||
|
@ -124,8 +124,7 @@ log, cb) => {
|
|||
`Error returned from Azure: ${err.message}`)
|
||||
);
|
||||
}
|
||||
const md5 = result.headers['content-md5'] || '';
|
||||
const eTag = objectUtils.getHexMD5(md5);
|
||||
const eTag = objectUtils.getHexMD5(result.headers['content-md5']);
|
||||
return cb(null, eTag, size);
|
||||
}], log, cb);
|
||||
};
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
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,
|
||||
};
|
|
@ -1,156 +0,0 @@
|
|||
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.mode = modeObj.mode;
|
||||
retentionObj.date = dateObj.date;
|
||||
return retentionObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseRetentionXml - Parse and validate xml body, returning callback with
|
||||
* object retentionObj: { mode: <value>, date: <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 {string} mode - retention mode
|
||||
* @param {string} date - retention retain until date
|
||||
* @return {string} - returns retention information xml string
|
||||
*/
|
||||
function convertToXml(mode, date) {
|
||||
const xml = [];
|
||||
xml.push('<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">');
|
||||
if (mode && date) {
|
||||
xml.push(`<Mode>${mode}</Mode>`);
|
||||
xml.push(`<RetainUntilDate>${date}</RetainUntilDate>`);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
xml.push('</Retention>');
|
||||
return xml.join('');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseRetentionXml,
|
||||
convertToXml,
|
||||
};
|
|
@ -152,6 +152,8 @@ function routes(req, res, params, logger) {
|
|||
const clientInfo = {
|
||||
clientIP: req.socket.remoteAddress,
|
||||
clientPort: req.socket.remotePort,
|
||||
httpCode: res.statusCode,
|
||||
httpMessage: res.statusMessage,
|
||||
httpMethod: req.method,
|
||||
httpURL: req.url,
|
||||
endpoint: req.endpoint,
|
||||
|
|
|
@ -45,20 +45,6 @@ function routeDELETE(request, response, api, log, statsClient) {
|
|||
return routesUtils.responseNoBody(err, corsHeaders,
|
||||
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);
|
||||
});
|
||||
} else if (request.query.encryption !== undefined) {
|
||||
return api.callApiMethod('bucketDeleteEncryption', request,
|
||||
response, log, (err, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
return routesUtils.responseNoBody(err, corsHeaders,
|
||||
response, 204, log);
|
||||
});
|
||||
}
|
||||
api.callApiMethod('bucketDelete', request, response, log,
|
||||
(err, corsHeaders) => {
|
||||
|
|
|
@ -51,11 +51,11 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
});
|
||||
} else if (request.query.lifecycle !== undefined) {
|
||||
api.callApiMethod('bucketGetLifecycle', request, response, log,
|
||||
(err, xml, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
routesUtils.responseXMLBody(err, xml, response, log,
|
||||
corsHeaders);
|
||||
});
|
||||
(err, xml, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
routesUtils.responseXMLBody(err, xml, response, log,
|
||||
corsHeaders);
|
||||
});
|
||||
} else if (request.query.uploads !== undefined) {
|
||||
// List MultipartUploads
|
||||
api.callApiMethod('listMultipartUploads', request, response, log,
|
||||
|
@ -71,34 +71,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||
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 if (request.query.notification !== undefined) {
|
||||
api.callApiMethod('bucketGetNotification', request, response, log,
|
||||
(err, xml, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
return routesUtils.responseXMLBody(err, xml, response,
|
||||
log, corsHeaders);
|
||||
});
|
||||
} else if (request.query.encryption !== undefined) {
|
||||
api.callApiMethod('bucketGetEncryption', request, response, log,
|
||||
(err, xml, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
return routesUtils.responseXMLBody(err, xml, response,
|
||||
log, corsHeaders);
|
||||
});
|
||||
} else {
|
||||
// GET bucket
|
||||
api.callApiMethod('bucketGet', request, response, log,
|
||||
|
@ -109,6 +81,7 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
/* eslint-disable no-lonely-if */
|
||||
if (request.query.acl !== undefined) {
|
||||
// GET object ACL
|
||||
api.callApiMethod('objectGetACL', request, response, log,
|
||||
|
@ -117,14 +90,8 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||
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) {
|
||||
// GET object Tagging
|
||||
api.callApiMethod('objectGetTagging', request, response, log,
|
||||
(err, xml, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
|
@ -139,13 +106,6 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
return routesUtils.responseXMLBody(err, xml, response, log,
|
||||
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 {
|
||||
// GET object
|
||||
api.callApiMethod('objectGet', request, response, log,
|
||||
|
@ -161,6 +121,7 @@ function routerGET(request, response, api, log, statsClient, dataRetrievalFn) {
|
|||
range, log);
|
||||
});
|
||||
}
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
return routesUtils.responseNoBody(
|
||||
errors.BadRequest, null, response, null, log);
|
||||
}
|
||||
|
||||
// PUT bucket ACL
|
||||
if (request.query.acl !== undefined) {
|
||||
api.callApiMethod('bucketPutACL', request, response, log,
|
||||
|
@ -59,34 +60,6 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||
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 if (request.query.notification !== undefined) {
|
||||
api.callApiMethod('bucketPutNotification', request, response, log,
|
||||
(err, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
routesUtils.responseNoBody(err, corsHeaders, response, 200,
|
||||
log);
|
||||
});
|
||||
} else if (request.query.encryption !== undefined) {
|
||||
api.callApiMethod('bucketPutEncryption', request, response, log,
|
||||
(err, corsHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
return routesUtils.responseNoBody(err, corsHeaders,
|
||||
response, 200, log);
|
||||
});
|
||||
} else {
|
||||
// PUT bucket
|
||||
return api.callApiMethod('bucketPut', request, response, log,
|
||||
|
@ -100,8 +73,8 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
// PUT object, PUT object ACL, PUT object multipart,
|
||||
// PUT object copy or PUT object legal hold
|
||||
// PUT object, PUT object ACL, PUT object multipart or
|
||||
// PUT object copy
|
||||
// if content-md5 is not present in the headers, try to
|
||||
// parse content-md5 from meta headers
|
||||
|
||||
|
@ -159,13 +132,6 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
return routesUtils.responseNoBody(err, resHeaders,
|
||||
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) {
|
||||
api.callApiMethod('objectPutTagging', request, response, log,
|
||||
(err, resHeaders) => {
|
||||
|
@ -173,13 +139,6 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
return routesUtils.responseNoBody(err, resHeaders,
|
||||
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']) {
|
||||
return api.callApiMethod('objectCopy', request, response, log,
|
||||
(err, xml, additionalHeaders) => {
|
||||
|
@ -201,6 +160,7 @@ function routePUT(request, response, api, log, statsClient) {
|
|||
log.end().addDefaultFields({
|
||||
contentLength: request.parsedContentLength,
|
||||
});
|
||||
|
||||
api.callApiMethod('objectPut', request, response, log,
|
||||
(err, resHeaders) => {
|
||||
routesUtils.statsReport500(err, statsClient);
|
||||
|
|
|
@ -6,7 +6,6 @@ const jsonStream = require('JSONStream');
|
|||
const werelogs = require('werelogs');
|
||||
|
||||
const errors = require('../../../errors');
|
||||
const jsutil = require('../../../jsutil');
|
||||
|
||||
class ListRecordStream extends stream.Transform {
|
||||
constructor(logger) {
|
||||
|
@ -88,7 +87,6 @@ class LogConsumer {
|
|||
readRecords(params, cb) {
|
||||
const recordStream = new ListRecordStream(this.logger);
|
||||
const _params = params || {};
|
||||
const cbOnce = jsutil.once(cb);
|
||||
|
||||
this.bucketClient.getRaftLog(
|
||||
this.raftSession, _params.startSeq, _params.limit,
|
||||
|
@ -98,26 +96,26 @@ class LogConsumer {
|
|||
// no such raft session, log and ignore
|
||||
this.logger.warn('raft session does not exist yet',
|
||||
{ raftId: this.raftSession });
|
||||
return cbOnce(null, { info: { start: null,
|
||||
return cb(null, { info: { start: null,
|
||||
end: null } });
|
||||
}
|
||||
if (err.code === 416) {
|
||||
// requested range not satisfiable
|
||||
this.logger.debug('no new log record to process',
|
||||
{ raftId: this.raftSession });
|
||||
return cbOnce(null, { info: { start: null,
|
||||
return cb(null, { info: { start: null,
|
||||
end: null } });
|
||||
}
|
||||
this.logger.error(
|
||||
'Error handling record log request', { error: err });
|
||||
return cbOnce(err);
|
||||
return cb(err);
|
||||
}
|
||||
// setup a temporary listener until the 'header' event
|
||||
// is emitted
|
||||
recordStream.on('error', err => {
|
||||
this.logger.error('error receiving raft log',
|
||||
{ error: err.message });
|
||||
return cbOnce(errors.InternalError);
|
||||
return cb(errors.InternalError);
|
||||
});
|
||||
const jsonResponse = stream.pipe(jsonStream.parse('log.*'));
|
||||
jsonResponse.pipe(recordStream);
|
||||
|
@ -126,7 +124,7 @@ class LogConsumer {
|
|||
.on('header', header => {
|
||||
// remove temporary listener
|
||||
recordStream.removeAllListeners('error');
|
||||
return cbOnce(null, { info: header.info,
|
||||
return cb(null, { info: header.info,
|
||||
log: recordStream });
|
||||
})
|
||||
.on('error', err => recordStream.emit('error', err));
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
// - rep_group_id 07 bytes replication group identifier
|
||||
// - other_information arbitrary user input, such as a unique string
|
||||
|
||||
const base62Integer = require('base62');
|
||||
const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const base62String = require('base-x')(BASE62);
|
||||
|
||||
// the lengths of the components in bytes
|
||||
const LENGTH_TS = 14; // timestamp: epoch in ms
|
||||
const LENGTH_SEQ = 6; // position in ms slot
|
||||
|
@ -131,7 +127,7 @@ function generateVersionId(info, replicationGroupId) {
|
|||
* @param {string} str - the versionId to encode
|
||||
* @return {string} - the encoded versionId
|
||||
*/
|
||||
function hexEncode(str) {
|
||||
function encode(str) {
|
||||
return Buffer.from(str, 'utf8').toString('hex');
|
||||
}
|
||||
|
||||
|
@ -142,7 +138,7 @@ function hexEncode(str) {
|
|||
* @param {string} str - the encoded versionId to decode
|
||||
* @return {(string|Error)} - the decoded versionId or an error
|
||||
*/
|
||||
function hexDecode(str) {
|
||||
function decode(str) {
|
||||
try {
|
||||
const result = Buffer.from(str, 'hex').toString('utf8');
|
||||
if (result === '') {
|
||||
|
@ -156,98 +152,4 @@ function hexDecode(str) {
|
|||
}
|
||||
}
|
||||
|
||||
/* base62 version Ids constants:
|
||||
*
|
||||
* Note: base62Integer() cannot encode integers larger than 15 digits
|
||||
* so we assume that B62V_TOTAL <= 30 and we cut it in half. Please
|
||||
* revisit if B62V_TOTAL is greater than 30.
|
||||
*/
|
||||
const B62V_TOTAL = LENGTH_TS + LENGTH_SEQ;
|
||||
const B62V_HALF = B62V_TOTAL / 2;
|
||||
const B62V_EPAD = '0'.repeat(Math.ceil(B62V_HALF * (Math.log(10) / Math.log(62))));
|
||||
const B62V_DPAD = '0'.repeat(B62V_HALF);
|
||||
|
||||
/**
|
||||
* Encode a versionId to obscure internal information contained
|
||||
* in a version ID (less than 40 bytes).
|
||||
*
|
||||
* @param {string} str - the versionId to encode
|
||||
* @return {string} - the encoded base62VersionId
|
||||
*/
|
||||
function base62Encode(str) {
|
||||
const part1 = Number(str.substring(0, B62V_HALF));
|
||||
const part2 = Number(str.substring(B62V_HALF, B62V_TOTAL));
|
||||
const part3 = Buffer.from(str.substring(B62V_TOTAL));
|
||||
const enc1 = base62Integer.encode(part1);
|
||||
const enc2 = base62Integer.encode(part2);
|
||||
const enc3 = base62String.encode(part3);
|
||||
return (B62V_EPAD + enc1).slice(-B62V_EPAD.length) +
|
||||
(B62V_EPAD + enc2).slice(-B62V_EPAD.length) +
|
||||
enc3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base62VersionId. May return an error if the input string is
|
||||
* invalid hex string or results in an invalid value.
|
||||
*
|
||||
* @param {string} str - the encoded base62VersionId to decode
|
||||
* @return {(string|Error)} - the decoded versionId or an error
|
||||
*/
|
||||
function base62Decode(str) {
|
||||
try {
|
||||
let start = 0;
|
||||
const enc1 = str.substring(start, start + B62V_EPAD.length);
|
||||
const orig1 = base62Integer.decode(enc1);
|
||||
start += B62V_EPAD.length;
|
||||
const enc2 = str.substring(start, start + B62V_EPAD.length);
|
||||
const orig2 = base62Integer.decode(enc2);
|
||||
start += B62V_EPAD.length;
|
||||
const enc3 = str.substring(start);
|
||||
const orig3 = base62String.decode(enc3);
|
||||
return (B62V_DPAD + orig1.toString()).slice(-B62V_DPAD.length) +
|
||||
(B62V_DPAD + orig2.toString()).slice(-B62V_DPAD.length) +
|
||||
orig3.toString();
|
||||
} catch (err) {
|
||||
// in case of exceptions caused by base62 libs
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
const ENC_TYPE_HEX = 0; // legacy (large) encoding
|
||||
const ENC_TYPE_BASE62 = 1; // new (tiny) encoding
|
||||
|
||||
/**
|
||||
* Encode a versionId to obscure internal information contained
|
||||
* in a version ID.
|
||||
*
|
||||
* @param {string} str - the versionId to encode
|
||||
* @param {Number} [encType=ENC_TYPE_HEX] - encode format
|
||||
* @return {string} - the encoded versionId
|
||||
*/
|
||||
function encode(str, encType = ENC_TYPE_HEX) {
|
||||
if (encType === ENC_TYPE_BASE62) {
|
||||
return base62Encode(str);
|
||||
}
|
||||
return hexEncode(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a versionId. May return an error if the input string is
|
||||
* invalid format or results in an invalid value. The function will
|
||||
* automatically determine the format acc/ to an heuristic.
|
||||
*
|
||||
* @param {string} str - the encoded versionId to decode
|
||||
* @return {(string|Error)} - the decoded versionId or an error
|
||||
*/
|
||||
function decode(str) {
|
||||
if (str.length < 40) {
|
||||
return base62Decode(str);
|
||||
}
|
||||
return hexDecode(str);
|
||||
}
|
||||
|
||||
module.exports = { generateVersionId, getInfVid,
|
||||
hexEncode, hexDecode,
|
||||
base62Encode, base62Decode,
|
||||
encode, decode,
|
||||
ENC_TYPE_HEX, ENC_TYPE_BASE62 };
|
||||
module.exports = { generateVersionId, getInfVid, encode, decode };
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"engines": {
|
||||
"node": ">=6.9.5"
|
||||
},
|
||||
"version": "7.10.3",
|
||||
"version": "7.4.12",
|
||||
"description": "Common utilities for the S3 project components",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
@ -20,10 +20,8 @@
|
|||
"@hapi/joi": "^15.1.0",
|
||||
"JSONStream": "^1.0.0",
|
||||
"agentkeepalive": "^4.1.3",
|
||||
"ajv": "8.8.1",
|
||||
"ajv": "6.12.2",
|
||||
"async": "~2.1.5",
|
||||
"base62": "2.0.1",
|
||||
"base-x": "3.0.8",
|
||||
"debug": "~2.6.9",
|
||||
"diskusage": "^1.1.1",
|
||||
"ioredis": "4.9.5",
|
||||
|
@ -50,7 +48,6 @@
|
|||
"eslint-config-scality": "scality/Guidelines#ec33dfb",
|
||||
"eslint-plugin-react": "^4.3.0",
|
||||
"mocha": "8.0.1",
|
||||
"sinon": "^9.0.2",
|
||||
"temp": "0.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
'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 TlsTransport =
|
||||
require('../../../lib/network/kmip/transport/tls.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);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should succeed healthcheck with working KMIP client and server', done => {
|
||||
const options = {
|
||||
kmip: {
|
||||
client: {
|
||||
bucketNameAttributeName: null,
|
||||
compoundCreateActivate: false,
|
||||
},
|
||||
codec: {},
|
||||
transport: {
|
||||
pipelineDepth: 8,
|
||||
tls: {
|
||||
port: 5696,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const kmipClient = new KMIPClient(options, TTLVCodec,
|
||||
LoopbackServerTransport);
|
||||
kmipClient.healthcheck(logger, err => {
|
||||
assert.ifError(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail healthcheck with KMIP server not running', done => {
|
||||
const options = {
|
||||
kmip: {
|
||||
client: {
|
||||
bucketNameAttributeName: null,
|
||||
compoundCreateActivate: false,
|
||||
},
|
||||
codec: {},
|
||||
transport: {
|
||||
pipelineDepth: 8,
|
||||
tls: {
|
||||
port: 5696,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const kmipClient = new KMIPClient(options, TTLVCodec, TlsTransport);
|
||||
kmipClient.healthcheck(logger, err => {
|
||||
assert(err);
|
||||
assert(err.InternalError);
|
||||
assert(err.description.includes('ECONNREFUSED'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
'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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const TransportTemplate =
|
||||
require('../../../lib/network/kmip/transport/TransportTemplate.js');
|
||||
const { logger } = require('../../utils/kmip/ersatz.js');
|
||||
|
||||
describe('KMIP Connection Management', () => {
|
||||
let server;
|
||||
before(done => {
|
||||
server = net.createServer(conn => {
|
||||
// abort the connection as soon as it is accepted
|
||||
conn.destroy();
|
||||
});
|
||||
server.listen(5696);
|
||||
server.on('listening', done);
|
||||
});
|
||||
after(done => {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
it('should gracefully handle connection errors', done => {
|
||||
const transport = new TransportTemplate(
|
||||
tls,
|
||||
{
|
||||
pipelineDepth: 1,
|
||||
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 */
|
||||
transport.send(logger, request, (err, conversation, response) => {
|
||||
assert(err);
|
||||
assert(!response);
|
||||
done();
|
||||
});
|
||||
transport.end();
|
||||
});
|
||||
});
|
|
@ -1,82 +0,0 @@
|
|||
'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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -65,63 +65,4 @@ describe('Basic listing algorithm', () => {
|
|||
Basic, { maxKeys: 1 }, logger);
|
||||
assert.deepStrictEqual(res, ['key1']);
|
||||
});
|
||||
|
||||
const attr1 = {
|
||||
key: 'key1',
|
||||
value: '{"foo": "bar"}',
|
||||
};
|
||||
const attr2 = {
|
||||
key: 'key2',
|
||||
value: '{"customAttributes": {"foo": "bar"}}',
|
||||
};
|
||||
const attr3 = {
|
||||
key: 'key3',
|
||||
value: `{"customAttributes": {
|
||||
"cd_tenant_id%3D%3D6a84c782-8766-11eb-b0a1-d7238b6e9579": "",
|
||||
"cd_tenant_id%3D%3Dc486659c-8761-11eb-87c2-8b0faea3c595": ""
|
||||
}}`,
|
||||
};
|
||||
const attr4 = {
|
||||
key: 'key4',
|
||||
value: `{"customAttributes": {
|
||||
"cd_tenant_id%3D%3D6a84c782-8766-11eb-b0a1-d7238b6e9579": ""
|
||||
}}`,
|
||||
};
|
||||
const input = [attr1, attr2, attr3, attr4];
|
||||
|
||||
it('Shall ignore custom attributes if no filter is specified', () => {
|
||||
const output = input;
|
||||
const res = performListing(
|
||||
input, Basic,
|
||||
{},
|
||||
logger);
|
||||
assert.deepStrictEqual(res, output);
|
||||
});
|
||||
|
||||
it('Shall report nothing if filter does not match', () => {
|
||||
const output = [];
|
||||
const res = performListing(
|
||||
input, Basic,
|
||||
{ filterKey: 'do not exist' },
|
||||
logger);
|
||||
assert.deepStrictEqual(res, output);
|
||||
});
|
||||
|
||||
it('Shall find key in custom attributes', () => {
|
||||
const output = [attr3];
|
||||
const res = performListing(
|
||||
input, Basic,
|
||||
{ filterKey: 'cd_tenant_id%3D%3Dc486659c-8761-11eb-87c2-8b0faea3c595' },
|
||||
logger);
|
||||
assert.deepStrictEqual(res, output);
|
||||
});
|
||||
|
||||
it('Shall find key starting with a prefix in custom attributes', () => {
|
||||
const output = [attr3, attr4];
|
||||
const res = performListing(
|
||||
input, Basic,
|
||||
{ filterKeyStartsWith: 'cd_tenant_id%3D%3D' },
|
||||
logger);
|
||||
assert.deepStrictEqual(res, output);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,11 +16,6 @@ const expectCanId =
|
|||
const searchEmail2 = 'sampleaccount4@sampling.com';
|
||||
const expectCanId2 = 'newCanId';
|
||||
|
||||
const searchCanId = '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be';
|
||||
const expectAccountId = '123456789012';
|
||||
|
||||
const invalidAccountId = 'doesnotexist';
|
||||
|
||||
describe('S3 in_memory auth backend', () => {
|
||||
it('should find an account', done => {
|
||||
const backend = new Backend(JSON.parse(JSON.stringify(ref)));
|
||||
|
@ -31,26 +26,6 @@ describe('S3 in_memory auth backend', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should find an accounts accountId from canonicalId', done => {
|
||||
const backend = new Backend(JSON.parse(JSON.stringify(ref)));
|
||||
backend.getAccountIds([searchCanId], log, (err, res) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(res.message.body[searchCanId],
|
||||
expectAccountId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return "Not Found" for missing accounts', done => {
|
||||
const backend = new Backend(JSON.parse(JSON.stringify(ref)));
|
||||
backend.getAccountIds([invalidAccountId], log, (err, res) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(res.message.body[invalidAccountId],
|
||||
'Not Found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear old account authdata on refresh', done => {
|
||||
const backend = new Backend(JSON.parse(JSON.stringify(ref)));
|
||||
backend.refreshAuthData(obj2);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const queryAuthCheck =
|
||||
require('../../../../lib/auth/v2/queryAuthCheck').check;
|
||||
|
@ -27,97 +26,3 @@ 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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
'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,56 +115,8 @@ const testLifecycleConfiguration = {
|
|||
},
|
||||
],
|
||||
};
|
||||
|
||||
const testUid = '99ae3446-7082-4c17-ac97-52965dc004ec';
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
const testNotificationConfiguration = {
|
||||
queueConfig: [
|
||||
{
|
||||
events: ['s3:ObjectCreated:*'],
|
||||
queueArn: 'arn:scality:bucketnotif:::target1',
|
||||
filterRules: [
|
||||
{
|
||||
name: 'prefix',
|
||||
value: 'logs/',
|
||||
},
|
||||
{
|
||||
name: 'suffix',
|
||||
value: '.log',
|
||||
},
|
||||
],
|
||||
id: 'test-queue-config-1',
|
||||
},
|
||||
{
|
||||
events: ['s3:ObjectRemoved:Delete', 's3:ObjectCreated:Copy'],
|
||||
queueArn: 'arn:scality:bucketnotif:::target2',
|
||||
id: 'test-queue-config-2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// create a dummy bucket to test getters and setters
|
||||
|
||||
Object.keys(acl).forEach(
|
||||
aclObj => describe(`different acl configurations : ${aclObj}`, () => {
|
||||
const dummyBucket = new BucketInfo(
|
||||
|
@ -180,12 +132,7 @@ Object.keys(acl).forEach(
|
|||
testWebsiteConfiguration,
|
||||
testCorsConfiguration,
|
||||
testReplicationConfiguration,
|
||||
testLifecycleConfiguration,
|
||||
testBucketPolicy,
|
||||
testUid,
|
||||
testobjectLockEnabled,
|
||||
testObjectLockConfiguration,
|
||||
testNotificationConfiguration);
|
||||
testLifecycleConfiguration);
|
||||
|
||||
describe('serialize/deSerialize on BucketInfo class', () => {
|
||||
const serialized = dummyBucket.serialize();
|
||||
|
@ -211,12 +158,6 @@ Object.keys(acl).forEach(
|
|||
dummyBucket._replicationConfiguration,
|
||||
lifecycleConfiguration:
|
||||
dummyBucket._lifecycleConfiguration,
|
||||
bucketPolicy: dummyBucket._bucketPolicy,
|
||||
uid: dummyBucket._uid,
|
||||
objectLockEnabled: dummyBucket._objectLockEnabled,
|
||||
objectLockConfiguration:
|
||||
dummyBucket._objectLockConfiguration,
|
||||
notificationConfiguration: dummyBucket._notificationConfiguration,
|
||||
};
|
||||
assert.strictEqual(serialized, JSON.stringify(bucketInfos));
|
||||
done();
|
||||
|
@ -233,16 +174,15 @@ Object.keys(acl).forEach(
|
|||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('this should have the right BucketInfo types', () => {
|
||||
assert.strictEqual(typeof dummyBucket.getName(), 'string');
|
||||
assert.strictEqual(typeof dummyBucket.getOwner(), 'string');
|
||||
assert.strictEqual(typeof dummyBucket.getOwnerDisplayName(),
|
||||
'string');
|
||||
assert.strictEqual(typeof dummyBucket.getCreationDate(),
|
||||
'string');
|
||||
assert.strictEqual(typeof dummyBucket.isObjectLockEnabled(),
|
||||
'boolean');
|
||||
});
|
||||
it('this should have the right BucketInfo types',
|
||||
() => {
|
||||
assert.strictEqual(typeof dummyBucket.getName(), 'string');
|
||||
assert.strictEqual(typeof dummyBucket.getOwner(), 'string');
|
||||
assert.strictEqual(typeof dummyBucket.getOwnerDisplayName(),
|
||||
'string');
|
||||
assert.strictEqual(typeof dummyBucket.getCreationDate(),
|
||||
'string');
|
||||
});
|
||||
it('this should have the right acl\'s types', () => {
|
||||
assert.strictEqual(typeof dummyBucket.getAcl(), 'object');
|
||||
assert.strictEqual(
|
||||
|
@ -317,25 +257,6 @@ Object.keys(acl).forEach(
|
|||
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
||||
testLifecycleConfiguration);
|
||||
});
|
||||
it('getBucketPolicy should return policy', () => {
|
||||
assert.deepStrictEqual(
|
||||
dummyBucket.getBucketPolicy(), testBucketPolicy);
|
||||
});
|
||||
it('getUid should return unique id of bucket', () => {
|
||||
assert.deepStrictEqual(dummyBucket.getUid(), testUid);
|
||||
});
|
||||
it('object lock should be disabled by default', () => {
|
||||
assert.deepStrictEqual(
|
||||
dummyBucket.isObjectLockEnabled(), false);
|
||||
});
|
||||
it('getObjectLockConfiguration should return configuration', () => {
|
||||
assert.deepStrictEqual(dummyBucket.getObjectLockConfiguration(),
|
||||
testObjectLockConfiguration);
|
||||
});
|
||||
it('getNotificationConfiguration should return configuration', () => {
|
||||
assert.deepStrictEqual(dummyBucket.getNotificationConfiguration(),
|
||||
testNotificationConfiguration);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setters on BucketInfo class', () => {
|
||||
|
@ -457,67 +378,6 @@ Object.keys(acl).forEach(
|
|||
assert.deepStrictEqual(dummyBucket.getLifecycleConfiguration(),
|
||||
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);
|
||||
});
|
||||
[true, false].forEach(bool => {
|
||||
it('setObjectLockEnabled should set object lock status', () => {
|
||||
dummyBucket.setObjectLockEnabled(bool);
|
||||
assert.deepStrictEqual(dummyBucket.isObjectLockEnabled(),
|
||||
bool);
|
||||
});
|
||||
});
|
||||
it('setNotificationConfiguration should set notification configuration', () => {
|
||||
const newNotifConfig = {
|
||||
queueConfig: [
|
||||
{
|
||||
events: ['s3:ObjectRemoved:*'],
|
||||
queueArn: 'arn:scality:bucketnotif:::target3',
|
||||
filterRules: [
|
||||
{
|
||||
name: 'prefix',
|
||||
value: 'configs/',
|
||||
},
|
||||
],
|
||||
id: 'test-config-3',
|
||||
},
|
||||
],
|
||||
};
|
||||
dummyBucket.setNotificationConfiguration(newNotifConfig);
|
||||
assert.deepStrictEqual(
|
||||
dummyBucket.getNotificationConfiguration(), newNotifConfig);
|
||||
});
|
||||
it('setUid should set bucket uid', () => {
|
||||
const testUid = '7751ec04-da87-44a1-99b4-95ebb345d40e';
|
||||
dummyBucket.setUid(testUid);
|
||||
assert.deepStrictEqual(
|
||||
dummyBucket.getUid(), testUid);
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
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();
|
||||
});
|
||||
});
|
|
@ -112,11 +112,7 @@ const invalidFilters = [
|
|||
{ tag: 'Tag', label: 'no-value', error: 'MissingRequiredParameter',
|
||||
errMessage: 'Tag XML does not contain both Key and Value' },
|
||||
{ tag: 'Tag', label: 'key-too-long', error: 'InvalidRequest',
|
||||
errMessage: 'A Tag\'s Key must be a length between 1 and 128' },
|
||||
{ tag: 'Tag', label: 'value-too-long', error: 'InvalidRequest',
|
||||
errMessage: 'A Tag\'s Value must be a length between 0 and 256' },
|
||||
{ tag: 'Tag', label: 'prefix-too-long', error: 'InvalidRequest',
|
||||
errMessage: 'The maximum size of a prefix is 1024' }];
|
||||
errMessage: 'Tag Key must be a length between 1 and 128 char' }];
|
||||
|
||||
function generateAction(errorTag, tagObj) {
|
||||
const xmlObj = {};
|
||||
|
@ -162,9 +158,6 @@ function generateFilter(errorTag, tagObj) {
|
|||
if (tagObj.label === 'only-prefix') {
|
||||
middleTags = '<And><Prefix></Prefix></And>';
|
||||
}
|
||||
if (tagObj.label === 'empty-prefix') {
|
||||
middleTags = '<Prefix></Prefix>';
|
||||
}
|
||||
if (tagObj.label === 'single-tag') {
|
||||
middleTags = '<And><Tags><Key>fo</Key><Value></Value></Tags></And>';
|
||||
}
|
||||
|
@ -178,26 +171,10 @@ function generateFilter(errorTag, tagObj) {
|
|||
const longKey = 'a'.repeat(129);
|
||||
middleTags = `<Tag><Key>${longKey}</Key><Value></Value></Tag>`;
|
||||
}
|
||||
if (tagObj.label === 'value-too-long') {
|
||||
const longValue = 'b'.repeat(257);
|
||||
middleTags = `<Tag><Key>a</Key><Value>${longValue}</Value></Tag>`;
|
||||
}
|
||||
if (tagObj.label === 'prefix-too-long') {
|
||||
const longValue = 'a'.repeat(1025);
|
||||
middleTags = `<Prefix>${longValue}</Prefix>`;
|
||||
}
|
||||
if (tagObj.label === 'mult-prefixes') {
|
||||
middleTags = '<Prefix>foo</Prefix><Prefix>bar</Prefix>' +
|
||||
`<Prefix>${tagObj.lastPrefix}</Prefix>`;
|
||||
}
|
||||
if (tagObj.label === 'mult-tags') {
|
||||
middleTags = '<And><Tag><Key>color</Key><Value>blue</Value></Tag>' +
|
||||
'<Tag><Key>shape</Key><Value>circle</Value></Tag></And>';
|
||||
}
|
||||
if (tagObj.label === 'not-unique-key-tag') {
|
||||
middleTags = '<And><Tag><Key>color</Key><Value>blue</Value></Tag>' +
|
||||
'<Tag><Key>color</Key><Value>red</Value></Tag></And>';
|
||||
}
|
||||
Filter = `<Filter>${middleTags}</Filter>`;
|
||||
if (tagObj.label === 'also-prefix') {
|
||||
Filter = '<Filter></Filter><Prefix></Prefix>';
|
||||
|
@ -372,24 +349,4 @@ describe('LifecycleConfiguration class getLifecycleConfiguration', () => {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return InvalidRequest is tag key is not unique', done => {
|
||||
tagObj.label = 'not-unique-key-tag';
|
||||
const errMessage = 'Tag Keys must be unique';
|
||||
generateParsedXml('Filter', tagObj, parsedXml => {
|
||||
checkError(parsedXml, 'InvalidRequest', errMessage, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include prefix in the response even if it is an empty string', done => {
|
||||
tagObj.label = 'empty-prefix';
|
||||
const expectedPrefix = '';
|
||||
generateParsedXml('Filter', tagObj, parsedXml => {
|
||||
const lcConfig = new LifecycleConfiguration(parsedXml).
|
||||
getLifecycleConfiguration();
|
||||
assert.strictEqual(expectedPrefix,
|
||||
lcConfig.rules[0].filter.rulePrefix);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const { parseString } = require('xml2js');
|
||||
|
||||
const NotificationConfiguration =
|
||||
require('../../../lib/models/NotificationConfiguration.js');
|
||||
|
||||
function checkError(parsedXml, err, errMessage, cb) {
|
||||
const config = new NotificationConfiguration(parsedXml).
|
||||
getValidatedNotificationConfiguration();
|
||||
assert.strictEqual(config.error[err], true);
|
||||
assert.strictEqual(config.error.description, errMessage);
|
||||
cb();
|
||||
}
|
||||
|
||||
function generateEvent(testParams) {
|
||||
const event = [];
|
||||
if (testParams.key === 'Event') {
|
||||
if (Array.isArray(testParams.value)) {
|
||||
testParams.value.forEach(v => {
|
||||
event.push(`${event}<Event>${v}</Event>`);
|
||||
});
|
||||
} else {
|
||||
event.push(`<Event>${testParams.value}</Event>`);
|
||||
}
|
||||
} else {
|
||||
event.push('<Event>s3:ObjectCreated:*</Event>');
|
||||
}
|
||||
return event.join('');
|
||||
}
|
||||
|
||||
function generateFilter(testParams) {
|
||||
let filter = '';
|
||||
if (testParams.key === 'Filter') {
|
||||
filter = `<Filter>${testParams.value}</Filter>`;
|
||||
}
|
||||
if (testParams.key === 'S3Key') {
|
||||
filter = `<Filter><S3Key>${testParams.value}</S3Key></Filter>`;
|
||||
}
|
||||
if (testParams.key === 'FilterRule') {
|
||||
if (Array.isArray(testParams.value)) {
|
||||
testParams.value.forEach(v => {
|
||||
filter = `${filter}<Filter><S3Key><FilterRule>${v}` +
|
||||
'</FilterRule></S3Key></Filter>';
|
||||
});
|
||||
} else {
|
||||
filter = `<Filter><S3Key><FilterRule>${testParams.value}` +
|
||||
'</FilterRule></S3Key></Filter>';
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
function generateXml(testParams) {
|
||||
const id = testParams.key === 'Id' ? `<Id>${testParams.value}</Id>` : '<Id>queue-id</Id>';
|
||||
const arn = testParams.key === 'QueueArn' ?
|
||||
`<Queue>${testParams.value}</Queue>` :
|
||||
'<Queue>arn:scality:bucketnotif:::target</Queue>';
|
||||
const event = generateEvent(testParams);
|
||||
const filter = generateFilter(testParams);
|
||||
let queueConfig = `<QueueConfiguration>${id}${arn}${event}${filter}` +
|
||||
'</QueueConfiguration>';
|
||||
if (testParams.key === 'QueueConfiguration') {
|
||||
if (testParams.value === 'double') {
|
||||
queueConfig = `${queueConfig}${queueConfig}`;
|
||||
} else {
|
||||
queueConfig = testParams.value;
|
||||
}
|
||||
}
|
||||
const notification = testParams.key === 'NotificationConfiguration' ? '' :
|
||||
`<NotificationConfiguration>${queueConfig}</NotificationConfiguration>`;
|
||||
return notification;
|
||||
}
|
||||
|
||||
function generateParsedXml(testParams, cb) {
|
||||
const xml = generateXml(testParams);
|
||||
parseString(xml, (err, parsedXml) => {
|
||||
assert.equal(err, null, 'Error parsing xml');
|
||||
cb(parsedXml);
|
||||
});
|
||||
}
|
||||
|
||||
const failTests = [
|
||||
{
|
||||
name: 'fail with empty configuration',
|
||||
params: { key: 'NotificationConfiguration' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'request xml is undefined or empty',
|
||||
},
|
||||
{
|
||||
name: 'fail with invalid id',
|
||||
params: { key: 'Id', value: 'a'.repeat(256) },
|
||||
error: 'InvalidArgument',
|
||||
errorMessage: 'queue configuration ID is greater than 255 characters long',
|
||||
},
|
||||
{
|
||||
name: 'fail with repeated id',
|
||||
params: { key: 'QueueConfiguration', value: 'double' },
|
||||
error: 'InvalidRequest',
|
||||
errorMessage: 'queue configuration ID must be unique',
|
||||
},
|
||||
{
|
||||
name: 'fail with empty QueueArn',
|
||||
params: { key: 'QueueArn', value: '' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'each queue configuration must contain a queue arn',
|
||||
},
|
||||
{
|
||||
name: 'fail with invalid QueueArn',
|
||||
params: { key: 'QueueArn', value: 'arn:scality:bucketnotif:target' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'queue arn is invalid',
|
||||
},
|
||||
{
|
||||
name: 'fail with invalid QueueArn partition',
|
||||
params: { key: 'QueueArn', value: 'arn:aws:bucketnotif:::target' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'queue arn is invalid',
|
||||
},
|
||||
{
|
||||
name: 'fail with empty event',
|
||||
params: { key: 'Event', value: '' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'each queue configuration must contain an event',
|
||||
},
|
||||
{
|
||||
name: 'fail with invalid event',
|
||||
params: { key: 'Event', value: 's3:BucketCreated:Put' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'event array contains invalid or unsupported event',
|
||||
},
|
||||
{
|
||||
name: 'fail with unsupported event',
|
||||
params: { key: 'Event', value: 's3:Replication:OperationNotTracked' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'event array contains invalid or unsupported event',
|
||||
},
|
||||
{
|
||||
name: 'fail with filter that does not contain S3Key',
|
||||
params: { key: 'Filter', value: '<FilterRule><Name>Prefix</Name><Value>logs/</Value></FilterRule>' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'if included, queue configuration filter must contain S3Key',
|
||||
},
|
||||
{
|
||||
name: 'fail with filter that does not contain a rule',
|
||||
params: { key: 'S3Key', value: '<Name>Prefix</Name><Value>logs/</Value>' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'if included, queue configuration filter must contain a rule',
|
||||
},
|
||||
{
|
||||
name: 'fail with filter rule that does not contain name and value',
|
||||
params: { key: 'FilterRule', value: '<Value>noname</Value>' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'each included filter must contain a name and value',
|
||||
},
|
||||
{
|
||||
name: 'fail with invalid name in filter rule',
|
||||
params: { key: 'FilterRule', value: '<Name>Invalid</Name><Value>logs/</Value>' },
|
||||
error: 'MalformedXML',
|
||||
errorMessage: 'filter Name must be one of Prefix or Suffix',
|
||||
},
|
||||
];
|
||||
|
||||
const passTests = [
|
||||
{
|
||||
name: 'pass with empty QueueConfiguration',
|
||||
params: { key: 'QueueConfiguration', value: '[]' },
|
||||
},
|
||||
{
|
||||
name: 'pass with multiple events in one queue configuration',
|
||||
params: {
|
||||
key: 'Event', value: ['s3:ObjectCreated:Put', 's3:ObjectCreated:Copy'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pass with multiple filter rules',
|
||||
params: {
|
||||
key: 'FilterRule',
|
||||
value: ['<Name>Prefix</Name><Value>logs/</Value>', '<Name>Suffix</Name><Value>.pdf</Value>'] },
|
||||
},
|
||||
{
|
||||
name: 'pass with no id',
|
||||
params: { key: 'Id', value: '' },
|
||||
},
|
||||
{
|
||||
name: 'pass with basic config', params: {},
|
||||
},
|
||||
];
|
||||
|
||||
describe('NotificationConfiguration class getValidatedNotificationConfiguration',
|
||||
() => {
|
||||
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 NotificationConfiguration(xml).
|
||||
getValidatedNotificationConfiguration();
|
||||
assert.ifError(config.error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,265 +0,0 @@
|
|||
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>`;
|
||||
}
|
||||
if (testParams.key === 'NoRule') {
|
||||
return '';
|
||||
}
|
||||
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 },
|
||||
},
|
||||
{
|
||||
name: 'pass without Rule',
|
||||
params: { key: 'NoRule' },
|
||||
},
|
||||
];
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
const assert = require('assert');
|
||||
const ObjectMDLocation = require('../../../lib/models/ObjectMDLocation');
|
||||
|
||||
describe('ObjectMDLocation', () => {
|
||||
it('class getters/setters', () => {
|
||||
const locValue = {
|
||||
key: 'fookey',
|
||||
start: 42,
|
||||
size: 100,
|
||||
dataStoreName: 'awsbackend',
|
||||
dataStoreETag: '2:abcdefghi',
|
||||
cryptoScheme: 1,
|
||||
cipheredDataKey: 'CiPhErEdDaTaKeY',
|
||||
};
|
||||
const location = new ObjectMDLocation(locValue);
|
||||
assert.strictEqual(location.getKey(), 'fookey');
|
||||
assert.strictEqual(location.getDataStoreName(), 'awsbackend');
|
||||
assert.strictEqual(location.getDataStoreETag(), '2:abcdefghi');
|
||||
assert.strictEqual(location.getPartNumber(), 2);
|
||||
assert.strictEqual(location.getPartETag(), 'abcdefghi');
|
||||
assert.strictEqual(location.getPartStart(), 42);
|
||||
assert.strictEqual(location.getPartSize(), 100);
|
||||
assert.strictEqual(location.getCryptoScheme(), 1);
|
||||
assert.strictEqual(location.getCipheredDataKey(), 'CiPhErEdDaTaKeY');
|
||||
|
||||
assert.deepStrictEqual(location.getValue(), locValue);
|
||||
|
||||
location.setPartSize(200);
|
||||
assert.strictEqual(location.getPartSize(), 200);
|
||||
});
|
||||
|
||||
it('ObjectMDLocation::setDataLocation()', () => {
|
||||
const location = new ObjectMDLocation({
|
||||
key: 'fookey',
|
||||
start: 42,
|
||||
size: 100,
|
||||
dataStoreName: 'awsbackend',
|
||||
dataStoreETag: '2:abcdefghi',
|
||||
cryptoScheme: 1,
|
||||
cipheredDataKey: 'CiPhErEdDaTaKeY',
|
||||
});
|
||||
location.setDataLocation({ key: 'secondkey',
|
||||
dataStoreName: 'gcpbackend' });
|
||||
assert.strictEqual(location.getKey(), 'secondkey');
|
||||
assert.strictEqual(location.getDataStoreName(), 'gcpbackend');
|
||||
assert.strictEqual(location.getCryptoScheme(), undefined);
|
||||
assert.strictEqual(location.getCipheredDataKey(), undefined);
|
||||
assert.deepStrictEqual(location.getValue(), {
|
||||
dataStoreETag: '2:abcdefghi',
|
||||
dataStoreName: 'gcpbackend',
|
||||
key: 'secondkey',
|
||||
size: 100,
|
||||
start: 42,
|
||||
});
|
||||
location.setDataLocation({ key: 'thirdkey',
|
||||
dataStoreName: 'azurebackend',
|
||||
cryptoScheme: 1,
|
||||
cipheredDataKey: 'NeWcIpHeReDdAtAkEy' });
|
||||
assert.strictEqual(location.getKey(), 'thirdkey');
|
||||
assert.strictEqual(location.getDataStoreName(), 'azurebackend');
|
||||
assert.strictEqual(location.getCryptoScheme(), 1);
|
||||
assert.strictEqual(location.getCipheredDataKey(), 'NeWcIpHeReDdAtAkEy');
|
||||
});
|
||||
});
|
|
@ -2,11 +2,6 @@ const assert = require('assert');
|
|||
const ObjectMD = require('../../../lib/models/ObjectMD');
|
||||
const constants = require('../../../lib/constants');
|
||||
|
||||
const retainDate = new Date();
|
||||
retainDate.setDate(retainDate.getDate() + 1);
|
||||
const laterDate = new Date();
|
||||
laterDate.setDate(laterDate.getDate() + 5);
|
||||
|
||||
describe('ObjectMD class setters/getters', () => {
|
||||
let md = null;
|
||||
|
||||
|
@ -106,11 +101,6 @@ describe('ObjectMD class setters/getters', () => {
|
|||
dataStoreVersionId: '',
|
||||
}],
|
||||
['DataStoreName', null, ''],
|
||||
['LegalHold', null, false],
|
||||
['LegalHold', true],
|
||||
['RetentionMode', 'GOVERNANCE'],
|
||||
['RetentionDate', retainDate.toISOString()],
|
||||
['OriginOp', null, ''],
|
||||
].forEach(test => {
|
||||
const property = test[0];
|
||||
const testValue = test[1];
|
||||
|
@ -204,21 +194,6 @@ describe('ObjectMD class setters/getters', () => {
|
|||
assert.strictEqual(
|
||||
md.getReplicationSiteDataStoreVersionId('zenko'), 'a');
|
||||
});
|
||||
|
||||
it('ObjectMD::set/getRetentionMode', () => {
|
||||
md.setRetentionMode('COMPLIANCE');
|
||||
assert.deepStrictEqual(md.getRetentionMode(), 'COMPLIANCE');
|
||||
});
|
||||
|
||||
it('ObjectMD::set/getRetentionDate', () => {
|
||||
md.setRetentionDate(laterDate.toISOString());
|
||||
assert.deepStrictEqual(md.getRetentionDate(), laterDate.toISOString());
|
||||
});
|
||||
|
||||
it('ObjectMD::set/getOriginOp', () => {
|
||||
md.setOriginOp('Copy');
|
||||
assert.deepStrictEqual(md.getOriginOp(), 'Copy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ObjectMD import from stored blob', () => {
|
||||
|
@ -341,9 +316,7 @@ describe('getAttributes static method', () => {
|
|||
'replicationInfo': true,
|
||||
'dataStoreName': true,
|
||||
'last-modified': true,
|
||||
'md-model-version': true,
|
||||
'originOp': true,
|
||||
};
|
||||
'md-model-version': true };
|
||||
assert.deepStrictEqual(attributes, expectedResult);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,9 +4,8 @@ const assert = require('assert');
|
|||
const policyValidator = require('../../../lib/policy/policyValidator');
|
||||
const errors = require('../../../lib/errors');
|
||||
const validateUserPolicy = policyValidator.validateUserPolicy;
|
||||
const validateResourcePolicy = policyValidator.validateResourcePolicy;
|
||||
const successRes = { error: null, valid: true };
|
||||
const sampleUserPolicy = {
|
||||
const samplePolicy = {
|
||||
Version: '2012-10-17',
|
||||
Statement: {
|
||||
Sid: 'FooBar1234',
|
||||
|
@ -16,19 +15,6 @@ const sampleUserPolicy = {
|
|||
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 = {
|
||||
required: {
|
||||
|
@ -44,84 +30,45 @@ const errDict = {
|
|||
Resource: 'Policy statement must contain resources.',
|
||||
},
|
||||
};
|
||||
let policy;
|
||||
|
||||
function failRes(policyType, errDescription) {
|
||||
let error;
|
||||
if (policyType === 'user') {
|
||||
error = Object.assign({}, errors.MalformedPolicyDocument);
|
||||
}
|
||||
if (policyType === 'resource') {
|
||||
error = Object.assign({}, errors.MalformedPolicy);
|
||||
}
|
||||
function failRes(errDescription) {
|
||||
const error = Object.assign({}, errors.MalformedPolicyDocument);
|
||||
error.description = errDescription || error.description;
|
||||
return { error, valid: false };
|
||||
}
|
||||
|
||||
function check(input, expected, policyType) {
|
||||
let result;
|
||||
if (policyType === 'user') {
|
||||
result = validateUserPolicy(JSON.stringify(input));
|
||||
}
|
||||
if (policyType === 'resource') {
|
||||
result = validateResourcePolicy(JSON.stringify(input));
|
||||
}
|
||||
function check(input, expected) {
|
||||
const result = validateUserPolicy(JSON.stringify(input));
|
||||
assert.deepStrictEqual(result, expected);
|
||||
}
|
||||
|
||||
let userPolicy;
|
||||
let resourcePolicy;
|
||||
const user = 'user';
|
||||
const resource = 'resource';
|
||||
|
||||
beforeEach(() => {
|
||||
userPolicy = JSON.parse(JSON.stringify(sampleUserPolicy));
|
||||
resourcePolicy = JSON.parse(JSON.stringify(sampleResourcePolicy));
|
||||
policy = JSON.parse(JSON.stringify(samplePolicy));
|
||||
});
|
||||
|
||||
describe('Policies validation - Invalid JSON', () => {
|
||||
it('should return error for invalid user policy JSON', () => {
|
||||
it('should return error for invalid JSON', () => {
|
||||
const result = validateUserPolicy('{"Version":"2012-10-17",' +
|
||||
'"Statement":{"Effect":"Allow""Action":"s3:PutObject",' +
|
||||
'"Resource":"arn:aws:s3*"}}');
|
||||
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));
|
||||
assert.deepStrictEqual(result, failRes());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Policies validation - Version', () => {
|
||||
it('should validate user policy with version date 2012-10-17', () => {
|
||||
check(userPolicy, successRes, user);
|
||||
it('should validate with version date 2012-10-17', () => {
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('should validate resource policy with version date 2012-10-17', () => {
|
||||
check(resourcePolicy, successRes, 'resource');
|
||||
it('should return error for other dates', () => {
|
||||
policy.Version = '2012-11-17';
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
it('user policy should return error for other dates', () => {
|
||||
userPolicy.Version = '2012-11-17';
|
||||
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);
|
||||
it('should return error if Version field is missing', () => {
|
||||
policy.Version = undefined;
|
||||
check(policy, failRes(errDict.required.Version));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -130,24 +77,20 @@ describe('Policies validation - Principal', () => {
|
|||
{
|
||||
name: 'an account id',
|
||||
value: { AWS: '111111111111' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'anonymous user AWS form',
|
||||
value: { AWS: '*' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'an account arn',
|
||||
value: { AWS: 'arn:aws:iam::111111111111:root' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple account id',
|
||||
value: {
|
||||
AWS: ['111111111111', '111111111112'],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple account arn',
|
||||
|
@ -157,22 +100,14 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::111111111112:root',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'anonymous user as string',
|
||||
value: '*',
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'user arn',
|
||||
value: { AWS: 'arn:aws:iam::111111111111:user/alex' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'user arn with path',
|
||||
value: { AWS: 'arn:aws:iam::111111111111:user/path/in/org/leaf' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple user arns',
|
||||
|
@ -182,14 +117,12 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::111111111111:user/thibault',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'role arn',
|
||||
value: {
|
||||
AWS: 'arn:aws:iam::111111111111:role/dev',
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple role arn',
|
||||
|
@ -199,7 +132,6 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::111111111111:role/prod',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'saml provider',
|
||||
|
@ -207,84 +139,57 @@ describe('Policies validation - Principal', () => {
|
|||
Federated:
|
||||
'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
||||
},
|
||||
policyType: [user],
|
||||
},
|
||||
{
|
||||
name: 'with backbeat service',
|
||||
value: { Service: 'backbeat' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'with canonical user id',
|
||||
value: { CanonicalUser:
|
||||
'1examplecanonicalid12345678909876' +
|
||||
'54321qwerty12345asdfg67890z1x2c' },
|
||||
policyType: [resource],
|
||||
},
|
||||
].forEach(test => {
|
||||
if (test.policyType.includes(user)) {
|
||||
it(`should allow user policy principal field with ${test.name}`,
|
||||
() => {
|
||||
userPolicy.Statement.Principal = test.value;
|
||||
delete userPolicy.Statement.Resource;
|
||||
check(userPolicy, successRes, user);
|
||||
});
|
||||
it(`should allow principal field with ${test.name}`, () => {
|
||||
policy.Statement.Principal = test.value;
|
||||
delete policy.Statement.Resource;
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it(`should allow user policy notPrincipal field with ${test.name}`,
|
||||
() => {
|
||||
userPolicy.Statement.NotPrincipal = test.value;
|
||||
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);
|
||||
});
|
||||
}
|
||||
it(`shoud allow notPrincipal field with ${test.name}`, () => {
|
||||
policy.Statement.NotPrincipal = test.value;
|
||||
delete policy.Statement.Resource;
|
||||
check(policy, successRes);
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
name: 'wrong format account id',
|
||||
value: { AWS: '11111111111z' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'empty string',
|
||||
value: '',
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'anonymous user federated form',
|
||||
value: { federated: '*' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'wildcard in resource',
|
||||
name: 'wildcard in ressource',
|
||||
value: { AWS: 'arn:aws:iam::111111111111:user/*' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'a malformed account arn',
|
||||
value: { AWS: 'arn:aws:iam::111111111111:' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple malformed account id',
|
||||
value: {
|
||||
AWS: ['1111111111z1', '1111z1111112'],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple anonymous',
|
||||
value: {
|
||||
AWS: ['*', '*'],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple malformed account arn',
|
||||
|
@ -294,22 +199,18 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::111111111112:',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'account id as a string',
|
||||
value: '111111111111',
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'account arn as a string',
|
||||
value: 'arn:aws:iam::111111111111:root',
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'user arn as a string',
|
||||
value: 'arn:aws:iam::111111111111:user/alex',
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple malformed user arns',
|
||||
|
@ -319,14 +220,12 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::111111111111:user/',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'malformed role arn',
|
||||
value: {
|
||||
AWS: 'arn:aws:iam::111111111111:role/',
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'multiple malformed role arn',
|
||||
|
@ -336,84 +235,36 @@ describe('Policies validation - Principal', () => {
|
|||
'arn:aws:iam::11111111z111:role/prod',
|
||||
],
|
||||
},
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'saml provider as a string',
|
||||
value: 'arn:aws:iam::111111111111:saml-provider/mysamlprovider',
|
||||
policyType: [user],
|
||||
},
|
||||
{
|
||||
name: 'with other service than backbeat',
|
||||
value: { Service: 'non-existent-service' },
|
||||
policyType: [user, resource],
|
||||
},
|
||||
{
|
||||
name: 'invalid canonical user',
|
||||
value: { CanonicalUser:
|
||||
'12345invalid-canonical-id$$$//098' +
|
||||
'7654321poiu1q2w3e4r5t6y7u8i9o0p' },
|
||||
policyType: [resource],
|
||||
},
|
||||
].forEach(test => {
|
||||
if (test.policyType.includes(user)) {
|
||||
it(`user policy should fail with ${test.name}`, () => {
|
||||
userPolicy.Statement.Principal = test.value;
|
||||
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 fail with ${test.name}`, () => {
|
||||
policy.Statement.Principal = test.value;
|
||||
delete policy.Statement.Resource;
|
||||
check(policy, failRes());
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow Resource field', () => {
|
||||
userPolicy.Statement.Principal = '*';
|
||||
check(userPolicy, failRes(user), user);
|
||||
policy.Statement.Principal = '*';
|
||||
check(policy, failRes());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Policies validation - Statement', () => {
|
||||
[
|
||||
{
|
||||
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 object', () => {
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('user policy should succeed for a valid object', () => {
|
||||
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 = [
|
||||
it('should succeed for a valid array', () => {
|
||||
policy.Statement = [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Action: 's3:PutObject',
|
||||
|
@ -425,373 +276,255 @@ describe('Policies validation - Statement', () => {
|
|||
Resource: 'arn:aws:s3:::my_bucket/uploads/widgetco/*',
|
||||
},
|
||||
];
|
||||
check(userPolicy, successRes, user);
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('resource policy should succeed for a valid object', () => {
|
||||
resourcePolicy.Statement = [
|
||||
{
|
||||
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 undefined', () => {
|
||||
policy.Statement = undefined;
|
||||
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 list', () => {
|
||||
policy.Statement = [];
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
it(`resource policy ${test.name}`, () => {
|
||||
test.toDelete.forEach(p => delete resourcePolicy.Statement[0][p]);
|
||||
if (test.expected === 'fail') {
|
||||
check(resourcePolicy, failRes(resource, test.errMessage),
|
||||
resource);
|
||||
} else {
|
||||
check(resourcePolicy, test.expected, resource);
|
||||
}
|
||||
});
|
||||
it('should return an error for an empty object', () => {
|
||||
policy.Statement = {};
|
||||
check(policy, failRes(errDict.required.Action));
|
||||
});
|
||||
|
||||
it('should return an error for missing a required field - Action', () => {
|
||||
delete policy.Statement.Action;
|
||||
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', () => {
|
||||
it('user policy should succeed if Sid is any alphanumeric string', () => {
|
||||
check(userPolicy, successRes, user);
|
||||
it('should succeed if Sid is any alphanumeric string', () => {
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('resource policy should succeed if Sid is any alphanumeric string',
|
||||
() => {
|
||||
check(resourcePolicy, successRes, resource);
|
||||
it('should fail if Sid is not a valid format', () => {
|
||||
policy.Statement.Sid = 'foo bar()';
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
it('user policy should fail if Sid is not a valid format', () => {
|
||||
userPolicy.Statement.Sid = 'foo bar()';
|
||||
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);
|
||||
it('should fail if Sid is not a string', () => {
|
||||
policy.Statement.Sid = 1234;
|
||||
check(policy, failRes());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Policies validation - Statement::Effect_block', () => {
|
||||
it('user policy should succeed for Allow', () => {
|
||||
check(userPolicy, successRes, user);
|
||||
it('should succeed for Allow', () => {
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('resource policy should succeed for Allow', () => {
|
||||
check(resourcePolicy, successRes, resource);
|
||||
it('should succeed for Deny', () => {
|
||||
policy.Statement.Effect = 'Deny';
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('user policy should succeed for Deny', () => {
|
||||
userPolicy.Statement.Effect = 'Deny';
|
||||
check(userPolicy, successRes, user);
|
||||
it('should fail for strings other than Allow/Deny', () => {
|
||||
policy.Statement.Effect = 'Reject';
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
it('resource policy should succeed for Deny', () => {
|
||||
resourcePolicy.Statement[0].Effect = 'Deny';
|
||||
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);
|
||||
it('should fail if Effect is not a string', () => {
|
||||
policy.Statement.Effect = 1;
|
||||
check(policy, failRes());
|
||||
});
|
||||
});
|
||||
|
||||
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/' +
|
||||
describe('Policies validation - Statement::Action_block/' +
|
||||
'Statement::NotAction_block', () => {
|
||||
beforeEach(() => {
|
||||
userPolicy.Statement.Action = undefined;
|
||||
userPolicy.Statement.NotAction = undefined;
|
||||
policy.Statement.Action = undefined;
|
||||
policy.Statement.NotAction = undefined;
|
||||
});
|
||||
|
||||
actionTests.forEach(test => {
|
||||
it(`${test.name}`, () => {
|
||||
userPolicy.Statement.Action = test.value;
|
||||
if (test.expected === 'fail') {
|
||||
check(userPolicy, failRes(user, test.errMessage), user);
|
||||
} else {
|
||||
check(userPolicy, test.expected, user);
|
||||
}
|
||||
it('should succeed for foo:bar', () => {
|
||||
policy.Statement.Action = 'foo:bar';
|
||||
check(policy, successRes);
|
||||
|
||||
userPolicy.Statement.Action = undefined;
|
||||
userPolicy.Statement.NotAction = test.value;
|
||||
if (test.expected === 'fail') {
|
||||
check(userPolicy, failRes(user, test.errMessage), user);
|
||||
} else {
|
||||
check(userPolicy, test.expected, user);
|
||||
}
|
||||
});
|
||||
policy.Statement.Action = undefined;
|
||||
policy.Statement.NotAction = 'foo:bar';
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('should succeed for foo:*', () => {
|
||||
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('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' +
|
||||
describe('Policies validation - Statement::Resource_block' +
|
||||
'Statement::NotResource_block', () => {
|
||||
beforeEach(() => {
|
||||
userPolicy.Statement.Resource = undefined;
|
||||
userPolicy.Statement.NotResource = undefined;
|
||||
policy.Statement.Resource = undefined;
|
||||
policy.Statement.NotResource = undefined;
|
||||
});
|
||||
|
||||
resourceTests.forEach(test => {
|
||||
it(`${test.name}`, () => {
|
||||
userPolicy.Statement.Resource = test.value;
|
||||
if (test.expected === 'fail') {
|
||||
check(userPolicy, failRes(user, test.errMessage), user);
|
||||
} else {
|
||||
check(userPolicy, test.expected, user);
|
||||
}
|
||||
it('should succeed for arn:aws:s3:::*', () => {
|
||||
policy.Statement.Resource = 'arn:aws:s3:::*';
|
||||
check(policy, successRes);
|
||||
|
||||
userPolicy.Statement.Resource = undefined;
|
||||
userPolicy.Statement.NotResource = test.value;
|
||||
if (test.expected === 'fail') {
|
||||
check(userPolicy, failRes(user, test.errMessage), user);
|
||||
} else {
|
||||
check(userPolicy, test.expected, user);
|
||||
}
|
||||
});
|
||||
policy.Statement.Resource = undefined;
|
||||
policy.Statement.NotResource = 'arn:aws:s3:::*';
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('should succeed for arn:aws:s3:::test/home/${aws:username}', () => {
|
||||
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', () => {
|
||||
userPolicy.Statement.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);
|
||||
policy.Statement.Resource = [];
|
||||
check(policy, failRes(errDict.minItems.Resource));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Policies validation - Statement::Condition_block', () => {
|
||||
it('user policy should succeed for single Condition', () => {
|
||||
check(userPolicy, successRes, user);
|
||||
it('should succeed for single Condition', () => {
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
it('resource policy should succeed for single Condition', () => {
|
||||
check(resourcePolicy, successRes, resource);
|
||||
it('should succeed for multiple Conditions', () => {
|
||||
policy.Statement.Condition = {
|
||||
StringNotLike: { 's3:prefix': ['Development/*'] },
|
||||
Null: { 's3:prefix': false },
|
||||
};
|
||||
check(policy, successRes);
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
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 when Condition is not an Object', () => {
|
||||
policy.Statement.Condition = 'NumericLessThanEquals';
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
it(`resource policy ${test.name}`, () => {
|
||||
resourcePolicy.Statement[0].Condition = test.value;
|
||||
if (test.expected === 'fail') {
|
||||
check(resourcePolicy, failRes(resource), resource);
|
||||
} else {
|
||||
check(resourcePolicy, test.expected, resource);
|
||||
}
|
||||
});
|
||||
it('should fail for an invalid Condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
SomethingLike: { 's3:prefix': ['Development/*'] },
|
||||
};
|
||||
check(policy, failRes());
|
||||
});
|
||||
|
||||
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());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -472,7 +472,7 @@ describe('Principal evaluator', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: 'should allow user as principal if account is different',
|
||||
name: 'should deny user as principal if account is different',
|
||||
statement: [
|
||||
{
|
||||
Principal: {
|
||||
|
@ -491,7 +491,7 @@ describe('Principal evaluator', () => {
|
|||
accountId: defaultAccountId,
|
||||
},
|
||||
result: {
|
||||
result: 'Allow',
|
||||
result: 'Deny',
|
||||
checkAction: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1161,85 +1161,6 @@ describe('policyEvaluator', () => {
|
|||
};
|
||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
||||
});
|
||||
|
||||
it('should allow with StringEquals operator and ExistingObjectTag ' +
|
||||
'key if meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
StringEquals: { 's3:ExistingObjectTag/tagKey': 'tagValue' },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_existingObjTag: 'tagKey=tagValue',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Allow');
|
||||
});
|
||||
|
||||
it('should allow StringEquals operator and RequestObjectTag ' +
|
||||
'key if meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
StringEquals: { 's3:RequestObjectTagKey/tagKey': 'tagValue' },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagKey=tagValue',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Allow');
|
||||
});
|
||||
|
||||
it('should allow with ForAnyValue prefix if meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagOne=keyOne&tagThree=keyThree',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Allow');
|
||||
});
|
||||
|
||||
it('should allow with ForAllValues prefix if meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagOne=keyOne&tagTwo=keyTwo',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Allow');
|
||||
});
|
||||
|
||||
it('should not allow with ForAnyValue prefix if do not meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
'ForAnyValue:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagThree=keyThree&tagFour=keyFour',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
||||
});
|
||||
|
||||
it('should not allow with ForAllValues prefix if do not meet condition', () => {
|
||||
policy.Statement.Condition = {
|
||||
'ForAllValues:StringLike': { 's3:RequestObjectTagKeys': ['tagOne', 'tagTwo'] },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagThree=keyThree&tagFour=keyFour',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
||||
});
|
||||
|
||||
it('should be neutral with StringEquals if condition key does not exist', () => {
|
||||
policy.Statement.Condition = {
|
||||
StringEquals: { 's3:Foobar/tagKey': 'tagValue' },
|
||||
};
|
||||
const rcModifiers = {
|
||||
_requestObjTags: 'tagKey=tagValue',
|
||||
_needTagEval: true,
|
||||
};
|
||||
check(requestContext, rcModifiers, policy, 'Neutral');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
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);
|
||||
});
|
||||
});
|
|
@ -1,127 +0,0 @@
|
|||
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 = {
|
||||
mode: 'GOVERNANCE',
|
||||
date: 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 date', done => {
|
||||
const xml = convertToXml('GOVERNANCE', '');
|
||||
assert.equal(xml, '');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return empty string if no retention mode', done => {
|
||||
const xml = convertToXml('', passDate.toISOString());
|
||||
assert.equal(xml, '');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return xml string', done => {
|
||||
const xml = convertToXml('GOVERNANCE', passDate.toISOString());
|
||||
assert.strictEqual(xml, expectedXml);
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -28,63 +28,14 @@ describe('test generating versionIds', () => {
|
|||
|
||||
// nodejs 10 no longer returns error for non-hex string versionIds
|
||||
it.skip('should return error decoding non-hex string versionIds', () => {
|
||||
const encoded = vids.map(vid => VID.hexEncode(vid));
|
||||
const decoded = encoded.map(vid => VID.hexDecode(`${vid}foo`));
|
||||
const encoded = vids.map(vid => VID.encode(vid));
|
||||
const decoded = encoded.map(vid => VID.decode(`${vid}foo`));
|
||||
decoded.forEach(result => assert(result instanceof Error));
|
||||
});
|
||||
|
||||
it('should encode and decode versionIds', () => {
|
||||
const encoded = vids.map(vid => VID.hexEncode(vid));
|
||||
const decoded = encoded.map(vid => VID.hexDecode(vid));
|
||||
assert.strictEqual(vids.length, count);
|
||||
assert.deepStrictEqual(vids, decoded);
|
||||
});
|
||||
|
||||
it('simple base62 version test', () => {
|
||||
const vid = '98376906954349999999RG001 145.20.5';
|
||||
const encoded = VID.base62Encode(vid);
|
||||
assert.strictEqual(encoded, 'aJLWKz4Ko9IjBBgXKj5KQT2G9UHv0g7P');
|
||||
const decoded = VID.base62Decode(encoded);
|
||||
assert.strictEqual(vid, decoded);
|
||||
});
|
||||
|
||||
it('base62 version test with smaller part1 number', () => {
|
||||
const vid = '00000000054349999999RG001 145.20.5';
|
||||
const encoded = VID.base62Encode(vid);
|
||||
const decoded = VID.base62Decode(encoded);
|
||||
assert.strictEqual(vid, decoded);
|
||||
});
|
||||
|
||||
it('base62 version test with smaller part2 number', () => {
|
||||
const vid = '98376906950000099999RG001 145.20.5';
|
||||
const encoded = VID.base62Encode(vid);
|
||||
const decoded = VID.base62Decode(encoded);
|
||||
assert.strictEqual(vid, decoded);
|
||||
});
|
||||
|
||||
it('base62 version test with smaller part3', () => {
|
||||
const vid = '98376906950000099999R1 145.20.5';
|
||||
const encoded = VID.base62Encode(vid);
|
||||
const decoded = VID.base62Decode(encoded);
|
||||
assert.strictEqual(vid, decoded);
|
||||
});
|
||||
|
||||
it('base62 version test with smaller part3 - 2', () => {
|
||||
const vid = '98376906950000099999R1x';
|
||||
const encoded = VID.base62Encode(vid);
|
||||
const decoded = VID.base62Decode(encoded);
|
||||
assert.strictEqual(vid, decoded);
|
||||
});
|
||||
|
||||
it('error case: when invalid base62 key part 3 has invalid base62 character', () => {
|
||||
const invalidBase62VersionId = 'aJLWKz4Ko9IjBBgXKj5KQT.G9UHv0g7P';
|
||||
const decoded = VID.base62Decode(invalidBase62VersionId);
|
||||
assert(decoded instanceof Error);
|
||||
});
|
||||
|
||||
it('should encode and decode base62 versionIds', () => {
|
||||
const encoded = vids.map(vid => VID.base62Encode(vid));
|
||||
const decoded = encoded.map(vid => VID.base62Decode(vid));
|
||||
const encoded = vids.map(vid => VID.encode(vid));
|
||||
const decoded = encoded.map(vid => VID.decode(vid));
|
||||
assert.strictEqual(vids.length, count);
|
||||
assert.deepStrictEqual(vids, decoded);
|
||||
});
|
||||
|
|
|
@ -1,372 +0,0 @@
|
|||
'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;
|
|
@ -1,127 +0,0 @@
|
|||
'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'),
|
||||
];
|
|
@ -1,102 +0,0 @@
|
|||
/* eslint new-cap: "off" */
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
const logger = {
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
};
|
||||
|
||||
/* Fake tls AND socket objects, duck type */
|
||||
class EchoChannel extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.clogged = false;
|
||||
}
|
||||
|
||||
/* tls object members substitutes */
|
||||
|
||||
connect(port, options, cb) {
|
||||
process.nextTick(cb);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* socket object members substitutes */
|
||||
|
||||
cork() {
|
||||
return this;
|
||||
}
|
||||
|
||||
uncork() {
|
||||
return this;
|
||||
}
|
||||
|
||||
write(data) {
|
||||
if (!this.clogged) {
|
||||
return process.nextTick(() => this.emit('data', data));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
end() {
|
||||
return this.emit('end');
|
||||
}
|
||||
|
||||
/* Instrumentation member functions */
|
||||
|
||||
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 };
|
|
@ -1,82 +0,0 @@
|
|||
'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),
|
||||
]),
|
||||
],
|
||||
},
|
||||
];
|
|
@ -1,120 +0,0 @@
|
|||
'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),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
];
|
|
@ -1,247 +0,0 @@
|
|||
'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,
|
||||
},
|
||||
];
|
135
yarn.lock
135
yarn.lock
|
@ -39,42 +39,20 @@
|
|||
dependencies:
|
||||
"@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==
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
|
||||
"@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:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||
|
@ -142,14 +120,14 @@ ajv-keywords@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
|
||||
integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw=
|
||||
|
||||
ajv@8.8.1:
|
||||
version "8.8.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.1.tgz#e73dd88eeb4b10bbcd82bee136e6fbe801664d18"
|
||||
integrity sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg==
|
||||
ajv@6.12.2:
|
||||
version "6.12.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
|
||||
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^4.7.0:
|
||||
|
@ -250,18 +228,6 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base-x@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
|
||||
integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
base62@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/base62/-/base62-2.0.1.tgz#729cfe179ed34c61e4a489490105b44ce4ea1197"
|
||||
integrity sha512-4t4WQK7mdbcWzqEBiq6tfo2qDCeIZGXvjifJZyxHIVcjQkZJxpFtu/pa2Va69OouCkg6izZ08hKnPxroeDyzew==
|
||||
|
||||
base64-arraybuffer@0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
|
@ -603,7 +569,7 @@ depd@^1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
diff@4.0.2, diff@^4.0.2:
|
||||
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==
|
||||
|
@ -944,6 +910,11 @@ fast-deep-equal@^3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
fast-levenshtein@~2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
|
@ -1059,7 +1030,7 @@ glob-parent@~5.1.0:
|
|||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@7.1.6, glob@^7.0.3, glob@^7.1.2:
|
||||
glob@7.1.6, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
|
@ -1071,18 +1042,6 @@ glob@7.1.6, glob@^7.0.3, glob@^7.1.2:
|
|||
once "^1.3.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:
|
||||
version "9.18.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||
|
@ -1431,10 +1390,10 @@ js-yaml@^3.5.1:
|
|||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
json-schema-traverse@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -1458,11 +1417,6 @@ jsonpointer@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
|
||||
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:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keypress/-/keypress-0.1.0.tgz#4a3188d4291b66b4f65edb99f806aa9ae293592a"
|
||||
|
@ -1621,11 +1575,6 @@ lodash.flatten@^4.4.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
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:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
|
||||
|
@ -1805,17 +1754,6 @@ next-tick@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||
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:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
|
||||
|
@ -1970,13 +1908,6 @@ path-is-inside@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
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"
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.0.7:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
|
@ -2153,11 +2084,6 @@ require-directory@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
require-from-string@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
|
@ -2248,19 +2174,6 @@ simple-glob@^0.2:
|
|||
lodash.flatten "^4.4.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:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
|
||||
|
@ -2431,7 +2344,7 @@ strip-json-comments@~1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
||||
integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
|
||||
|
||||
supports-color@7.1.0, supports-color@^7.1.0:
|
||||
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==
|
||||
|
@ -2505,7 +2418,7 @@ type-check@~0.3.2:
|
|||
dependencies:
|
||||
prelude-ls "~1.1.2"
|
||||
|
||||
type-detect@4.0.8, 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==
|
||||
|
|
Loading…
Reference in New Issue