Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic auth to Zeebe deployments #4269

Merged
merged 5 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app/lib/zeebe-api/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership.
*
* Camunda licenses this file to you under the MIT; you may not use this file
* except in compliance with the MIT License.
*/

module.exports.AUTH_TYPES = {
NONE: 'none',
BASIC: 'basic',
OAUTH: 'oauth'
};

module.exports.ENDPOINT_TYPES = {
SELF_HOSTED: 'selfHosted',
CAMUNDA_CLOUD: 'camundaCloud'
};
110 changes: 68 additions & 42 deletions app/lib/zeebe-api/zeebe-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ const createLog = require('../log');
const { X509Certificate } = require('node:crypto');

const {
get,
isDefined,
pick,
values,
reduce
set,
values
} = require('min-dash');

const ERROR_REASONS = {
Expand All @@ -34,25 +36,36 @@ const ERROR_REASONS = {
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS'
};

const ENDPOINT_TYPES = {
SELF_HOSTED: 'selfHosted',
OAUTH: 'oauth',
CAMUNDA_CLOUD: 'camundaCloud'
};
const {
AUTH_TYPES,
ENDPOINT_TYPES
} = require('./constants');

const RESOURCE_TYPES = {
BPMN: 'bpmn',
DMN: 'dmn',
FORM: 'form'
};

const ENDPOINT_SECRETS = [
'endpoint.clientId',
'endpoint.clientSecret',
'endpoint.basicAuthUsername',
'endpoint.basicAuthPassword'
];

const CLIENT_OPTIONS_SECRETS = [
'options.basicAuth.username',
'options.basicAuth.password'
];

/**
* @typedef {Object} ZeebeClientParameters
* @property {Endpoint} endpoint
*/

/**
* @typedef {SelfHostedNoAuthEndpoint|SelfHostedOAuthEndpoint|CamundaCloudEndpoint} Endpoint
* @typedef {SelfHostedNoAuthEndpoint|SelfHostedBasicAuthEndpoint|SelfHostedOAuthEndpoint|CamundaCloudEndpoint} Endpoint
*/

/**
Expand All @@ -61,6 +74,13 @@ const RESOURCE_TYPES = {
* @property {string} url
*/

/**
* @typedef {Object} SelfHostedBasicAuthEndpoint
* @property {'basic'} type
* @property {string} username
* @property {string} password
*/

/**
* @typedef {Object} SelfHostedOAuthEndpoint
* @property {'oauth'} type
Expand Down Expand Up @@ -117,15 +137,15 @@ class ZeebeAPI {
const client = await this._getZeebeClient(endpoint);

this._log.debug('check connection', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
});

try {
await client.topology();
return { success: true };
} catch (err) {
this._log.error('connection check failed', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
}, err);

return {
Expand Down Expand Up @@ -157,7 +177,7 @@ class ZeebeAPI {
} = this._fs.readFile(filePath, { encoding: false });

this._log.debug('deploy', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
});

const client = await this._getZeebeClient(endpoint);
Expand All @@ -176,7 +196,7 @@ class ZeebeAPI {
response: response
};
} catch (err) {
this._log.error('deploy failed', withoutSecrets(parameters), err);
this._log.error('deploy failed', withoutSecrets(parameters, ENDPOINT_SECRETS), err);

return {
success: false,
Expand All @@ -203,7 +223,7 @@ class ZeebeAPI {
} = parameters;

this._log.debug('run', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
});

const client = await this._getZeebeClient(endpoint);
Expand All @@ -222,7 +242,7 @@ class ZeebeAPI {
};
} catch (err) {
this._log.error('run failed', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
}, err);

return {
Expand All @@ -247,7 +267,7 @@ class ZeebeAPI {
} = parameters;

this._log.debug('fetch gateway version', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
});

const client = await this._getZeebeClient(endpoint);
Expand All @@ -263,7 +283,7 @@ class ZeebeAPI {
};
} catch (err) {
this._log.error('fetch gateway version failed', {
parameters: withoutSecrets(parameters)
parameters: withoutSecrets(parameters, ENDPOINT_SECRETS)
}, err);

return {
Expand Down Expand Up @@ -307,18 +327,27 @@ class ZeebeAPI {
async _createZeebeClient(endpoint) {
const {
type,
authType = AUTH_TYPES.NONE,
url
} = endpoint;

let options = {
retry: false
};

if (!values(ENDPOINT_TYPES).includes(type)) {
if (!values(ENDPOINT_TYPES).includes(type) || !values(AUTH_TYPES).includes(authType)) {
return;
}

if (type === ENDPOINT_TYPES.OAUTH) {
if (authType === AUTH_TYPES.BASIC) {
options = {
...options,
basicAuth: {
username: endpoint.basicAuthUsername,
password: endpoint.basicAuthPassword
}
};
} else if (authType === AUTH_TYPES.OAUTH) {
options = {
...options,
oAuth: {
Expand Down Expand Up @@ -349,12 +378,15 @@ class ZeebeAPI {

this._log.debug('creating client', {
url,
options: filterRecursive(options, [
'clientId:secret',
'clientSecret:secret',
'customRootCert:blob',
'rootCerts:blob'
])
options: withoutSecrets(
filterRecursive(options, [
'clientId:secret',
'clientSecret:secret',
'customRootCert:blob',
'rootCerts:blob'
]),
CLIENT_OPTIONS_SECRETS
)
});

return new this._ZeebeNode.ZBClient(url, options);
Expand Down Expand Up @@ -523,7 +555,8 @@ function getErrorReason(error, endpoint) {
} = error;

const {
type
type,
authType = AUTH_TYPES.NONE
} = endpoint;

// (1) handle grpc errors
Expand All @@ -542,7 +575,7 @@ function getErrorReason(error, endpoint) {

// (3) handle <not found>
if (message.includes('ENOTFOUND') || message.includes('Not Found')) {
if (type === ENDPOINT_TYPES.OAUTH) {
if (authType === AUTH_TYPES.OAUTH) {
return ERROR_REASONS.OAUTH_URL;
} else if (type === ENDPOINT_TYPES.CAMUNDA_CLOUD) {
return ERROR_REASONS.INVALID_CLIENT_ID;
Expand All @@ -563,7 +596,7 @@ function getErrorReason(error, endpoint) {
return ERROR_REASONS.FORBIDDEN;
}

if (message.includes('Unsupported protocol') && type === ENDPOINT_TYPES.OAUTH) {
if (message.includes('Unsupported protocol') && authType === AUTH_TYPES.OAUTH) {
return ERROR_REASONS.OAUTH_URL;
}

Expand All @@ -574,25 +607,18 @@ function isHashEqual(parameter1, parameter2) {
return JSON.stringify(parameter1) === JSON.stringify(parameter2);
}

function withoutSecrets(parameters) {

const endpointSecrets = [
'clientId',
'clientSecret',
];
function withoutSecrets(parameters, paths) {
paths.forEach(secret => {
const path = secret.split('.');

const endpoint = reduce(parameters.endpoint, (filteredEndpoint, value, key) => {
const value = get(parameters, path);

if (endpointSecrets.includes(key)) {
value = '******';
if (isDefined(value)) {
set(parameters, path, '******');
}
});

filteredEndpoint[key] = value;

return filteredEndpoint;
}, {});

return { ...parameters, endpoint };
return parameters;
}

function asSerializedError(error) {
Expand Down
Loading