Skip to content
Merged
3 changes: 2 additions & 1 deletion graphman.configuration
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"post-export",
"pre-import",
"post-revise",
"post-renew"
"post-renew",
"http-proxy"
]
}
}
102 changes: 102 additions & 0 deletions modules/graphman-extension-http-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved.
module.exports = {
/**
* * Extension to provide HTTP proxy agent
* * @param input http proxy configuration
* * @param input.address proxy server url
* * @param input.credentialRef proxy credential reference
* * @param input.options extra proxy agent options
* * @param context has the context of current operation details
* * @return proxy agent instance
*/
apply: function (input, context) {
Comment thread
graju256 marked this conversation as resolved.

if(input.agentType === "socks") {
return createSocksProxyAgent(input, context);
} else {
return createHttpProxyAgent(input, context);
}
}
}

function createSocksProxyAgent(input, context) {
let agent = null;
const proxyConfig = createProxyConfig(input);

try {
const { SocksProxyAgent } = require("socks-proxy-agent");
agent = new SocksProxyAgent(proxyConfig.url || new URL(input.address), proxyConfig);
} catch (e) {
throw "failed to configure socks proxy agent" + e.message;
}
return agent
}

function createHttpProxyAgent(input, context) {
let agent = null;
const isHttps = context.gateway["address"].startsWith('https://');
const proxyConfig = createProxyConfig(input);
const proxyUrlLower = input.address.toLowerCase();
const isProxyHttps = proxyUrlLower.startsWith('https://');

if (isProxyHttps) {
// If proxy URL uses https://, ensure TLS options are configured if needed
if (!proxyConfig.tls) {
proxyConfig.tls = {};
}
// If rejectUnauthorized is not explicitly set for proxy TLS, default to false for compatibility
if (proxyConfig.tls.rejectUnauthorized === undefined) {
proxyConfig.tls.rejectUnauthorized = false;
}
}
Comment on lines +42 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this section. Please check it whether it is really affecting the client connection behavior.


try {
// Use https-proxy-agent for HTTPS targets, http-proxy-agent for HTTP targets
// Note: The agent type is based on TARGET protocol, not proxy URL protocol

if (isHttps) {
const { HttpsProxyAgent } = require("https-proxy-agent")
agent = new HttpsProxyAgent(input.address, proxyConfig);
} else {
const { HttpProxyAgent } = require("http-proxy-agent")
agent = new HttpProxyAgent(input.address, proxyConfig);
}

} catch (e) {
throw "failed to configure http proxy agent " + e.message;
}

return agent;
}

function createProxyConfig(obj) {
const proxy = {};
const cred = obj.credentialRef;
let auth;

if (cred) {
if (obj.agentType === "socks") {
const url = new URL(obj.address);
url.username = cred.username;
url.password = cred.password;

proxy.url = url;
} else {
auth = `Basic ${Buffer.from(`${cred.username}:${cred.password}`).toString('base64')}`;
}
}

Object.keys(obj.options).forEach(key => {
proxy[key] = obj.options[key];
});

if (!proxy.headers) {
proxy.headers = {};
}

if (auth) {
proxy.headers["Proxy-Authorization"] = auth;
}

return proxy;
}
44 changes: 39 additions & 5 deletions modules/graphman.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ const SUPPORTED_OPERATIONS = [
"config"
];

const SUPPORTED_EXTENSIONS = ["pre-request", "post-export", "pre-import", "post-revise", "post-renew", "multiline-text-diff", "policy-code-validator"];

const SUPPORTED_EXTENSIONS = ["pre-request", "post-export", "pre-import", "post-revise", "post-renew", "multiline-text-diff", "policy-code-validator", "https-proxy"];
const SCHEMA_FEATURE_LIST = {
"v11.2.1": ["mappings", "mappings-source", "policy-as-code"],
"v11.2.0": ["mappings", "mappings-source", "policy-as-code"],
Expand Down Expand Up @@ -62,8 +61,10 @@ module.exports = {
utils.logAt(config.options.log);

config.credentials = makeCredentials(config.credentials || {});
config.proxies = makeProxies(config.proxies || {});
config.gateways = makeGateways(config.gateways || {});


// override configured gateway details using params if specified
if (params.gateways) Object.keys(params.gateways).forEach(key => {
const gateway = params.gateways[key];
Expand All @@ -86,6 +87,7 @@ module.exports = {
defaultConfiguration: function () {
return {
credentials: makeCredentials({}),
proxies: makeProxies({}),
gateways: makeGateways({}),
options: makeOptions({})
}
Expand All @@ -104,6 +106,20 @@ module.exports = {
return obj;
},

proxyConfiguration: function (name) {
const config = this.configuration();
const obj = name ? Object.assign({name: name}, config.proxies[name]) : null;
if (!obj) {
return null;
}

if (obj.credential) {
obj["credentialRef"] = config.credentials[obj.credential];
}

return obj;
},

schemaMetadata: function () {
return this.metadata;
},
Expand Down Expand Up @@ -222,6 +238,10 @@ module.exports = {
attachCredential(req, gateway, "<local>");
}

if (gateway.proxy) {
req.proxy = this.proxyConfiguration(gateway.proxy);
}

const globalOptions = this.loadedConfig && this.loadedConfig.options ? this.loadedConfig.options : {};
if (globalOptions.caFilename) {
// Trusted CA certificate(s) for server verification (PEM; file may contain multiple certs).
Expand All @@ -239,8 +259,16 @@ module.exports = {
* @param callback
*/
invoke: function (options, opContext, callback) {
Comment thread
graju256 marked this conversation as resolved.

if (options.proxy) {
options.agent = utils.extension("http-proxy").apply(options.proxy, opContext);
options.proxy = null;
}

options = utils.extension("pre-request").apply(options, opContext);
const req = ((!options.protocol||options.protocol === 'https'||options.protocol === 'https:') ? https : http).request(options, function(response) {
const isHttps = !options.protocol || options.protocol === 'https' || options.protocol === 'https:';

const req = (isHttps ? https : http).request(options, function(response) {
let respInfo = {initialized: false, chunks: []};

response.on('data', function (chunk) {
Expand Down Expand Up @@ -356,7 +384,7 @@ function makeOptions(options) {
"policyCodeFormat": "xml",
"keyFormat": "p12",
"caFilename": null,
"extensions": ["pre-request", "post-export", "pre-import", "post-revise", "post-renew"]
"extensions": ["pre-request", "post-export", "pre-import", "post-revise", "post-renew", "http-proxy"]
}, options);
}

Expand All @@ -374,6 +402,12 @@ function makeCredentials(credentials) {
return credentials;
}

function makeProxies(proxies) {
// No hard default; just ensure each proxy knows its name if useful
Object.entries(proxies).forEach(([key, item]) => item['name'] = key);
return proxies;
}

function makeGateways(gateways) {
// populate default gateway if no gateway profiles are defined
if (!Object.keys(gateways).length) {
Expand Down Expand Up @@ -401,4 +435,4 @@ function pascalCasing(text) {
}

return text;
}
}