diff --git a/package-lock.json b/package-lock.json index 0193f52c32..daf3fb0ee6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8498,6 +8498,11 @@ "integrity": "sha512-uaht4XcYSq5ZrPriQW8C+g5DhptewRd1E84ph7L167sCyzLObz+U3JTpmYq/CNkvjNsz2mtyQoHPNEYQYTzWmg==", "dev": true }, + "node_modules/@types/js-nacl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/js-nacl/-/js-nacl-1.3.0.tgz", + "integrity": "sha512-juUvxo444ZfwDSwWyhssMxSN+snqTdiUoOVXZF+/ffVrGHq3rAf1fmczWn3z9TCEAuRbaTmgAcYlZ9MutyyOkQ==" + }, "node_modules/@types/json-diff": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@types/json-diff/-/json-diff-0.5.2.tgz", @@ -46250,12 +46255,25 @@ "@types/vorpal": "^1.11.0" } }, + "packages/node-dev/node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "packages/nodes-base": { "name": "n8n-nodes-base", "version": "0.194.0", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@kafkajs/confluent-schema-registry": "1.0.6", + "@types/js-nacl": "^1.3.0", "amqplib": "^0.8.0", "aws4": "^1.8.0", "basic-auth": "^2.0.1", @@ -46275,6 +46293,7 @@ "imap-simple": "^4.3.0", "isbot": "^3.3.4", "iso-639-1": "^2.1.3", + "js-nacl": "^1.4.0", "jsonwebtoken": "^8.5.1", "kafkajs": "^1.14.0", "lodash.get": "^4.4.2", @@ -54844,6 +54863,11 @@ "integrity": "sha512-uaht4XcYSq5ZrPriQW8C+g5DhptewRd1E84ph7L167sCyzLObz+U3JTpmYq/CNkvjNsz2mtyQoHPNEYQYTzWmg==", "dev": true }, + "@types/js-nacl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/js-nacl/-/js-nacl-1.3.0.tgz", + "integrity": "sha512-juUvxo444ZfwDSwWyhssMxSN+snqTdiUoOVXZF+/ffVrGHq3rAf1fmczWn3z9TCEAuRbaTmgAcYlZ9MutyyOkQ==" + }, "@types/json-diff": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@types/json-diff/-/json-diff-0.5.2.tgz", @@ -74246,6 +74270,7 @@ "@types/gm": "^1.18.2", "@types/imap-simple": "^4.2.0", "@types/jest": "^27.4.0", + "@types/js-nacl": "^1.3.0", "@types/jsonwebtoken": "^8.5.2", "@types/lodash.set": "^4.3.6", "@types/lossless-json": "^1.0.0", @@ -74285,6 +74310,7 @@ "isbot": "^3.3.4", "iso-639-1": "^2.1.3", "jest": "^27.4.7", + "js-nacl": "^1.4.0", "jsonwebtoken": "^8.5.1", "kafkajs": "^1.14.0", "lodash.get": "^4.4.2", diff --git a/packages/nodes-base/credentials/VenafiTlsProtectCloudApi.credentials.ts b/packages/nodes-base/credentials/VenafiTlsProtectCloudApi.credentials.ts new file mode 100644 index 0000000000..91488a6e9c --- /dev/null +++ b/packages/nodes-base/credentials/VenafiTlsProtectCloudApi.credentials.ts @@ -0,0 +1,35 @@ +import { + IAuthenticateGeneric, + ICredentialDataDecryptedObject, + ICredentialTestRequest, + ICredentialType, + IHttpRequestOptions, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class VenafiTlsProtectCloudApi implements ICredentialType { + name = 'venafiTlsProtectCloudApi'; + displayName = 'Venafi TLS Protect Cloud'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + 'tppl-api-key': '={{$credentials.apiKey}}', + }, + }, + }; + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.venafi.cloud', + url: '/v1/preferences', + }, + }; +} diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateDescription.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateDescription.ts new file mode 100644 index 0000000000..d813c71718 --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateDescription.ts @@ -0,0 +1,398 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const certificateOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + noDataExpression: true, + type: 'options', + displayOptions: { + show: { + resource: ['certificate'], + }, + }, + options: [ + { + name: 'Delete', + value: 'delete', + description: 'Delete a certificate', + action: 'Delete a certificate', + }, + { + name: 'Download', + value: 'download', + description: 'Download a certificate', + action: 'Download a certificate', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a certificate', + action: 'Get a certificate', + }, + { + name: 'Get Many', + value: 'getMany', + description: 'Retrieve many certificates', + action: 'Get many certificates', + }, + { + name: 'Renew', + value: 'renew', + description: 'Renew a certificate', + action: 'Renew a certificate', + }, + ], + default: 'delete', + }, +]; + +export const certificateFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* certificate:download */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Certificate ID', + name: 'certificateId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + }, + }, + default: '', + }, + { + displayName: 'Download Item', + name: 'downloadItem', + type: 'options', + options: [ + { + name: 'Certificate', + value: 'certificate', + }, + { + name: 'Keystore', + value: 'keystore', + }, + ], + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + }, + }, + default: 'certificate', + }, + { + displayName: 'Keystore Type', + name: 'keystoreType', + type: 'options', + options: [ + { + name: 'JKS', + value: 'JKS', + }, + { + name: 'PKCS12', + value: 'PKCS12', + }, + { + name: 'PEM', + value: 'PEM', + }, + ], + default: 'PEM', + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + downloadItem: ['keystore'], + }, + }, + }, + { + displayName: 'Certificate Label', + name: 'certificateLabel', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + downloadItem: ['keystore'], + }, + }, + default: '', + }, + { + displayName: 'Private Key Passphrase', + name: 'privateKeyPassphrase', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + downloadItem: ['keystore'], + }, + }, + default: '', + }, + { + displayName: 'Keystore Passphrase', + name: 'keystorePassphrase', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + downloadItem: ['keystore'], + keystoreType: ['JKS'], + }, + }, + default: '', + }, + { + displayName: 'Input Data Field Name', + name: 'binaryProperty', + type: 'string', + default: 'data', + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + }, + }, + required: true, + description: + 'The name of the input field containing the binary file data to be uploaded', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['download'], + resource: ['certificate'], + }, + }, + options: [ + { + displayName: 'Chain Order', + name: 'chainOrder', + type: 'options', + options: [ + { + name: 'EE_FIRST', + value: 'EE_FIRST', + description: 'Download the certificate with the end-entity portion of the chain first', + }, + { + name: 'EE_ONLY', + value: 'EE_ONLY', + description: 'Download only the end-entity certificate', + }, + { + name: 'ROOT_FIRST', + value: 'ROOT_FIRST', + description: 'Download the certificate with root portion of the chain first', + }, + ], + default: 'ROOT_FIRST', + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + options: [ + { + name: 'PEM', + value: 'PEM', + }, + { + name: 'DER', + value: 'DER', + }, + ], + default: 'PEM', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* certificate:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Certificate ID', + name: 'certificateId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['get', 'delete'], + resource: ['certificate'], + }, + }, + default: '', + }, + /* -------------------------------------------------------------------------- */ + /* certificate:getMany */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: ['getMany'], + resource: ['certificate'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['getMany'], + resource: ['certificate'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 50, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['getMany'], + resource: ['certificate'], + }, + }, + options: [ + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* certificate:renew */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Application Name or ID', + name: 'applicationId', + type: 'options', + description: + 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getApplications', + }, + displayOptions: { + show: { + operation: ['renew'], + resource: ['certificate'], + }, + }, + default: '', + }, + { + displayName: 'Existing Certificate ID', + name: 'existingCertificateId', + type: 'string', + displayOptions: { + show: { + operation: ['renew'], + resource: ['certificate'], + }, + }, + default: '', + }, + { + displayName: 'Certificate Issuing Template Name or ID', + name: 'certificateIssuingTemplateId', + type: 'options', + description: + 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getCertificateIssuingTemplates', + }, + displayOptions: { + show: { + operation: ['renew'], + resource: ['certificate'], + }, + }, + default: '', + }, + { + displayName: 'Certificate Signing Request', + name: 'certificateSigningRequest', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: ['renew'], + resource: ['certificate'], + }, + }, + default: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['renew'], + resource: ['certificate'], + }, + }, + options: [ + { + displayName: 'Validity Period', + name: 'validityPeriod', + type: 'options', + options: [ + { + name: '1 Year', + value: 'P1Y', + }, + { + name: '10 Days', + value: 'P10D', + }, + { + name: '12 Hours', + value: 'PT12H', + }, + ], + default: 'P1Y', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateInterface.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateInterface.ts new file mode 100644 index 0000000000..53d41cc404 --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateInterface.ts @@ -0,0 +1,41 @@ +export interface ICertficateRequest { + isVaaSGenerated?: boolean; + csrAttributes?: ICsrAttributes; + applicationServerTypeId?: string; + certificateSigningRequest?: string; + applicationId?: string; + certificateIssuingTemplateId?: string; + certficateOwnerUserId?: string; + validityPeriod?: string; +} + +export interface ICsrAttributes { + commonName?: string; + organization?: string; + organizationalUnits?: string[]; + locality?: string; + state?: string; + country?: string; + keyTypeParameters?: IKeyTypeParameters; + subjectAlternativeNamesByType?: ISubjectAltNamesByType; +} + +export interface IKeyTypeParameters { + keyType?: string; + keyCurve?: string; + keyLength?: number; +} + +export interface ISubjectAltNamesByType { + dnsNames?: string[]; + rfc822Names?: string[]; + ipAddresses?: string[]; + uniformResourceIdentifiers?: string[]; +} + +export interface ICertficateKeystoreRequest { + exportFormat?: string; + encryptedPrivateKeyPassphrase?: string; + encryptedKeystorePassphrase?: string; + certificateLabel?: string; +} diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateRequestDescription.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateRequestDescription.ts new file mode 100644 index 0000000000..f2d3e9d73e --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/CertificateRequestDescription.ts @@ -0,0 +1,388 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const certificateRequestOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + noDataExpression: true, + type: 'options', + displayOptions: { + show: { + resource: ['certificateRequest'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new certificate request', + action: 'Create a certificate request', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a certificate request', + action: 'Get a certificate request', + }, + { + name: 'Get Many', + value: 'getMany', + description: 'Retrieve many certificate requests', + action: 'Get many certificate requests', + }, + ], + default: 'create', + }, +]; + +export const certificateRequestFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* certificateRequest:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Application Name or ID', + name: 'applicationId', + type: 'options', + description: + 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getApplications', + }, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + }, + }, + default: '', + }, + { + displayName: 'Certificate Issuing Template Name or ID', + name: 'certificateIssuingTemplateId', + type: 'options', + description: + 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getCertificateIssuingTemplates', + }, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + }, + }, + default: '', + }, + { + displayName: 'Generate CSR', + name: 'generateCsr', + type: 'boolean', + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + }, + }, + default: false, + }, + { + displayName: 'Application Server Type Name or ID', + name: 'applicationServerTypeId', + type: 'options', + description: + 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getApplicationServerTypes', + }, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + generateCsr: [true], + }, + }, + default: '', + }, + { + displayName: 'Common Name', + name: 'commonName', + required: true, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + generateCsr: [true], + }, + }, + type: 'string', + default: 'n8n.io', + description: 'The Common Name field for the certificate Subject (CN)', + }, + // Optional... + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + generateCsr: [true], + }, + }, + options: [ + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'A 2 letter country code', + }, + { + displayName: 'Key Curve', + name: 'keyCurve', + type: 'options', + options: [ + { + name: 'ED25519', + value: 'ED25519', + description: 'Use Edwards-curve Digital Signature Algorithm (EdDSA)', + }, + { + name: 'P256', + value: 'P256', + description: 'Use Elliptic Prime Curve 256 bit encryption', + }, + { + name: 'P384', + value: 'P384', + description: 'Use Elliptic Prime Curve 384 bit encryption', + }, + { + name: 'P521', + value: 'P521', + description: 'Use Elliptic Prime Curve 521 bit encryption', + }, + { + name: 'UNKNOWN', + value: 'UNKNOWN', + }, + ], + default: 'ED25519', + }, + { + displayName: 'Key Length', + name: 'keyLength', + type: 'number', + default: 2048, + description: 'The number of bits to allow for key generation', + }, + { + displayName: 'Key Type', + name: 'keyType', + type: 'options', + options: [ + { + name: 'EC', + value: 'EC', + description: 'Elliptic Curve (EC)', + }, + { + name: 'RSA', + value: 'RSA', + description: 'Rivest, Shamir, Adleman key (RSA)', + }, + ], + default: 'RSA', + description: 'The encryption algorithm for the public key', + }, + { + displayName: 'Locality', + name: 'locality', + type: 'string', + default: '', + description: 'The name of a city or town', + }, + { + displayName: 'Organization', + name: 'organization', + type: 'string', + default: '', + description: 'The name of a company or organization', + }, + { + displayName: 'Organizational Units', + name: 'organizationalUnits', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: '', + description: 'The name of a department or section', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'The name of a state or province', + }, + { + displayName: 'Subject Alt Names', + name: 'SubjectAltNamesUi', + placeholder: 'Add Subject', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + options: [ + { + name: 'SubjectAltNamesValues', + displayName: 'Subject Alt Name', + values: [ + { + displayName: 'Typename', + name: 'Typename', + type: 'options', + options: [ + { + name: 'DNS', + value: 'dnsNames', + }, + /*{ + name: 'IP Address', + value: 'ipAddresses', + }, + { + name: 'RFC822 Names', + value: 'rfc822Names', + }, + + { + name: 'URI', + value: 'uniformResourceIdentifiers', + },*/ + ], + description: 'What type of SAN is being used', + default: 'dnsNames', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: 'community.n8n.io', + description: + 'The SAN friendly name that corresponds to the Type or TypeName parameter. For example, if a TypeName is IPAddress, the Name value is a valid IP address.', + }, + ], + }, + ], + }, + ], + }, + // End CSR Builder + { + displayName: 'Certificate Signing Request', + name: 'certificateSigningRequest', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + generateCsr: [false], + }, + }, + default: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: ['create'], + resource: ['certificateRequest'], + }, + }, + options: [ + { + displayName: 'Validity Period', + name: 'validityPeriod', + type: 'options', + options: [ + { + name: '1 Year', + value: 'P1Y', + }, + { + name: '10 Days', + value: 'P10D', + }, + { + name: '12 Hours', + value: 'PT12H', + }, + ], + default: 'P1Y', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* certificateRequest:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Certificate Request ID', + name: 'certificateRequestId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: ['get'], + resource: ['certificateRequest'], + }, + }, + default: '', + }, + /* -------------------------------------------------------------------------- */ + /* certificateRequest:getMany */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: ['getMany'], + resource: ['certificateRequest'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: ['getMany'], + resource: ['certificateRequest'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 50, + description: 'Max number of results to return', + }, +]; diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts new file mode 100644 index 0000000000..678dd9d0a9 --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts @@ -0,0 +1,146 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, + IPollFunctions, +} from 'n8n-core'; + +import { IDataObject, JsonObject, NodeApiError } from 'n8n-workflow'; + +import { get } from 'lodash'; + +import * as nacl_factory from 'js-nacl'; + +export async function venafiApiRequest( + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, + method: string, + resource: string, + body = {}, + qs: IDataObject = {}, + uri?: string, + option: IDataObject = {}, + // tslint:disable-next-line:no-any +): Promise { + const operation = this.getNodeParameter('operation', 0) as string; + + const options: OptionsWithUri = { + headers: { + Accept: 'application/json', + 'content-type': 'application/json', + }, + method, + body, + qs, + uri: `https://api.venafi.cloud${resource}`, + json: true, + }; + + if (Object.keys(option).length) { + Object.assign(options, option); + } + + // For cert download we don't need any headers + // If we remove for everything the key fetch fails + if (operation === 'download') { + // We need content-type for keystore + if (!resource.endsWith('keystore')) { + delete options!.headers!['Accept']; + delete options!.headers!['content-type']; + } + } + + try { + if (Object.keys(body).length === 0) { + delete options.body; + } + return await this.helpers.requestWithAuthentication.call( + this, + 'venafiTlsProtectCloudApi', + options, + ); + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function venafiApiRequestAllItems( + this: IExecuteFunctions | ILoadOptionsFunctions, + propertyName: string, + method: string, + endpoint: string, + // tslint:disable-next-line:no-any + body: any = {}, + query: IDataObject = {}, + // tslint:disable-next-line:no-any +): Promise { + const returnData: IDataObject[] = []; + + let responseData; + + do { + responseData = await venafiApiRequest.call(this, method, endpoint, body, query); + endpoint = get(responseData, '_links[0].Next'); + returnData.push.apply(returnData, responseData[propertyName]); + } while (responseData._links && responseData._links[0].Next); + + return returnData; +} + +export async function encryptPassphrase( + this: IExecuteFunctions | ILoadOptionsFunctions, + certificateId: string, + passphrase: string, + storePassphrase: string, +) { + let dekHash = ''; + const dekResponse = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificates/${certificateId}`, + ); + + if (dekResponse.dekHash) { + dekHash = dekResponse.dekHash; + } + + let pubKey = ''; + const pubKeyResponse = await venafiApiRequest.call( + this, + 'GET', + `/v1/edgeencryptionkeys/${dekHash}`, + ); + + if (pubKeyResponse.key) { + pubKey = pubKeyResponse.key; + } + + let encryptedKeyPass = ''; + let encryptedKeyStorePass = ''; + + const promise = () => { + return new Promise((resolve, reject) => { + // tslint:disable-next-line:no-any + nacl_factory.instantiate((nacl: any) => { + try { + const passphraseUTF8 = nacl.encode_utf8(passphrase) as string; + const keyPassBuffer = nacl.crypto_box_seal(passphraseUTF8, Buffer.from(pubKey, 'base64')); + encryptedKeyPass = Buffer.from(keyPassBuffer).toString('base64'); + + const storePassphraseUTF8 = nacl.encode_utf8(storePassphrase) as string; + const keyStorePassBuffer = nacl.crypto_box_seal( + storePassphraseUTF8, + Buffer.from(pubKey, 'base64'), + ); + encryptedKeyStorePass = Buffer.from(keyStorePassBuffer).toString('base64'); + + return resolve([encryptedKeyPass, encryptedKeyStorePass]); + } catch (error) { + return reject(error); + } + }); + }); + }; + return await promise(); +} diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.json b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.json new file mode 100644 index 0000000000..8aaa7e1d1f --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.json @@ -0,0 +1,18 @@ +{ + "node": "n8n-nodes-base.venafiTlsProtectCloud", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": ["Development"], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/venafiTlsProtectCloud" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.venafiTlsProtectCloud/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.ts new file mode 100644 index 0000000000..ef125f44ce --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.ts @@ -0,0 +1,488 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { encryptPassphrase, venafiApiRequest, venafiApiRequestAllItems } from './GenericFunctions'; + +import { certificateFields, certificateOperations } from './CertificateDescription'; + +import { + certificateRequestFields, + certificateRequestOperations, +} from './CertificateRequestDescription'; + +import { + ICertficateKeystoreRequest, + ICertficateRequest, + ICsrAttributes, + IKeyTypeParameters, + ISubjectAltNamesByType, +} from './CertificateInterface'; + +export class VenafiTlsProtectCloud implements INodeType { + description: INodeTypeDescription = { + displayName: 'Venafi TLS Protect Cloud', + name: 'venafiTlsProtectCloud', + icon: 'file:../venafi.svg', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Venafi TLS Protect Cloud​ API', + defaults: { + name: 'Venafi TLS Protect Cloud​', + color: '#000000', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'venafiTlsProtectCloudApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + noDataExpression: true, + type: 'options', + options: [ + { + name: 'Certificate', + value: 'certificate', + }, + { + name: 'Certificate Request', + value: 'certificateRequest', + }, + ], + default: 'certificateRequest', + }, + ...certificateOperations, + ...certificateFields, + ...certificateRequestOperations, + ...certificateRequestFields, + ], + }; + + methods = { + loadOptions: { + async getApplications(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { applications } = await venafiApiRequest.call( + this, + 'GET', + '/outagedetection/v1/applications', + ); + for (const application of applications) { + returnData.push({ + name: application.name, + value: application.id, + }); + } + return returnData; + }, + async getApplicationServerTypes( + this: ILoadOptionsFunctions, + ): Promise { + const returnData: INodePropertyOptions[] = []; + const { applicationServerTypes } = await venafiApiRequest.call( + this, + 'GET', + '/outagedetection/v1/applicationservertypes', + ); + for (const applicationServerType of applicationServerTypes) { + returnData.push({ + name: applicationServerType.platformName, + value: applicationServerType.id, + }); + } + return returnData; + }, + async getCertificateIssuingTemplates( + this: ILoadOptionsFunctions, + ): Promise { + const returnData: INodePropertyOptions[] = []; + const { certificateIssuingTemplates } = await venafiApiRequest.call( + this, + 'GET', + '/v1/certificateissuingtemplates', + ); + for (const issueTemplate of certificateIssuingTemplates) { + returnData.push({ + name: issueTemplate.name, + value: issueTemplate.id, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + try { + if (resource === 'certificateRequest') { + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config&urls.primaryName=outagedetection-service#//v1/certificaterequests_create + if (operation === 'create') { + const applicationId = this.getNodeParameter('applicationId', i) as string; + const certificateIssuingTemplateId = this.getNodeParameter( + 'certificateIssuingTemplateId', + i, + ) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const generateCsr = this.getNodeParameter('generateCsr', i) as boolean; + + const body: ICertficateRequest = { + applicationId, + certificateIssuingTemplateId, + }; + + if (generateCsr) { + const applicationServerTypeId = this.getNodeParameter( + 'applicationServerTypeId', + i, + ) as string; + const commonName = this.getNodeParameter('commonName', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const keyTypeDetails: IKeyTypeParameters = {}; + const csrAttributes: ICsrAttributes = {}; + const subjectAltNamesByType: ISubjectAltNamesByType = {}; + + body.isVaaSGenerated = true; + body.applicationServerTypeId = applicationServerTypeId as string; + + csrAttributes.commonName = commonName as string; + + // Csr Generation + if (additionalFields.organization) { + csrAttributes.organization = additionalFields.organization as string; + } + if (additionalFields.organizationalUnits) { + csrAttributes.organizationalUnits = + additionalFields.organizationalUnits as string[]; + } + if (additionalFields.locality) { + csrAttributes.locality = additionalFields.locality as string; + } + if (additionalFields.state) { + csrAttributes.state = additionalFields.state as string; + } + if (additionalFields.country) { + csrAttributes.country = additionalFields.country as string; + } + body.csrAttributes = csrAttributes; + + // Key type + if (additionalFields.keyType) { + keyTypeDetails.keyType = additionalFields.keyType as string; + } + if (additionalFields.keyCurve) { + keyTypeDetails.keyCurve = additionalFields.keyCurve as string; + } + if (additionalFields.keyLength) { + keyTypeDetails.keyLength = additionalFields.keyLength as number; + } + if (Object.keys(keyTypeDetails).length !== 0) { + body.csrAttributes.keyTypeParameters = keyTypeDetails; + } + + // SAN + if (additionalFields.SubjectAltNamesUi) { + for (const key of (additionalFields.SubjectAltNamesUi as IDataObject) + .SubjectAltNamesValues as IDataObject[]) { + if (key.Typename === 'dnsNames') { + subjectAltNamesByType.dnsNames + ? subjectAltNamesByType.dnsNames.push(key.name as string) + : (subjectAltNamesByType.dnsNames = [key.name as string]); + } + /*if (key.Typename === 'ipAddresses') { + subjectAltNamesByType.ipAddresses ? subjectAltNamesByType.ipAddresses.push(key.name as string) : subjectAltNamesByType.ipAddresses = [key.name as string]; + } + if (key.Typename === 'rfc822Names') { + subjectAltNamesByType.rfc822Names ? subjectAltNamesByType.rfc822Names.push(key.name as string) : subjectAltNamesByType.rfc822Names = [key.name as string]; + } + if (key.Typename === 'uniformResourceIdentifiers') { + subjectAltNamesByType.uniformResourceIdentifiers ? subjectAltNamesByType.uniformResourceIdentifiers.push(key.name as string) : subjectAltNamesByType.uniformResourceIdentifiers = [key.name as string]; + }*/ + } + } + if (Object.keys(subjectAltNamesByType).length !== 0) { + body.csrAttributes.subjectAlternativeNamesByType = subjectAltNamesByType; + } + } else { + const certificateSigningRequest = this.getNodeParameter( + 'certificateSigningRequest', + i, + ) as string; + body.isVaaSGenerated = false; + body.certificateSigningRequest = certificateSigningRequest; + } + + Object.assign(body, options); + + responseData = await venafiApiRequest.call( + this, + 'POST', + `/outagedetection/v1/certificaterequests`, + body, + qs, + ); + + responseData = responseData.certificateRequests; + } + + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config&urls.primaryName=outagedetection-service#//v1/certificaterequests_getById + if (operation === 'get') { + const certificateId = this.getNodeParameter('certificateRequestId', i) as string; + + responseData = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificaterequests/${certificateId}`, + {}, + qs, + ); + } + + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config&urls.primaryName=outagedetection-service#//v1/certificaterequests_getAll + if (operation === 'getMany') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll) { + responseData = await venafiApiRequestAllItems.call( + this, + 'certificateRequests', + 'GET', + `/outagedetection/v1/certificaterequests`, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + responseData = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificaterequests`, + {}, + qs, + ); + + responseData = responseData.certificateRequests.splice(0, limit); + } + } + } + + if (resource === 'certificate') { + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=%2Fv3%2Fapi-docs%2Fswagger-config&urls.primaryName=outagedetection-service#/%2Fv1/certificateretirement_deleteCertificates + if (operation === 'delete') { + const certificateId = this.getNodeParameter('certificateId', i) as string; + + responseData = await venafiApiRequest.call( + this, + 'POST', + `/outagedetection/v1/certificates/deletion`, + { certificateIds: [certificateId] }, + ); + + responseData = responseData.certificates; + } + + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=%2Fv3%2Fapi-docs%2Fswagger-config&urls.primaryName=outagedetection-service#/ + if (operation === 'download') { + const certificateId = this.getNodeParameter('certificateId', i) as string; + const binaryProperty = this.getNodeParameter('binaryProperty', i) as string; + const downloadItem = this.getNodeParameter('downloadItem', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + // Cert Download + if (downloadItem === 'certificate') { + Object.assign(qs, options); + responseData = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificates/${certificateId}/contents`, + {}, + qs, + undefined, + { encoding: null, json: false, resolveWithFullResponse: true, cert: true }, + ); + } else { + const exportFormat = this.getNodeParameter('keystoreType', i) as string; + + const body: ICertficateKeystoreRequest = { + exportFormat, + }; + + const privateKeyPassphrase = this.getNodeParameter( + 'privateKeyPassphrase', + i, + ) as string; + const certificateLabel = this.getNodeParameter('certificateLabel', i) as string; + + body.certificateLabel = certificateLabel; + + let keystorePassphrase = ''; + + if (exportFormat === 'JKS') { + keystorePassphrase = this.getNodeParameter('keystorePassphrase', i) as string; + } + + const encryptedValues = (await encryptPassphrase.call( + this, + certificateId, + privateKeyPassphrase, + keystorePassphrase, + )) as string; + body.encryptedPrivateKeyPassphrase = encryptedValues[0]; + if (exportFormat === 'JKS') { + body.encryptedKeystorePassphrase = encryptedValues[1]; + } + + responseData = await venafiApiRequest.call( + this, + 'POST', + `/outagedetection/v1/certificates/${certificateId}/keystore`, + body, + {}, + undefined, + { encoding: null, json: false, resolveWithFullResponse: true }, + ); + } + + const contentDisposition = responseData.headers['content-disposition']; + const fileNameRegex = /(?<=filename=").*\b/; + const match = fileNameRegex.exec(contentDisposition); + let fileName = ''; + + if (match !== null) { + fileName = match[0]; + } + + const binaryData = await this.helpers.prepareBinaryData( + Buffer.from(responseData.body), + fileName, + ); + + responseData = { + json: {}, + binary: { + [binaryProperty]: binaryData, + }, + }; + } + + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=%2Fv3%2Fapi-docs%2Fswagger-config&urls.primaryName=outagedetection-service#/%2Fv1/certificates_getById + if (operation === 'get') { + const certificateId = this.getNodeParameter('certificateId', i) as string; + + responseData = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificates/${certificateId}`, + {}, + qs, + ); + } + + //https://api.venafi.cloud/webjars/swagger-ui/index.html?configUrl=%2Fv3%2Fapi-docs%2Fswagger-config&urls.primaryName=outagedetection-service#/%2Fv1/certificates_getAllAsCsv + if (operation === 'getMany') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + + Object.assign(qs, filters); + + if (returnAll) { + responseData = await venafiApiRequestAllItems.call( + this, + 'certificates', + 'GET', + `/outagedetection/v1/certificates`, + {}, + qs, + ); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await venafiApiRequest.call( + this, + 'GET', + `/outagedetection/v1/certificates`, + {}, + qs, + ); + + responseData = responseData.certificates; + } + } + + //https://docs.venafi.cloud/api/t-cloud-api-renew-cert/ + if (operation === 'renew') { + const applicationId = this.getNodeParameter('applicationId', i) as string; + const certificateIssuingTemplateId = this.getNodeParameter( + 'certificateIssuingTemplateId', + i, + ) as string; + const certificateSigningRequest = this.getNodeParameter( + 'certificateSigningRequest', + i, + ) as string; + const existingCertificateId = this.getNodeParameter( + 'existingCertificateId', + i, + ) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = { + certificateSigningRequest, + certificateIssuingTemplateId, + applicationId, + existingCertificateId, + }; + + Object.assign(body, options); + + responseData = await venafiApiRequest.call( + this, + 'POST', + `/outagedetection/v1/certificaterequests`, + body, + qs, + ); + + responseData = responseData.certificateRequests; + } + } + + returnData.push( + ...this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { + itemData: { item: i }, + }), + ); + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ json: { error: error.message } }); + continue; + } + throw error; + } + } + + return [returnData as INodeExecutionData[]]; + } +} diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts new file mode 100644 index 0000000000..8db5ba08ca --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts @@ -0,0 +1,101 @@ +import { IPollFunctions } from 'n8n-core'; + +import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; + +import moment from 'moment'; + +import { venafiApiRequest } from './GenericFunctions'; + +export class VenafiTlsProtectCloudTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Venafi TLS Protect Cloud Trigger', + name: 'venafiTlsProtectCloudTrigger', + icon: 'file:../venafi.svg', + group: ['trigger'], + version: 1, + subtitle: '={{$parameter["triggerOn"]}}', + description: 'Starts the workflow when Venafi events occure', + defaults: { + name: 'Venafi TLS Protect Cloud​', + color: '#000000', + }, + credentials: [ + { + name: 'venafiTlsProtectCloudApi', + required: true, + }, + ], + polling: true, + inputs: [], + outputs: ['main'], + properties: [ + { + displayName: 'Trigger On', + name: 'trigger On', + type: 'options', + options: [ + { + name: 'Certificate Expired', + value: 'certificateExpired', + }, + ], + required: true, + default: 'certificateExpired', + }, + ], + }; + + async poll(this: IPollFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const event = this.getNodeParameter('event') as string; + + const now = moment().format(); + + const startDate = webhookData.lastTimeChecked || now; + const endDate = now; + + const { certificates: certificates } = await venafiApiRequest.call( + this, + 'POST', + `/outagedetection/v1/certificatesearch`, + { + expression: { + operands: [ + { + operator: 'AND', + operands: [ + { + field: 'validityEnd', + operator: 'LTE', + values: [endDate], + }, + { + field: 'validityEnd', + operator: 'GTE', + values: [startDate], + }, + ], + }, + ], + }, + ordering: { + orders: [ + { + field: 'certificatInstanceModificationDate', + direction: 'DESC', + }, + ], + }, + }, + {}, + ); + + webhookData.lastTimeChecked = endDate; + + if (Array.isArray(certificates) && certificates.length) { + return [this.helpers.returnJsonArray(certificates)]; + } + + return null; + } +} diff --git a/packages/nodes-base/nodes/Venafi/venafi.svg b/packages/nodes-base/nodes/Venafi/venafi.svg new file mode 100644 index 0000000000..70ed4594fa --- /dev/null +++ b/packages/nodes-base/nodes/Venafi/venafi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 232658f50c..d31e340462 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -317,6 +317,7 @@ "dist/credentials/UrlScanIoApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/VonageApi.credentials.js", + "dist/credentials/VenafiTlsProtectCloudApi.credentials.js", "dist/credentials/VenafiTlsProtectDatacenterApi.credentials.js", "dist/credentials/WebflowApi.credentials.js", "dist/credentials/WebflowOAuth2Api.credentials.js", @@ -686,6 +687,7 @@ "dist/nodes/UptimeRobot/UptimeRobot.node.js", "dist/nodes/UrlScanIo/UrlScanIo.node.js", "dist/nodes/Vero/Vero.node.js", + "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.js", "dist/nodes/Venafi/Datacenter/VenafiTlsProtectDatacenter.node.js", "dist/nodes/Vonage/Vonage.node.js", "dist/nodes/Wait/Wait.node.js", @@ -754,6 +756,7 @@ }, "dependencies": { "@kafkajs/confluent-schema-registry": "1.0.6", + "@types/js-nacl": "^1.3.0", "amqplib": "^0.8.0", "aws4": "^1.8.0", "basic-auth": "^2.0.1", @@ -773,6 +776,7 @@ "imap-simple": "^4.3.0", "isbot": "^3.3.4", "iso-639-1": "^2.1.3", + "js-nacl": "^1.4.0", "jsonwebtoken": "^8.5.1", "kafkajs": "^1.14.0", "lodash.get": "^4.4.2",