lemon-core
Version:
Lemon Serverless Micro-Service Platform
220 lines • 9.84 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AWSKMSService = exports.fromBase64 = exports.isBase64 = void 0;
/**
* file: `cores/aws-kms-service.ts`
* - kms service for AWS KMS.
*
*
* **TODO**
* - [x] in VPC lambda, will timeout due to NAT => so, need to support via engine. => use `VPC Endpoints` with kms service.
* - [x] error of `not authorized` => use one of below.
* 1. add this Role (lemon-todaq-api-prod-ap-northeast-2-lambdaRole) to `Key users`.
* 2. add `kms:Decrypt` `kms:Encrypt` in resource of `iamRoleStatements`.
*
*
* @author Steve Jung <steve@lemoncloud.io>
* @date 2019-10-30 initial version.
*
* @copyright (C) lemoncloud.io 2019 - All Rights Reserved.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const engine_1 = require("../../engine/");
const client_kms_1 = require("@aws-sdk/client-kms");
const client_kms_2 = require("@aws-sdk/client-kms");
const tools_1 = require("../../tools");
const NS = engine_1.$U.NS('KMSS', 'blue'); // NAMESPACE TO BE PRINTED.
const ALIAS = `lemon-hello-api`; //NOTE - use env[KMS_KEY_ID] to overide.
const region = () => engine_1.$engine.environ('REGION', 'ap-northeast-2');
const instance = () => {
const cfg = (0, tools_1.awsConfig)(engine_1.$engine, region());
return new client_kms_1.KMSClient(cfg);
};
/**
* check if base64 string.
*/
const isBase64 = (text) => /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/.test(text);
exports.isBase64 = isBase64;
/**
* normal base64 to url encoded.
*/
const fromBase64 = (base64) => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
exports.fromBase64 = fromBase64;
/**
* class: `AWSKMSService`
* - shared Key Management Service to encrypt/decrypt message.
*/
class AWSKMSService {
constructor(keyId, options) {
/**
* get name of this
*/
this.name = () => `KMS`;
/**
* hello
*/
this.hello = () => `aws-kms-service:${this._keyId}`;
/**
* get key-id to encrypt.
*/
this.keyId = () => {
if (!this._keyId || typeof this._keyId !== 'string')
throw new Error(`.keyId<${typeof this._keyId}> (string) is required!`);
return this._keyId;
};
/**
* Encrypt message
*
* @param {*} message
*/
this.encrypt = (message) => __awaiter(this, void 0, void 0, function* () {
const keyId = this.keyId();
(0, engine_1._inf)(NS, `encrypt(${keyId}, ${message.substring(0, 10)}...)..`);
const KeyId = keyId;
const params = {
KeyId,
Plaintext: Buffer.from(message),
};
const result = yield this.instance().send(new client_kms_2.EncryptCommand(params));
(0, engine_1._log)(NS, '> result =', result);
const ciphertext = result.CiphertextBlob ? Buffer.from(result.CiphertextBlob).toString('base64') : message;
(0, engine_1._log)(NS, '> ciphertext =', ciphertext.substring(0, 32), '...');
return ciphertext;
});
/**
* Decrypt message
*
* @param {*} encryptedSecret
*/
this.decrypt = (encryptedSecret) => __awaiter(this, void 0, void 0, function* () {
(0, engine_1._inf)(NS, `decrypt(${encryptedSecret.substring(0, 12)}...)..`);
const CiphertextBlob = Buffer.from(encryptedSecret, 'base64');
//* api param.
const params = { CiphertextBlob: CiphertextBlob };
const data = yield this.instance().send(new client_kms_2.DecryptCommand(params));
// _log(NS, '> data.type =', typeof data);
return (data === null || data === void 0 ? void 0 : data.Plaintext) ? Buffer.from(data.Plaintext).toString('utf-8') : '';
});
/**
* make signature by message
*
* @param {*} message any string
* @param forJwtSignature (option) flag to get JWT signature format.
*/
this.sign = (message, forJwtSignature = true) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!message || typeof message !== 'string')
throw new Error(`@message[${message}] is invalid - kms.sign()`);
const KeyId = this.keyId();
(0, engine_1._inf)(NS, `sign(${KeyId}, ${message.substring(0, 10)}...)..`);
const params = {
KeyId,
Message: Buffer.from(message),
SigningAlgorithm: (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.algorithm) !== null && _b !== void 0 ? _b : 'RSASSA_PKCS1_V1_5_SHA_256',
MessageType: 'RAW',
};
const result = yield this.instance().send(new client_kms_1.SignCommand(params));
const signature = result.Signature ? Buffer.from(result.Signature).toString('base64') : '';
if (forJwtSignature)
return (0, exports.fromBase64)(signature);
return signature;
});
/**
* verify signature in asymetric way
* - it tooks around `30ms`
*
* @param {*} message any string
* @param {*} signature signature of Buffer or string(in base64)
*/
this.verify = (message, signature) => __awaiter(this, void 0, void 0, function* () {
var _c, _d;
if (!message || typeof message !== 'string')
throw new Error(`@message[${message}] is invalid - kms.verify()`);
if (!signature)
throw new Error(`@signature (string|Buffer) is required - kms.verify()`);
const KeyId = this.keyId();
(0, engine_1._inf)(NS, `verify(${KeyId}, ${message.substring(0, 10)}...)..`);
const params = {
KeyId,
Message: Buffer.from(message),
SigningAlgorithm: (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.algorithm) !== null && _d !== void 0 ? _d : 'RSASSA_PKCS1_V1_5_SHA_256',
MessageType: 'RAW',
Signature: typeof signature === 'string' ? Buffer.from(signature, 'base64') : signature,
};
const result = yield this.instance()
.send(new client_kms_2.VerifyCommand(params))
.catch(e => {
(0, engine_1._err)(NS, `! err=`, e);
return null;
});
return result === null || result === void 0 ? void 0 : result.SignatureValid;
});
/**
* retrieve public-key for asymetric verification.
* - used to verify signature with JWT library w/o making request to AWS KMS.
* - in general, cache this `public-key` to verify locally.
*
* @param encoding (optional) encoding type
*/
this.getPublicKey = (encoding = 'base64') => __awaiter(this, void 0, void 0, function* () {
const KeyId = this.keyId();
(0, engine_1._inf)(NS, `getPublicKey(${KeyId})..`);
const params = {
KeyId,
};
const result = yield this.instance().send(new client_kms_2.GetPublicKeyCommand(params));
return (result === null || result === void 0 ? void 0 : result.PublicKey) ? Buffer.from(result.PublicKey).toString(encoding) : '';
});
keyId = keyId !== null && keyId !== void 0 ? keyId : `${engine_1.$engine.environ(AWSKMSService.ENV_KMS_KEY_ID, AWSKMSService.DEF_KMS_TARGET)}`;
this._keyId = keyId;
this._options = options;
}
/**
* get KMS instance in stock
*/
instance() {
if (!this._instance)
this._instance = instance();
return this._instance;
}
/**
* it should be 'hello lemon'
*
* # Example
* ```sh
* # encrypt text
* $ aws kms encrypt --profile <profile> --key-id <kms-key-id> --plaintext "hello lemon" --query CiphertextBlob --output text
* ```
*/
sample() {
return __awaiter(this, void 0, void 0, function* () {
(0, engine_1._inf)(NS, 'sample()..');
//* check key-id.
const message = `hello lemon!`;
const KMS_KEY_ID = engine_1.$engine.environ(AWSKMSService.ENV_KMS_KEY_ID, `alias/${ALIAS}`);
const keyId = this.keyId();
(0, engine_1._log)(NS, '> key-id =', keyId);
const encrypted = yield this.encrypt(message);
(0, engine_1._log)(NS, '> encrypted =', encrypted);
const decrypted = yield this.decrypt(encrypted);
(0, engine_1._log)(NS, '> decrypted =', decrypted, '->', message == decrypted ? 'ok' : 'error');
return { KMS_KEY_ID, keyId, message, encrypted, decrypted };
});
}
}
exports.AWSKMSService = AWSKMSService;
/**
* environ name of KMS KEY
*/
AWSKMSService.ENV_KMS_KEY_ID = 'KMS_KEY_ID';
AWSKMSService.DEF_KMS_TARGET = `alias/${ALIAS}`;
//# sourceMappingURL=aws-kms-service.js.map
;