Skip to content

Commit

Permalink
Use secret interpolation from api3/commons (#1962)
Browse files Browse the repository at this point in the history
* Update dependency: @api3/commons@^0.7.1

* Use secret interpolation from api3/commons

* Add back secret name validation to config validation

---------

Co-authored-by: Emanuel Tesař <e.tesarr@gmail.com>
  • Loading branch information
dcroote and Siegrift authored Mar 3, 2024
1 parent e07a3b9 commit 1c49d6f
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-years-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@api3/airnode-validator": patch
---

Use secret interpolation from api3/commons
2 changes: 1 addition & 1 deletion packages/airnode-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@api3/airnode-utilities": "^0.14.0",
"@api3/airnode-validator": "^0.14.0",
"@api3/chains": "^4.9.0",
"@api3/commons": "^0.6.2",
"@api3/commons": "^0.7.1",
"@api3/ois": "2.3.2",
"@api3/promise-utils": "^0.4.0",
"@aws-sdk/client-lambda": "^3.518.0",
Expand Down
1 change: 1 addition & 0 deletions packages/airnode-validator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"dependencies": {
"@api3/airnode-protocol": "^0.14.0",
"@api3/commons": "^0.7.1",
"@api3/ois": "2.3.2",
"@api3/promise-utils": "^0.4.0",
"dotenv": "^16.4.5",
Expand Down
57 changes: 9 additions & 48 deletions packages/airnode-validator/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { goSync } from '@api3/promise-utils';
import template from 'lodash/template';
import reduce from 'lodash/reduce';
import { interpolateSecretsIntoConfig } from '@api3/commons';
import { z } from 'zod';
import { Config, configSchema } from '../config';
import { Receipt, receiptSchema } from '../receipt';
Expand All @@ -18,7 +17,9 @@ export function parseConfigWithSecrets(config: unknown, secrets: unknown): Valid
const parsedSecrets = parseSecrets(secrets);
if (!parsedSecrets.success) return parsedSecrets;

const interpolateConfigRes = interpolateSecrets(config, parsedSecrets.data);
const interpolateConfigRes = goSync(() =>
interpolateSecretsIntoConfig(config, parsedSecrets.data, { allowBlankSecretValue: false, validateSecretName: true })
);
if (!interpolateConfigRes.success) {
return {
success: false,
Expand Down Expand Up @@ -59,56 +60,16 @@ export function parseReceipt(receipt: unknown): ValidationResult<Receipt> {
return receiptSchema.safeParse(receipt);
}

// Regular expression that does not match anything, ensuring no escaping or interpolation happens
// https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L199
const NO_MATCH_REGEXP = /($^)/;
// Regular expression matching ES template literal delimiter (${}) with escaping
// https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L175
const ES_MATCH_REGEXP = /(?<!\\)\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
// Regular expression matching the escaped ES template literal delimiter (${}). We need to use "\\\\" (four backslashes)
// because "\\" becomes "\\\\" when converted to string
const ESCAPED_ES_MATCH_REGEXP = /\\\\(\$\{([^\\}]*(?:\\.[^\\}]*)*)\})/g;

function interpolateSecrets(config: unknown, secrets: Secrets): ValidationResult<unknown> {
const stringifiedSecrets = reduce(
secrets,
(acc, value, key) => {
return {
...acc,
// Convert to value to JSON to encode new lines as "\n". The resulting value will be a JSON string with quotes
// which are sliced off.
[key]: JSON.stringify(value).slice(1, -1),
};
},
{} as Secrets
);

const interpolationRes = goSync(() =>
JSON.parse(
template(JSON.stringify(config), {
escape: NO_MATCH_REGEXP,
evaluate: NO_MATCH_REGEXP,
interpolate: ES_MATCH_REGEXP,
})(stringifiedSecrets)
)
);

if (!interpolationRes.success) return interpolationRes;

const interpolatedConfig = JSON.stringify(interpolationRes.data);
// Un-escape the escaped config interpolations (e.g. to enable interpolation in processing snippets)
return goSync(() => JSON.parse(interpolatedConfig.replace(ESCAPED_ES_MATCH_REGEXP, '$1')));
}

/**
* Used to interpolate secrets into config. This function only interpolates the secrets and does not perform any
* validation. Only use this function when you are sure the interpolation result is a valid Airnode config.
*
* In case there is an error when interpolating secrets the function throws an error.
*/
export function unsafeParseConfigWithSecrets(config: unknown, secrets: Secrets): Config {
const interpolationResult = interpolateSecrets(config, secrets);
if (!interpolationResult.success) throw interpolationResult.error;

return interpolationResult.data as Config;
// System and docker secrets passed via process.env do not necessarily obey the expected secret name pattern
return interpolateSecretsIntoConfig(config, secrets, {
allowBlankSecretValue: true,
validateSecretName: false,
}) as Config;
}
10 changes: 6 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@
viem "^2.7.1"
zod "^3.22.4"

"@api3/commons@^0.6.2":
version "0.6.2"
resolved "https://registry.yarnpkg.com/@api3/commons/-/commons-0.6.2.tgz#e25d1bce560e53bf44075d08295bf243365c0bfd"
integrity sha512-IBR9WAcbglFdMyehiv3MWFVK1jKv2jAao6tU2DcUmMztea0H+Nn9xAomHCwAlg7mTNn3qk7ZpWosZNGZjfeSTw==
"@api3/commons@^0.7.1":
version "0.7.1"
resolved "https://registry.npmjs.org/@api3/commons/-/commons-0.7.1.tgz#f9c99ce31cfc2dc332945e396a4d9b646199d698"
integrity sha512-Cm2OfuLklEffSh2/DFnfyOBpa/JC3mXi4Xx0MUVZU8aqcGkc62bD3CqLXRXHEsxe2IQLg3B4NZhOZeNEWFl0nQ==
dependencies:
"@api3/ois" "^2.3.0"
"@api3/promise-utils" "^0.4.0"
"@typescript-eslint/eslint-plugin" "^6.2.1"
"@typescript-eslint/parser" "^6.2.1"
axios "^1.6.7"
dotenv "^16.4.5"
eslint-config-next "^13.1.6"
eslint-plugin-check-file "^2.6.2"
eslint-plugin-cypress "^2.14.0"
Expand Down

0 comments on commit 1c49d6f

Please sign in to comment.