Skip to content

Commit

Permalink
feat(TD004): add new check for Enforce element in TD004
Browse files Browse the repository at this point in the history
This element is appropriate only for Apigee X / hybrid
  • Loading branch information
DinoChiesa committed Jan 9, 2024
1 parent 7691138 commit 3a7acdf
Show file tree
Hide file tree
Showing 2 changed files with 303 additions and 181 deletions.
256 changes: 158 additions & 98 deletions lib/package/plugins/TD004-targetSslInfo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2019-2022 Google LLC
Copyright 2019-2024 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -21,124 +21,184 @@ const plugin = {
name: "TargetEndpoint HTTPTargetConnection SSLInfo",
message: "TargetEndpoint HTTPTargetConnection should use SSLInfo correctly.",
fatal: false,
severity: 1, // 1 = warn, 2 = error
severity: 1, // 1 = warn, 2 = error
nodeType: "Endpoint",
enabled: true
};

const path = require('path'),
util = require('util'),
debug = require("debug")("apigeelint:" + ruleId);

const onTargetEndpoint =
function(endpoint, cb) {
let htc = endpoint.getHTTPTargetConnection(),
shortFilename = path.basename(endpoint.getFileName()),
flagged = false;

debug(`onTargetEndpoint shortfile(${shortFilename})`);
if (htc) {
try {
let messages = [];
let urls = htc.select('URL');
let loadBalancers = htc.select('LoadBalancer');
let sslInfos = htc.select('SSLInfo');
if (sslInfos.length > 1) {
messages.push(`Incorrect multiple SSLInfo elements`);
}
if (urls.length > 1) {
messages.push(`Incorrect multiple URL elements`);
}
if (loadBalancers.length > 1) {
messages.push(`Incorrect multiple LoadBalancer elements`);
}
debug(`onTargetEndpoint sslInfos(${util.format(sslInfos)})`);
if (urls.length > 0 && loadBalancers.length > 0) {
messages.push(`Using both URL and LoadBalancer in a proxy leads to undefined behavior`);
}
else if (urls.length == 1) {
debug(`onTargetEndpoint url(${util.format(urls[0])})`);
let endpointUrl = urls[0].childNodes && urls[0].childNodes[0] && urls[0].childNodes[0].nodeValue;
let isHttps = endpointUrl.startsWith('https://');
if (isHttps) {
if (sslInfos.length == 0) {
messages.push(`Missing SSLInfo configuration`);
const path = require("path"),
util = require("util"),
debug = require("debug")("apigeelint:" + ruleId);

let bundleProfile = "apigee";
const onBundle = function (bundle, cb) {
if (bundle.profile) {
bundleProfile = bundle.profile;
}
if (typeof cb == "function") {
cb(null, false);
}
};

const onTargetEndpoint = function (endpoint, cb) {
const htc = endpoint.getHTTPTargetConnection(),
shortFilename = path.basename(endpoint.getFileName());
let flagged = false;

debug(`onTargetEndpoint shortfile(${shortFilename})`);
if (htc) {
try {
const messages = [];
const urls = htc.select("URL");
const loadBalancers = htc.select("LoadBalancer");
const sslInfos = htc.select("SSLInfo");
if (sslInfos.length > 1) {
messages.push(`Incorrect multiple SSLInfo elements`);
}
if (urls.length > 1) {
messages.push(`Incorrect multiple URL elements`);
}
if (loadBalancers.length > 1) {
messages.push(`Incorrect multiple LoadBalancer elements`);
}
debug(`onTargetEndpoint sslInfos(${util.format(sslInfos)})`);
if (urls.length > 0 && loadBalancers.length > 0) {
messages.push(
`Using both URL and LoadBalancer in a proxy leads to undefined behavior`
);
} else if (urls.length == 1) {
debug(`onTargetEndpoint url(${util.format(urls[0])})`);
const endpointUrl =
urls[0].childNodes &&
urls[0].childNodes[0] &&
urls[0].childNodes[0].nodeValue;
const isHttps = endpointUrl.startsWith("https://");
if (isHttps) {
if (sslInfos.length == 0) {
messages.push(`Missing SSLInfo configuration`);
} else {
let elts = htc.select(`SSLInfo/Enabled`);
const enabled =
elts &&
elts[0] &&
elts[0].childNodes &&
elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == "true";
if (!enabled) {
messages.push("SSLInfo configuration is not Enabled");
}
else {
let elts = htc.select(`SSLInfo/Enabled`);
let enabled = elts && elts[0] && elts[0].childNodes && elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == 'true';
if ( ! enabled) {
messages.push('SSLInfo configuration is not Enabled');
}

elts = htc.select(`SSLInfo/IgnoreValidationErrors`);
let ignoreErrors = elts && elts[0] && elts[0].childNodes && elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == 'true';
if (ignoreErrors) {
messages.push('SSLInfo configuration includes IgnoreValidationErrors = true');
elts = htc.select(`SSLInfo/Enforce`);
let enforce =
elts && elts[0] && elts[0].childNodes && elts[0].childNodes[0];
if (bundleProfile == "apigeex") {
enforce = enforce && enforce.nodeValue == "true";
if (!enforce) {
messages.push(
"SSLInfo configuration does not use Enforce=true"
);
}

elts = htc.select(`SSLInfo/TrustStore`);
let hasTrustStore = elts && (elts.length == 1) && elts[0].childNodes && elts[0].childNodes[0];
if ( ! hasTrustStore) {
messages.push('Missing TrustStore in SSLInfo');
} else {
if (enforce) {
messages.push(
"SSLInfo configuration must not use the Enforce element"
);
}
}

elts = htc.select(`SSLInfo/ClientAuthEnabled`);
let hasClientAuth = elts && (elts.length == 1) && elts[0].childNodes && elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == 'true';
elts = htc.select(`SSLInfo/IgnoreValidationErrors`);
const ignoreErrors =
elts &&
elts[0] &&
elts[0].childNodes &&
elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == "true";
if (ignoreErrors) {
messages.push(
"SSLInfo configuration includes IgnoreValidationErrors = true"
);
}

// check for keystore, keyalias
elts = htc.select(`SSLInfo/KeyStore`);
let hasKeyStore = elts && (elts.length == 1) && elts[0].childNodes && elts[0].childNodes[0];
elts = htc.select(`SSLInfo/KeyAlias`);
let hasKeyAlias = elts && (elts.length == 1) && elts[0].childNodes && elts[0].childNodes[0];
elts = htc.select(`SSLInfo/TrustStore`);
const hasTrustStore =
elts &&
elts.length == 1 &&
elts[0].childNodes &&
elts[0].childNodes[0];
if (!hasTrustStore) {
messages.push("Missing TrustStore in SSLInfo");
}

if ( hasClientAuth && (! hasKeyStore || ! hasKeyAlias)) {
messages.push('When ClientAuthEnabled = true, use a KeyStore and KeyAlias');
}
if ( ! hasClientAuth && (hasKeyStore || hasKeyAlias)) {
messages.push('When ClientAuthEnabled = false, do not use a KeyStore and KeyAlias');
}
elts = htc.select(`SSLInfo/ClientAuthEnabled`);
const hasClientAuth =
elts &&
elts.length == 1 &&
elts[0].childNodes &&
elts[0].childNodes[0] &&
elts[0].childNodes[0].nodeValue == "true";

// check for keystore, keyalias
elts = htc.select(`SSLInfo/KeyStore`);
const hasKeyStore =
elts &&
elts.length == 1 &&
elts[0].childNodes &&
elts[0].childNodes[0];
elts = htc.select(`SSLInfo/KeyAlias`);
const hasKeyAlias =
elts &&
elts.length == 1 &&
elts[0].childNodes &&
elts[0].childNodes[0];

if (hasClientAuth && (!hasKeyStore || !hasKeyAlias)) {
messages.push(
"When ClientAuthEnabled = true, use a KeyStore and KeyAlias"
);
}
if (!hasClientAuth && (hasKeyStore || hasKeyAlias)) {
messages.push(
"When ClientAuthEnabled = false, do not use a KeyStore and KeyAlias"
);
}
}
else if (sslInfos.length == 1) {
messages.push(`SSLInfo should not be used with an insecure http url`);
}
} else if (sslInfos.length == 1) {
messages.push(`SSLInfo should not be used with an insecure http url`);
}
else if (loadBalancers.length == 1) {
if (sslInfos.length == 1) {
messages.push(`With LoadBalancer, SSLInfo should be configured within TargetServer, not under HTTPTargetConnection`);
}
} else if (loadBalancers.length == 1) {
if (sslInfos.length == 1) {
messages.push(
`With LoadBalancer, SSLInfo should be configured within TargetServer, not under HTTPTargetConnection`
);
}
}

//debug(`onTargetEndpoint messages(${messages})`);
messages.forEach(message => {
endpoint.addMessage({
plugin,
line: htc.getElement().lineNumber,
column: htc.getElement().columnNumber,
message
});
debug(`onTargetEndpoint set flagged`);
flagged = true;
//debug(`onTargetEndpoint messages(${messages})`);
messages.forEach((message) => {
endpoint.addMessage({
plugin,
line: htc.getElement().lineNumber,
column: htc.getElement().columnNumber,
message
});
}
catch (exc1) {
console.error('exception: ' + exc1);
debug(`onTargetEndpoint exc(${exc1})`);
debug(`${exc1.stack}`);
}
debug(`onTargetEndpoint set flagged`);
flagged = true;
});
} catch (exc1) {
console.error("exception: " + exc1);
debug(`onTargetEndpoint exc(${exc1})`);
debug(`${exc1.stack}`);
}
}

if (typeof(cb) == 'function') {
debug(`onTargetEndpoint isFlagged(${flagged})`);
cb(null, flagged );
}
};
if (typeof cb == "function") {
debug(`onTargetEndpoint isFlagged(${flagged})`);
cb(null, flagged);
}
};

module.exports = {
plugin,
onBundle,
onTargetEndpoint
};
Loading

0 comments on commit 3a7acdf

Please sign in to comment.