From 2c0a2cc623d369503f4ef378885c4ad2e20e9817 Mon Sep 17 00:00:00 2001 From: Dino Chiesa Date: Thu, 13 Jun 2024 21:54:44 -0700 Subject: [PATCH 1/3] fix: refactor TD004 to extract two small checks into unique plugins - TD006 SSLInfo when LoadBalancer is present - TD007 SSLInfo should use TrustStore --- README.md | 4 +- lib/package/plugins/TD004-targetSslInfo.js | 191 +++++---- .../TD006-target-LoadBalancer-SslInfo.js | 75 ++++ .../plugins/TD007-targetSslInfo-Truststore.js | 106 +++++ test/specs/TD004-sslInfo.js | 20 +- test/specs/TD006-target-LB-SslInfo.js | 234 +++++++++++ test/specs/TD007-target-sslInfo-truststore.js | 392 ++++++++++++++++++ 7 files changed, 913 insertions(+), 109 deletions(-) create mode 100644 lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js create mode 100644 lib/package/plugins/TD007-targetSslInfo-Truststore.js create mode 100644 test/specs/TD006-target-LB-SslInfo.js create mode 100644 test/specs/TD007-target-sslInfo-truststore.js diff --git a/README.md b/README.md index 34c449b..9ab05ee 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ If you DO want apigeelint to use an `.apigeelintrc` file, format the file like t --profile apigeex ## always exclude these plugins ---excluded TD002,TD004 +--excluded TD002,TD007 ## use this formatter unless overridden --formatter table.js @@ -290,6 +290,8 @@ This is the current list: |   |:white_check_mark:| TD003 | TargetEndpoint name | TargetEndpoint name should match basename of filename. | |   |:white_check_mark:| TD004 | TargetEndpoint SSLInfo | TargetEndpoint HTTPTargetConnection should enable TLS/SSL. | |   |:white_check_mark:| TD005 | TargetEndpoint SSLInfo references | TargetEndpoint SSLInfo should use references for KeyStore and TrustStore. | +|   |:white_check_mark:| TD006 | TargetEndpoint SSLInfo | When using a LoadBalancer, the SSLInfo should not be configured under HTTPTargetConnection. | +|   |:white_check_mark:| TD007 | TargetEndpoint SSLInfo | TargetEndpoint HTTPTargetConnection SSLInfo should use TrustStore. | | Flow |   |   |   |   | |   |:white_check_mark:| FL001 | Unconditional Flows | Only one unconditional flow will get executed. Error if more than one was detected. | | Step |   |   |   |   | diff --git a/lib/package/plugins/TD004-targetSslInfo.js b/lib/package/plugins/TD004-targetSslInfo.js index 3f8c76e..b3aff71 100644 --- a/lib/package/plugins/TD004-targetSslInfo.js +++ b/lib/package/plugins/TD004-targetSslInfo.js @@ -18,8 +18,9 @@ const ruleId = require("../myUtil.js").getRuleId(); const plugin = { ruleId, - name: "TargetEndpoint HTTPTargetConnection SSLInfo", - message: "TargetEndpoint HTTPTargetConnection should use SSLInfo correctly.", + name: "TargetEndpoint HTTPTargetConnection SSLInfo should use TrustStore", + message: + "TargetEndpoint HTTPTargetConnection should use TrustStore with SSLInfo.", fatal: false, severity: 1, // 1 = warn, 2 = error nodeType: "Endpoint", @@ -66,110 +67,108 @@ const onTargetEndpoint = function (endpoint, cb) { 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"); - } - - 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" - ); - } + } else if (urls.length > 0) { + if (urls.length > 1) { + messages.push(`Multiple URL child elements is unsupported`); + } else { + 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 { - if (enforce) { + 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 must not use the Enforce element" + "SSLInfo configuration does not use Enabled=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" - ); - } + 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" + ); + } + } else { + if (enforce) { + messages.push( + "SSLInfo configuration must not use the Enforce element" + ); + } + } - 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"); - } + 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" + ); + } - 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" - ); + 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 > 0) { + debug(`using at least one LoadBalancer`); + if (loadBalancers.length > 1) { + messages.push(`Multiple LoadBalancer child elements is unsupported`); } } diff --git a/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js b/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js new file mode 100644 index 0000000..2ae936b --- /dev/null +++ b/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js @@ -0,0 +1,75 @@ +/* + 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. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +const ruleId = require("../myUtil.js").getRuleId(); + +const plugin = { + ruleId, + name: "TargetEndpoint HTTPTargetConnection SSLInfo with LoadBalancer", + message: + "TargetEndpoint HTTPTargetConnection should not use SSLInfo with LoadBalancer.", + fatal: false, + severity: 1, // 1 = warn, 2 = error + nodeType: "Endpoint", + enabled: true +}; + +const path = require("path"), + debug = require("debug")("apigeelint:" + ruleId); + +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 loadBalancers = htc.select("LoadBalancer"); + const sslInfos = htc.select("SSLInfo"); + debug( + `sslInfos (${sslInfos.length}) loadBalancers(${loadBalancers.length})` + ); + + if (loadBalancers.length == 1) { + if (sslInfos.length > 0) { + endpoint.addMessage({ + plugin, + line: sslInfos[0].lineNumber, + column: sslInfos[0].columnNumber, + message: `When using a LoadBalancer, the SSLInfo should not be configured under HTTPTargetConnection` + }); + 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); + } +}; + +module.exports = { + plugin, + onTargetEndpoint +}; diff --git a/lib/package/plugins/TD007-targetSslInfo-Truststore.js b/lib/package/plugins/TD007-targetSslInfo-Truststore.js new file mode 100644 index 0000000..10b4f68 --- /dev/null +++ b/lib/package/plugins/TD007-targetSslInfo-Truststore.js @@ -0,0 +1,106 @@ +/* + 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. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +const ruleId = require("../myUtil.js").getRuleId(); + +const plugin = { + ruleId, + name: "TargetEndpoint HTTPTargetConnection SSLInfo TrustStore", + message: + "TargetEndpoint HTTPTargetConnection should use TrustStore with SSLInfo.", + fatal: false, + severity: 1, // 1 = warn, 2 = error + nodeType: "Endpoint", + enabled: true +}; + +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 sslInfos = htc.select("SSLInfo"); + 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) { + const 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"); + } + } + } + } + + //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; + }); + } catch (exc1) { + console.error("exception: " + exc1); + debug(`onTargetEndpoint exc(${exc1})`); + debug(`${exc1.stack}`); + } + } + + if (typeof cb == "function") { + debug(`onTargetEndpoint isFlagged(${flagged})`); + cb(null, flagged); + } +}; + +module.exports = { + plugin, + onBundle, + onTargetEndpoint +}; diff --git a/test/specs/TD004-sslInfo.js b/test/specs/TD004-sslInfo.js index ae430ee..4afad1d 100644 --- a/test/specs/TD004-sslInfo.js +++ b/test/specs/TD004-sslInfo.js @@ -35,10 +35,11 @@ const assert = require("assert"), assert.equal(e, undefined, e ? " error " : " no error"); debug(`result: ${result}`); if (messages && messages.length) { + debug(`messages: ${util.format(messages)}`); assert.equal(result, true); assert.equal( - messages.length, target.report.messages.length, + messages.length, util.format(target.report.messages) ); messages.forEach((msg, ix) => { @@ -88,7 +89,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { https://foo.com/apis/{api_name}/maskconfigs `, - ["SSLInfo configuration is not Enabled", "Missing TrustStore in SSLInfo"] + ["SSLInfo configuration does not use Enabled=true"] ); test( 21, @@ -138,7 +139,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { https://foo.com/apis/{api_name}/maskconfigs `, - ["SSLInfo configuration is not Enabled", "Missing TrustStore in SSLInfo"] + ["SSLInfo configuration does not use Enabled=true"] ); test( @@ -152,7 +153,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { https://foo.com/apis/{api_name}/maskconfigs `, - ["Missing TrustStore in SSLInfo"] + [] ); test( @@ -202,9 +203,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { `, - [ - "With LoadBalancer, SSLInfo should be configured within TargetServer, not under HTTPTargetConnection" - ] + [] ); test( @@ -219,10 +218,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { https://foo.com/apis/{api_name}/maskconfigs `, - [ - "SSLInfo configuration includes IgnoreValidationErrors = true", - "Missing TrustStore in SSLInfo" - ] + ["SSLInfo configuration includes IgnoreValidationErrors = true"] ); test( @@ -240,7 +236,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { `, [ "SSLInfo configuration includes IgnoreValidationErrors = true", - "SSLInfo configuration is not Enabled" + "SSLInfo configuration does not use Enabled=true" ] ); diff --git a/test/specs/TD006-target-LB-SslInfo.js b/test/specs/TD006-target-LB-SslInfo.js new file mode 100644 index 0000000..4a76963 --- /dev/null +++ b/test/specs/TD006-target-LB-SslInfo.js @@ -0,0 +1,234 @@ +/* + 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. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +/* global describe, it, configuration */ + +const assert = require("assert"), + testID = "TD006", + util = require("util"), + debug = require("debug")("apigeelint:" + testID), + Bundle = require("../../lib/package/Bundle.js"), + bl = require("../../lib/package/bundleLinter.js"), + Endpoint = require("../../lib/package/Endpoint.js"), + plugin = require(bl.resolvePlugin(testID)), + Dom = require("@xmldom/xmldom").DOMParser, + testBase = function (caseNum, profile, desc, targetDef, messages) { + it(`case ${caseNum} ${desc}`, function () { + const tDoc = new Dom().parseFromString(targetDef), + target = new Endpoint(tDoc.documentElement, this, ""); + + plugin.onTargetEndpoint(target, function (e, result) { + assert.equal(e, undefined, e ? " error " : " no error"); + debug(`result: ${result}`); + if (messages && messages.length) { + debug(`messages: ${util.format(messages)}`); + assert.equal(result, true); + assert.equal( + target.report.messages.length, + messages.length, + util.format(target.report.messages) + ); + messages.forEach((msg, ix) => { + debug(`check msg ${ix}: ${msg}`); + assert.ok( + target.report.messages.find((m) => m.message == msg), + `index ${ix} ${util.format(target.report.messages)}` + ); + }); + } else { + assert.equal(result, false, util.format(target.report.messages)); + assert.equal( + target.report.messages.length, + 0, + util.format(target.report.messages) + ); + } + }); + }); + }; + +const test = function (caseNum, desc, targetDef, messages) { + return testBase(caseNum, "apigee", desc, targetDef, messages); +}; +const testApigeeX = function (caseNum, desc, targetDef, messages) { + return testBase(caseNum, "apigeex", desc, targetDef, messages); +}; + +describe(`${testID} - ${plugin.plugin.name}`, function () { + test( + 10, + "missing SSLInfo", + ` + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 20, + "empty SSLInfo with https url", + ` + + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 23, + "no SSLInfo, using http url", + ` + + http://foo.com/apis/{api_name}/maskconfigs + + `, + null /* http URL does not require SSLInfo */ + ); + + test( + 30, + "SSLInfo Enabled = false, no truststore", + ` + + + false + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 40, + "SSLInfo Enabled = true, no truststore", + ` + + + true + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 41, + "SSLInfo Enabled = true, with TrustStore", + ` + + + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 42, + "SSLInfo Enabled = true, ignoreerrors = false", + ` + + + true + false + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 43, + "SSLInfo Enabled = true, with LoadBalancer", + ` + + + true + false + truststore1 + + + + + + + `, + [ + `When using a LoadBalancer, the SSLInfo should not be configured under HTTPTargetConnection` + ] + ); + + test( + 50, + "SSLInfo IgnoreValidationErrors = true", + ` + + + true + true + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 60, + "SSLInfo IgnoreValidationErrors = true and Enabled = false", + ` + + + false + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); +}); + +describe(`${testID} - Print plugin results`, function () { + debug("test configuration: " + JSON.stringify(configuration)); + var bundle = new Bundle(configuration); + bl.executePlugin(testID, bundle); + let report = bundle.getReport(); + + it("should create a report object with valid schema", function () { + let formatter = bl.getFormatter("json.js"); + if (!formatter) { + assert.fail("formatter implementation not defined"); + } + let schema = require("./../fixtures/reportSchema.js"), + Validator = require("jsonschema").Validator, + v = new Validator(), + jsonReport = JSON.parse(formatter(report)), + validationResult = v.validate(jsonReport, schema); + assert.equal(validationResult.errors.length, 0, validationResult.errors); + }); +}); diff --git a/test/specs/TD007-target-sslInfo-truststore.js b/test/specs/TD007-target-sslInfo-truststore.js new file mode 100644 index 0000000..6783679 --- /dev/null +++ b/test/specs/TD007-target-sslInfo-truststore.js @@ -0,0 +1,392 @@ +/* + 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. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +/* global describe, it, configuration */ + +const assert = require("assert"), + testID = "TD007", + util = require("util"), + debug = require("debug")("apigeelint:" + testID), + Bundle = require("../../lib/package/Bundle.js"), + bl = require("../../lib/package/bundleLinter.js"), + Endpoint = require("../../lib/package/Endpoint.js"), + plugin = require(bl.resolvePlugin(testID)), + Dom = require("@xmldom/xmldom").DOMParser, + testBase = function (caseNum, profile, desc, targetDef, messages) { + it(`case ${caseNum} ${desc}`, function () { + const tDoc = new Dom().parseFromString(targetDef), + target = new Endpoint(tDoc.documentElement, this, ""); + + plugin.onTargetEndpoint(target, function (e, result) { + assert.equal(e, undefined, e ? " error " : " no error"); + debug(`result: ${result}`); + if (messages && messages.length) { + debug(`messages: ${util.format(messages)}`); + assert.equal(result, true); + assert.equal( + target.report.messages.length, + messages.length, + util.format(target.report.messages) + ); + messages.forEach((msg, ix) => { + debug(`check msg ${ix}: ${msg}`); + assert.ok( + target.report.messages.find((m) => m.message == msg), + `index ${ix} ${util.format(target.report.messages)}` + ); + }); + } else { + assert.equal(result, false, util.format(target.report.messages)); + assert.equal( + target.report.messages.length, + 0, + util.format(target.report.messages) + ); + } + }); + }); + }; + +const test = function (caseNum, desc, targetDef, messages) { + return testBase(caseNum, "apigee", desc, targetDef, messages); +}; +const testApigeeX = function (caseNum, desc, targetDef, messages) { + return testBase(caseNum, "apigeex", desc, targetDef, messages); +}; + +describe(`${testID} - ${plugin.plugin.name}`, function () { + test( + 10, + "missing SSLInfo", + ` + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 20, + "empty SSLInfo with https url", + ` + + + https://foo.com/apis/{api_name}/maskconfigs + + `, + ["Missing TrustStore in SSLInfo"] + ); + + test( + 23, + "no SSLInfo, using http url", + ` + + http://foo.com/apis/{api_name}/maskconfigs + + `, + null /* http URL does not require SSLInfo */ + ); + + test( + 30, + "SSLInfo Enabled = false, no truststore", + ` + + + false + + https://foo.com/apis/{api_name}/maskconfigs + + `, + ["Missing TrustStore in SSLInfo"] + ); + + test( + 40, + "SSLInfo Enabled = true, no truststore", + ` + + + true + + https://foo.com/apis/{api_name}/maskconfigs + + `, + ["Missing TrustStore in SSLInfo"] + ); + + test( + 41, + "SSLInfo Enabled = true, with TrustStore", + ` + + + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 42, + "SSLInfo Enabled = true, ignoreerrors = false", + ` + + + true + false + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 43, + "SSLInfo Enabled = true, with LoadBalancer", + ` + + + true + false + truststore1 + + + + + + + `, + [] + ); + + test( + 50, + "SSLInfo IgnoreValidationErrors = true", + ` + + + true + true + + https://foo.com/apis/{api_name}/maskconfigs + + `, + ["Missing TrustStore in SSLInfo"] + ); + + test( + 60, + "SSLInfo IgnoreValidationErrors = true and Enabled = false", + ` + + + false + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 70, + "SSLInfo ClientAuthEnabled all good", + ` + + + true + true + truststore1 + keystore1 + key1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 71, + "SSLInfo ClientAuthEnabled missing KeyStore + KeyAlias", + ` + + + true + true + truststore1 + + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 72, + "SSLInfo ClientAuthEnabled missing KeyAlias", + ` + + + true + true + truststore1 + keystore1 + + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 73, + "SSLInfo ClientAuthEnabled = false, includes KeyStore + KeyAlias", + ` + + + true + false + truststore1 + keystore1 + key1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 74, + "SSLInfo ClientAuthEnabled element not present, includes KeyStore + KeyAlias", + ` + + + true + + truststore1 + keystore1 + key1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + test( + 80, + "SSLInfo with URL and LoadBalancer", + ` + + + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + + + + + `, + null + ); + + testApigeeX( + 90, + "SSLInfo with Enforce", + ` + + + true + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + null + ); + + testApigeeX( + 91, + "SSLInfo with Enforce", + ` + + + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); + + test( + 92, + "SSLInfo with Enforce - not ApigeeX", + ` + + + true + true + truststore1 + + https://foo.com/apis/{api_name}/maskconfigs + + `, + [] + ); +}); + +describe(`${testID} - Print plugin results`, function () { + debug("test configuration: " + JSON.stringify(configuration)); + var bundle = new Bundle(configuration); + bl.executePlugin(testID, bundle); + let report = bundle.getReport(); + + it("should create a report object with valid schema", function () { + let formatter = bl.getFormatter("json.js"); + if (!formatter) { + assert.fail("formatter implementation not defined"); + } + let schema = require("./../fixtures/reportSchema.js"), + Validator = require("jsonschema").Validator, + v = new Validator(), + jsonReport = JSON.parse(formatter(report)), + validationResult = v.validate(jsonReport, schema); + assert.equal(validationResult.errors.length, 0, validationResult.errors); + }); +}); From 8af2e5850439d55c08a1a32801caba612f4ceab8 Mon Sep 17 00:00:00 2001 From: Dino Chiesa Date: Fri, 14 Jun 2024 14:32:43 -0700 Subject: [PATCH 2/3] fix: improve error message --- lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js b/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js index 2ae936b..c972e89 100644 --- a/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js +++ b/lib/package/plugins/TD006-target-LoadBalancer-SslInfo.js @@ -50,7 +50,7 @@ const onTargetEndpoint = function (endpoint, cb) { plugin, line: sslInfos[0].lineNumber, column: sslInfos[0].columnNumber, - message: `When using a LoadBalancer, the SSLInfo should not be configured under HTTPTargetConnection` + message: `When using a LoadBalancer, the SSLInfo should be configured in the TargetServer, not under HTTPTargetConnection` }); debug(`onTargetEndpoint set flagged`); flagged = true; From 0b3e788c5a52e7c7334d98d30aabc1ac99e50b59 Mon Sep 17 00:00:00 2001 From: Dino Chiesa Date: Fri, 14 Jun 2024 14:48:01 -0700 Subject: [PATCH 3/3] fix: correct TD006 test with proper message --- test/specs/TD006-target-LB-SslInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/specs/TD006-target-LB-SslInfo.js b/test/specs/TD006-target-LB-SslInfo.js index 4a76963..9bb34b3 100644 --- a/test/specs/TD006-target-LB-SslInfo.js +++ b/test/specs/TD006-target-LB-SslInfo.js @@ -177,7 +177,7 @@ describe(`${testID} - ${plugin.plugin.name}`, function () { `, [ - `When using a LoadBalancer, the SSLInfo should not be configured under HTTPTargetConnection` + "When using a LoadBalancer, the SSLInfo should be configured in the TargetServer, not under HTTPTargetConnection" ] );