Relay proxy
Using a proxy helps send sensitive data to the destination without exposing it to your servers, for example initiating a payment via Stripe API while ensuring PCI-compliance.
Modernbanc's low-latency and high-throughput workflow engine allows us to build and deploy a proxy fit for our use-case in minutes.
In this guide we'll build two examples of proxies: Static Proxy and Dynamic Proxy.
Static proxy
A static proxy is when we know exactly which fields need to be decrypted when forwarding your request to a third-party.
Let's assume we already know that this proxy will be used to call a specific Stripe API endpoint with a specific request body. The workflow will consist of the following steps:
This step retrieve the secrets by ids that were passed in the request body. Add a find
step with following parameters:
Parameter | Value |
---|---|
Type | Many |
Model | Secret |
Filters | in: [[steps[0].result.secret_in_filter_value]] |
Include | value (Our decrypted value) |
This step will actually make an API call to Stripe.
Parameter | Value |
---|---|
Type | HTTP Query |
Method | POST |
URL | api.stripe.com/v1 |
Dynamic Proxy
A dynamic proxy is when we dynamically detect presence of tokens in the payload and then replace it in the body - allowing for flexibility without a need to redeploy.
Our proxy workflow will contain the following steps.
This function
step will run custom Javascript code that looks through the specified object (in our case a request body) and scans for presence of tokens.
In this example we mark tokens by surrounding them with ||
, for example you might make a POST
request with the following body:
{
"card_number": "||SECRET_MAR24_LhkYjGQ3XAsyOV6pMkb||",
"card_expiry_month": "||SECRET_MAR24_5XGYKsfmmtToNUw9ezb||",
"card_expiry_year": "||SECRET_MAR24_JeebDVhj9Sify463jt7||",
"card_cvv": "||SECRET_MAR24_cwH5m18GrBigoCDutnn||"
}
Step body
Paste the following code in the workflow step code:
function findWrappedSubstrings(obj, path = '') {
let results = [];
let detectedTexts = []; // Array to store all detected texts for substitutions
const regex = /\|\|([^|]+)\|\|/g;
const traverse = (element, currentPath) => {
if (typeof element === 'object' && element !== null) {
for (const key in element) {
if (element.hasOwnProperty(key)) {
traverse(element[key], `${currentPath}${currentPath ? '.' : ''}${key}`);
}
}
} else if (typeof element === 'string') {
const positions = [];
let match;
while ((match = regex.exec(element)) !== null) {
const originalText = match[1];
positions.push({
start: match.index,
end: regex.lastIndex - 1,
length: originalText.length,
originalText: originalText
});
if (!detectedTexts.includes(originalText)) {
detectedTexts.push(originalText); // Check for missing parentheses here
}
}
if (positions.length > 0) {
results.push({
path: currentPath,
positions: positions
});
}
}
};
traverse(obj, path);
return { results, detectedTexts };
}
try {
const { results, detectedTexts } = findWrappedSubstrings(_trigger_version._input.body);
const secret_in_filter_value = detectedTexts.join(",")
callback({results, secret_in_filter_value}, null)
}
catch (err) {
callback(null, err)
}
This step will get comma-separated secret ids from the step above and retrieve underlying secrets and their values.
Add a find
step with following parameters:
Parameter | Value |
---|---|
Type | Many |
Model | Secret |
Filters | in: [[steps[0].result.secret_in_filter_value]] |
Include | value (Our decrypted value) |
In this step we'll use decrypted values found in previous step and create a final payload. Paste the following code in the workflow step code:
function substituteSecrets(obj, secretValues) {
const regex = /\|\|([^|]+)\|\|/g; // Regular expression to find secret placeholders
const traverseAndReplace = (element) => {
if (typeof element === 'object' && element !== null) {
for (const key in element) {
if (element.hasOwnProperty(key)) {
element[key] = traverseAndReplace(element[key]); // Recursive replacement
}
}
} else if (typeof element === 'string') {
// Check if the string exactly matches a secret ID pattern
const match = element.match(/^(\|\|([^|]+)\|\|)$/);
if (match && secretValues.hasOwnProperty(match[2])) {
return secretValues[match[2]]; // Directly return the value, preserving its type
}
// Replace all occurrences in strings without changing the entire string's nature
return element.replace(regex, (match, secretId) => {
return secretValues[secretId] !== undefined ? secretValues[secretId].toString() : match;
});
}
return element;
};
return traverseAndReplace(obj);
}
const found_secret_map = steps[1].result?.items.reduce((acc, item) => {
acc[item.id] = item.value;
return acc;
}, {});
const final_body = substituteSecrets(_trigger_version._input.body, found_secret_map);
callback(final_body, null)
Add any step to send a request either API or a database with a final decrypted payload from previous step.
In this example we'll send a POST request to Stripe. Create a connection_query
step.
Parameter | Value |
---|---|
Type | HTTP Query |
Method | POST |
URL | api.example.com/v1 |
Body | [[steps[3].result ?? {}]] |
Removing decrypted data from execution logs.
After testing the workflow in test
environment ensure you redacted all sensitive data from executions before deploying to production.
For example we could provide a replacements array in Version
tab as follows:
[
{
"paths": ["data[1].result, data[2].result, data[3].result"],
"with": "REDACTED"
}
]
Conclusion
That's it - this is just an example of how to deploy a proxy on Modernbanc. Our platform offers unmatched flexibility so you can tailor the steps to your use-case.
If you have any confusion on how to proceed or need help - we're just a .