/*
* Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
const { Buffer } = require('buffer');
const GreengrassCommon = require('aws-greengrass-common-js');
const Lambda = require('./lambda');
const Util = require('./util');
const KEY_SECRET_ID = 'SecretId';
const KEY_VERSION_ID = 'VersionId';
const KEY_VERSION_STAGE = 'VersionStage';
const KEY_SECRET_ARN = 'ARN';
const KEY_SECRET_NAME = 'Name';
const KEY_CREATED_DATE = 'CreatedDate';
const KEY_JSON_RESULT_FLAG = 'DeStringifyResultFlag';
const { envVars } = GreengrassCommon;
const { SECRETS_MANAGER_FUNCTION_ARN } = envVars;
/**
* Constructs a service interface object. Each API operation is exposed as a function on service.
* @class
* @memberOf aws-greengrass-core-sdk
*/
class SecretsManager {
/**
* Constructs a service object. This object has one method for each API operation.
*
* @example <caption>Constructing a SecretsManager object</caption>
* var secretsmanager = new GG.SecretsManager();
*/
constructor() {
this.lambda = new Lambda();
}
/**
* Called when a response from the service is returned.
*
* @callback secretsManagerCallback
* @param err {Error} The error object returned from the request. Set to <tt>null</tt> if the request is successful.
* @param data {Object|String} data returned from the request. Return type is decided on DeStringifyResultFlag flag in request. Set to <tt>null</tt> if a request error occurs.
* @param data.ARN {String} The ARN of the secret.
* @param data.Name {String} The friendly name of the secret.
* @param data.VersionId {String} The unique identifier of this version of the secret.
* @param data.SecretBinary {Buffer|TypedArray|Blob|String} The decrypted part of the protected secret information that was originally provided as binary data in the form of a byte array.
* The response parameter represents the binary data as a base64-encoded string.
* @param data.SecretString {String} The decrypted part of the protected secret information that was originally provided as a string.
* @param data.VersionStages {String[]} Specifies the secret version that you want to retrieve by the staging label attached to the version.
* <br/>Staging labels are used to keep track of different versions during the rotation process.
*/
/**
* Retrieves a specific local secret value.
*
* @param params {Object}
* @param params.SecretId {String} Specifies the secret containing the version that you want to retrieve. You can specify either the Amazon Resource Name (ARN) or the friendly name of the secret.
* @param params.VersionStage {String} Specifies the secret version that you want to retrieve by the staging label attached to the version.
* <br/>Staging labels are used to keep track of different versions during the rotation process.
* @param params.DeStringifyResultFlag {boolean} Optional Flag to decide the return type from getSecretValue. If set, it returns de-serialized data object, otherwise it returns stringified response.
* @param callback {secretsManagerCallback} The callback.
*
* @example <caption>Retrieving a local secret value</caption>
* // This operation retrieves a local secret value
*
* var params = {
* SecretId: "STRING_VALUE",
* VersionStage: "STRING_VALUE"
* };
* secretsmanager.getSecretValue(params, function(err, data) {
* if (err) console.log(err, err.stack); // an error occurred
* else console.log(data); // successful response
* });
*/
getSecretValue(params, callback) {
const secretId = Util.getParameter(params, KEY_SECRET_ID);
const versionId = Util.getParameter(params, KEY_VERSION_ID);
const versionStage = Util.getParameter(params, KEY_VERSION_STAGE);
const isJSONResultFlagSet = Util.getParameter(params, KEY_JSON_RESULT_FLAG);
if (secretId === undefined) {
callback(new Error(`"${KEY_SECRET_ID}" is a required parameter`), null);
return;
}
// TODO: Remove this once we support query by VersionId
if (versionId !== undefined) {
callback(new Error('Query by VersionId is not yet supported'), null);
return;
}
if (versionId !== undefined && versionStage !== undefined) {
callback(new Error('VersionId and VersionStage cannot both be specified at the same time'), null);
return;
}
const getSecretValueRequestBytes = SecretsManager._generateGetSecretValueRequestBytes(secretId, versionId, versionStage);
const invokeParams = {
FunctionName: SECRETS_MANAGER_FUNCTION_ARN,
Payload: getSecretValueRequestBytes,
};
console.log(`Getting secret value from secrets manager: ${getSecretValueRequestBytes}`);
this.lambda.invoke(invokeParams, (err, data) => {
if (err) {
callback(err, null); // an error occurred
} else if (SecretsManager._is200Response(data.Payload)) {
// successful response
if (isJSONResultFlagSet) {
callback(null, JSON.parse(data.Payload));
}
callback(null, data.Payload);
} else {
callback(new Error(JSON.stringify(data.Payload)), null); // error response
}
});
}
static _generateGetSecretValueRequestBytes(secretId, versionId, versionStage) {
const request = {
SecretId: secretId,
};
if (versionStage !== undefined) {
request.VersionStage = versionStage;
}
if (versionId !== undefined) {
request.VersionId = versionId;
}
return Buffer.from(JSON.stringify(request));
}
static _is200Response(payload) {
const hasSecretArn = this._stringContains(payload, KEY_SECRET_ARN);
const hasSecretName = this._stringContains(payload, KEY_SECRET_NAME);
const hasVersionId = this._stringContains(payload, KEY_VERSION_ID);
const hasCreatedDate = this._stringContains(payload, KEY_CREATED_DATE);
return hasSecretArn && hasSecretName && hasVersionId && hasCreatedDate;
}
static _stringContains(src, target) {
return src.indexOf(target) > -1;
}
}
module.exports = SecretsManager;