diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed1ab791..6c756f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 3.1.2-stage.2 (2026-03-03)
+
+* fix: update firewall schema to accept variables (#396) ([607d246](https://github.com/aziontech/lib/commit/607d246)), closes [#396](https://github.com/aziontech/lib/issues/396)
+
+## 3.1.2-stage.1 (2026-03-02)
+
+* fix: firewall functions instances support (#395) ([8d24afc](https://github.com/aziontech/lib/commit/8d24afc)), closes [#395](https://github.com/aziontech/lib/issues/395)
+
## 3.1.1 (2026-02-13)
* Merge pull request #389 from aziontech/stage ([c9bff59](https://github.com/aziontech/lib/commit/c9bff59)), closes [#389](https://github.com/aziontech/lib/issues/389)
diff --git a/package.json b/package.json
index 82a6ca84..17cfe724 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "azion",
- "version": "3.1.1",
+ "version": "3.1.2-stage.2",
"description": "Azion Packages for Edge Computing.",
"scripts": {
"prepare": "husky",
diff --git a/packages/config/src/configProcessor/helpers/azion.config.example.ts b/packages/config/src/configProcessor/helpers/azion.config.example.ts
index 12887f5c..0a95b7ca 100644
--- a/packages/config/src/configProcessor/helpers/azion.config.example.ts
+++ b/packages/config/src/configProcessor/helpers/azion.config.example.ts
@@ -477,40 +477,56 @@ const config: AzionConfig = {
],
firewall: [
{
- name: 'my_firewall',
+ name: 'my-firewall',
active: true,
functions: true,
networkProtection: true,
waf: true,
+ functionsInstances: [
+ {
+ name: 'my_func_instance',
+ ref: 'my_func_name',
+ },
+ ],
rules: [
{
- name: 'rateLimit_Then_Drop',
- active: true,
- match: '^/api/sensitive/',
- variable: 'request_uri',
+ name: 'rule-network',
+ description: 'Rule Network List',
+ criteria: [
+ {
+ variable: 'network',
+ conditional: 'if',
+ operator: 'is_in_list',
+ argument: 'my-ip-allowlist',
+ },
+ {
+ variable: 'network',
+ conditional: 'and',
+ operator: 'is_in_list',
+ argument: 'trusted-asn-list',
+ },
+ ],
behaviors: [
{
- setRateLimit: {
- type: 'second',
- limitBy: 'clientIp',
- averageRateLimit: '10',
- maximumBurstSize: '20',
- },
+ deny: true,
},
],
},
{
- name: 'customResponse_Only',
+ name: 'rateLimit_Then_Drop',
active: true,
criteria: [
{
variable: 'request_uri',
- operator: 'matches',
conditional: 'if',
- argument: '^/custom-error/',
+ operator: 'matches',
+ argument: '^/api/sensitive/',
},
],
behaviors: [
+ {
+ runFunction: 'my_func_instance',
+ },
{
setCustomResponse: {
statusCode: 403,
diff --git a/packages/config/src/configProcessor/helpers/schema.ts b/packages/config/src/configProcessor/helpers/schema.ts
index b900b633..f27d3e57 100644
--- a/packages/config/src/configProcessor/helpers/schema.ts
+++ b/packages/config/src/configProcessor/helpers/schema.ts
@@ -624,6 +624,32 @@ const firewallRulesBehaviorsSchema = {
errorMessage: 'The behaviors array must contain between 1 and 10 behavior items.',
};
+const firewallFunctionsInstances = {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 100,
+ errorMessage: 'The name must be a string between 1 and 100 characters long',
+ },
+ args: {
+ type: 'object',
+ default: {},
+ errorMessage: "The 'args' field must be an object",
+ },
+ ref: {
+ type: ['string', 'number'],
+ errorMessage: "The 'ref' field must be a string or number referencing an existing Function name or ID",
+ },
+ },
+ required: ['name', 'ref'],
+ additionalProperties: false,
+ errorMessage: {
+ additionalProperties: 'No additional properties are allowed in the firewallFunctionsInstance object',
+ },
+};
+
const azionConfigSchema = {
$id: 'azionConfig',
definitions: {
@@ -1361,9 +1387,19 @@ const azionConfigSchema = {
errorMessage: "The rule's 'match' field must be a string containing a valid regex pattern",
},
variable: {
- type: 'string',
- enum: FIREWALL_VARIABLES,
- errorMessage: `The 'variable' field must be one of: ${FIREWALL_VARIABLES.join(', ')}`,
+ anyOf: [
+ {
+ type: 'string',
+ pattern: '^\\$\\{(' + [...new Set(FIREWALL_VARIABLES)].join('|') + ')\\}$',
+ errorMessage: "The 'variable' field must be a valid variable wrapped in ${}",
+ },
+ {
+ type: 'string',
+ enum: [...FIREWALL_VARIABLES],
+ errorMessage: "The 'variable' field must be a valid firewall variable",
+ },
+ ],
+ errorMessage: "The 'variable' field must be a valid variable (with or without ${})",
},
behaviors: firewallRulesBehaviorsSchema,
criteria: {
@@ -1379,9 +1415,19 @@ const azionConfigSchema = {
errorMessage: `The 'conditional' field must be one of: ${FIREWALL_RULE_CONDITIONAL.join(', ')}`,
},
variable: {
- type: 'string',
- enum: FIREWALL_VARIABLES,
- errorMessage: `The 'variable' field must be one of: ${FIREWALL_VARIABLES.join(', ')}`,
+ anyOf: [
+ {
+ type: 'string',
+ pattern: '^\\$\\{(' + [...new Set(FIREWALL_VARIABLES)].join('|') + ')\\}$',
+ errorMessage: "The 'variable' field must be a valid variable wrapped in ${}",
+ },
+ {
+ type: 'string',
+ enum: [...FIREWALL_VARIABLES],
+ errorMessage: "The 'variable' field must be a valid firewall variable",
+ },
+ ],
+ errorMessage: "The 'variable' field must be a valid variable (with or without ${})",
},
operator: {
type: 'string',
@@ -1389,8 +1435,8 @@ const azionConfigSchema = {
errorMessage: `The 'operator' field must be one of: ${FIREWALL_RULE_OPERATORS.join(', ')}`,
},
argument: {
- type: 'string',
- errorMessage: 'The argument must be a string',
+ type: ['string', 'number'],
+ errorMessage: 'The argument must be a string or number',
},
},
required: ['conditional', 'variable', 'operator', 'argument'],
@@ -1428,6 +1474,10 @@ const azionConfigSchema = {
},
},
},
+ functionsInstances: {
+ type: 'array',
+ items: firewallFunctionsInstances,
+ },
},
required: ['name'],
additionalProperties: false,
@@ -2026,10 +2076,9 @@ const azionConfigSchema = {
},
},
additionalProperties: false,
- required: ['build', 'applications', 'workloads'],
errorMessage: {
additionalProperties:
- 'Config can only contain the following properties: build, functions, applications, workloads, purge, edgefirewall, networkList, waf, connectors, customPages',
+ 'Config can only contain the following properties: build, functions, applications, workloads, purge, firewall, networkList, waf, connectors, customPages',
type: 'Configuration must be an object',
},
},
diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts
index dacdaaee..e4a6fb4d 100644
--- a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts
+++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.test.ts
@@ -148,8 +148,8 @@ describe('FirewallProcessConfigStrategy', () => {
// criteria wrapped into array-of-arrays
expect(rule.criteria).toEqual([
[
- { variable: 'host', operator: 'is_equal', conditional: 'if', argument: 'example.com' },
- { variable: 'request_method', operator: 'exists', conditional: 'and' },
+ { variable: '${host}', operator: 'is_equal', conditional: 'if', argument: 'example.com' },
+ { variable: '${request_method}', operator: 'exists', conditional: 'and' },
],
]);
});
@@ -301,8 +301,8 @@ describe('FirewallProcessConfigStrategy', () => {
expect(rule.criteria).toEqual([
[
- { variable: 'host', operator: 'is_equal', conditional: 'if', argument: 'example.com' },
- { variable: 'request_method', operator: 'matches', conditional: 'and', argument: '^(GET|POST)$' },
+ { variable: '${host}', operator: 'is_equal', conditional: 'if', argument: 'example.com' },
+ { variable: '${request_method}', operator: 'matches', conditional: 'and', argument: '^(GET|POST)$' },
],
]);
expect(rule.behaviors).toEqual([{ type: 'deny' }]);
diff --git a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts
index da40bfa4..8d61a493 100644
--- a/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts
+++ b/packages/config/src/configProcessor/processStrategy/implementations/secure/firewallProcessConfigStrategy.ts
@@ -1,4 +1,10 @@
-import { AzionConfig, AzionFirewall, AzionFirewallCriteriaWithValue, AzionFirewallRule } from '../../../../types';
+import {
+ AzionConfig,
+ AzionFirewall,
+ AzionFirewallCriteriaWithValue,
+ AzionFirewallRule,
+ AzionFunction,
+} from '../../../../types';
import ProcessConfigStrategy from '../../processConfigStrategy';
/**
@@ -7,6 +13,22 @@ import ProcessConfigStrategy from '../../processConfigStrategy';
* @description This class is implementation of the Firewall ProcessConfig Strategy.
*/
class FirewallProcessConfigStrategy extends ProcessConfigStrategy {
+ /**
+ * Validate that referenced Function exists
+ */
+ private validateFunctionReference(
+ functions: AzionFunction[] | undefined,
+ functionNameOrId: string | number,
+ instanceName: string,
+ ) {
+ // Only validate if it's a string (name), skip validation for numbers (IDs)
+ if (typeof functionNameOrId === 'string') {
+ if (!Array.isArray(functions) || !functions.find((func) => func.name === functionNameOrId)) {
+ throw new Error(`Function instance "${instanceName}" references non-existent Function "${functionNameOrId}".`);
+ }
+ }
+ }
+
transformToManifest(config: AzionConfig) {
if (!config.firewall || !Array.isArray(config.firewall)) {
return [];
@@ -44,7 +66,7 @@ class FirewallProcessConfigStrategy extends ProcessConfigStrategy {
const { argument, ...rest } = criterion as AzionFirewallCriteriaWithValue;
return {
...rest,
- variable: criterion.variable,
+ variable: criterion.variable.startsWith('${') ? criterion.variable : `\${${criterion.variable}}`,
...(isWithArgument && { argument }),
};
}),
@@ -62,6 +84,18 @@ class FirewallProcessConfigStrategy extends ProcessConfigStrategy {
}));
}
+ if (fw.functionsInstances && fw.functionsInstances.length > 0) {
+ payload.functions_instances = fw.functionsInstances.map((instance) => {
+ this.validateFunctionReference(config.functions, instance.ref, instance.name);
+ return {
+ name: instance.name,
+ args: instance.args || {},
+ active: instance.active ?? true,
+ function: instance.ref,
+ };
+ });
+ }
+
return payload;
});
}
@@ -184,6 +218,16 @@ class FirewallProcessConfigStrategy extends ProcessConfigStrategy {
});
}
+ if (fw.functions_instance && fw.functions_instance.length > 0) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ firewallConfig.functionsInstances = fw.functions_instance.map((instance: any) => ({
+ name: instance.name,
+ args: instance.args || {},
+ active: instance.active ?? true,
+ ref: instance.function,
+ }));
+ }
+
return firewallConfig;
});
return transformedPayload.firewall;
diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts
index 1646efb4..1e5268f1 100644
--- a/packages/config/src/types.ts
+++ b/packages/config/src/types.ts
@@ -53,7 +53,8 @@ export type CommonVariable =
| 'request_method'
| 'request_uri'
| 'scheme'
- | 'uri';
+ | 'uri'
+ | 'network';
export type RequestVariable =
| CommonVariable
| 'server_addr'
@@ -82,7 +83,9 @@ export type RuleOperatorWithValue =
| 'starts_with'
| 'does_not_start_with'
| 'matches'
- | 'does_not_match';
+ | 'does_not_match'
+ | 'is_in_list'
+ | 'is_not_in_list';
export type RuleOperatorWithoutValue = 'exists' | 'does_not_exist';
export type RuleConditional = 'if' | 'and' | 'or';
@@ -597,7 +600,7 @@ export type AzionFirewallCriteriaWithValue = AzionFirewallCriteriaBase & {
/** Operator for comparison that requires input value */
operator: RuleOperatorWithValue;
/** Argument for comparison */
- argument: string;
+ argument: string | number;
};
export type AzionFirewallCriteriaWithoutValue = AzionFirewallCriteriaBase & {
@@ -627,6 +630,17 @@ export type AzionFirewallRule = {
behaviors: AzionFirewallBehavior;
};
+export type AzionFirewallFunctionsInstance = {
+ /** Function instance name */
+ name: string;
+ /** Function instance arguments */
+ args?: Record;
+ /** Active */
+ active?: boolean;
+ /** Reference to Function name or ID */
+ ref: string | number;
+};
+
/**
* Firewall configuration for Azion.
*/
@@ -645,6 +659,8 @@ export type AzionFirewall = {
rules?: AzionFirewallRule[];
/** Debug mode */
debugRules?: boolean;
+ /** Functions Instances */
+ functionsInstances?: AzionFirewallFunctionsInstance[];
};
// WAF V4 Types