From 5e7b232df01277caf86426abe7608e4b48ea63bf Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 6 Feb 2024 14:33:18 +0100 Subject: [PATCH 1/4] Add rego templating using handlebars --- apps/authz/Makefile | 9 + .../src/opa/rego/lib/criteria/resource.rego | 14 +- apps/authz/src/opa/rego/policies/e2e.rego | 69 ---- apps/authz/src/opa/template/mockData.ts | 112 ++++++ apps/authz/src/opa/template/script.ts | 73 ++++ apps/authz/src/opa/template/template.hbs | 11 + .../src/shared/types/policy-builder.type.ts | 347 ++++++------------ package-lock.json | 38 ++ package.json | 1 + 9 files changed, 370 insertions(+), 304 deletions(-) delete mode 100644 apps/authz/src/opa/rego/policies/e2e.rego create mode 100644 apps/authz/src/opa/template/mockData.ts create mode 100644 apps/authz/src/opa/template/script.ts create mode 100644 apps/authz/src/opa/template/template.hbs diff --git a/apps/authz/Makefile b/apps/authz/Makefile index b666b916d..b60db8a6d 100644 --- a/apps/authz/Makefile +++ b/apps/authz/Makefile @@ -106,6 +106,15 @@ authz/rego/wasm: --compiler-options "{\"module\":\"CommonJS\"}" \ ${AUTHZ_PROJECT_DIR}/src/opa/rego/script.ts +authz/rego/template: + npx dotenv -e ${AUTHZ_PROJECT_DIR}/.env -- \ + ts-node -r tsconfig-paths/register \ + --project ${AUTHZ_PROJECT_DIR}/tsconfig.app.json ${AUTHZ_PROJECT_DIR}/src/opa/template/script.ts + + make authz/rego/compile + + make authz/rego/wasm + authz/rego/bundle: rm -rf ${AUTHZ_PROJECT_DIR}/src/opa/build diff --git a/apps/authz/src/opa/rego/lib/criteria/resource.rego b/apps/authz/src/opa/rego/lib/criteria/resource.rego index dc388776e..caf7cc1a0 100644 --- a/apps/authz/src/opa/rego/lib/criteria/resource.rego +++ b/apps/authz/src/opa/rego/lib/criteria/resource.rego @@ -5,8 +5,13 @@ import future.keywords.in resource = data.entities.wallets[input.resource.uid] checkTransferResourceIntegrity { - contains(input.resource.uid, input.transactionRequest.from) - input.resource.uid == input.intent.from + checkAction({"signTransaction"}) + transactionRequestFromAddress = input.transactionRequest.from + resourceAddress = extractAddressFromCaip10(input.resource.uid) + intentFromAddress = extractAddressFromCaip10(input.intent.from) + transactionRequestFromAddress == resourceAddress + transactionRequestFromAddress == intentFromAddress + resourceAddress == intentFromAddress } walletGroups = {group.uid | @@ -31,3 +36,8 @@ checkWalletGroup(values) { group = walletGroups[_] group in values } + +extractAddressFromCaip10(caip10) = result { + arr = split(caip10, ":") + result = arr[count(arr) - 1] +} diff --git a/apps/authz/src/opa/rego/policies/e2e.rego b/apps/authz/src/opa/rego/policies/e2e.rego deleted file mode 100644 index 98c5afc60..000000000 --- a/apps/authz/src/opa/rego/policies/e2e.rego +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import future.keywords.in - -permit[{"policyId": "test-permit-policy-1"}] = reason { - users = {"matt@narval.xyz"} - resources = {"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"} - transferTypes = {"transferNative"} - tokens = {"eip155:137/slip44:966"} - transferValueCondition = {"currency": "*", "operator": "lte", "value": "1000000000000000000"} - approvalsRequired = [{ - "approvalCount": 2, - "countPrincipal": false, - "approvalEntityType": "Narval::User", - "entityIds": ["aa@narval.xyz", "bb@narval.xyz"], - }] - - checkPrincipal - checkNonceExists - checkAction({"signTransaction"}) - checkPrincipalId(users) - checkWalletId(resources) - checkIntentType(transferTypes) - checkIntentToken(tokens) - checkIntentAmount(transferValueCondition) - - approvals = checkApprovals(approvalsRequired) - - reason = { - "type": "permit", - "policyId": "test-permit-policy-1", - "approvalsSatisfied": approvals.approvalsSatisfied, - "approvalsMissing": approvals.approvalsMissing, - } -} - -forbid[{"policyId": "test-forbid-policy-1"}] = reason { - users = {"matt@narval.xyz"} - resources = {"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"} - transferTypes = {"transferNative"} - tokens = {"eip155:137/slip44:966"} - limit = "1000000000000000000" - - checkPrincipal - checkNonceExists - checkAction({"signTransaction"}) - checkPrincipalId(users) - checkWalletId(resources) - checkIntentType(transferTypes) - checkIntentToken(tokens) - checkSpendingLimit({ - "limit": limit, - "timeWindow": { - "type": "rolling", - "value": (12 * 60) * 60, - }, - "filters": { - "tokens": tokens, - "users": users, - }, - }) - - reason = { - "type": "forbid", - "policyId": "test-forbid-policy-1", - "approvalsSatisfied": [], - "approvalsMissing": [], - } -} diff --git a/apps/authz/src/opa/template/mockData.ts b/apps/authz/src/opa/template/mockData.ts new file mode 100644 index 000000000..8a679871a --- /dev/null +++ b/apps/authz/src/opa/template/mockData.ts @@ -0,0 +1,112 @@ +import { Criterion, PolicyCriterionBuilder, Then } from '@app/authz/shared/types/policy-builder.type' +import { Action } from '@narval/authz-shared' +import { Intents } from '@narval/transaction-request-intent' + +export const examplePermitPolicy: PolicyCriterionBuilder = { + then: Then.PERMIT, + name: 'examplePermitPolicy', + when: [ + { + criterion: Criterion.CHECK_TRANSFER_RESOURCE_INTEGRITY, + args: null + }, + { + criterion: Criterion.CHECK_NONCE_EXISTS, + args: null + }, + { + criterion: Criterion.CHECK_ACTION, + args: [Action.SIGN_TRANSACTION] + }, + { + criterion: Criterion.CHECK_PRINCIPAL_ID, + args: ['matt@narval.xyz'] + }, + { + criterion: Criterion.CHECK_WALLET_ID, + args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] + }, + { + criterion: Criterion.CHECK_INTENT_TYPE, + args: [Intents.TRANSFER_NATIVE] + }, + { + criterion: Criterion.CHECK_INTENT_TOKEN, + args: ['eip155:137/slip44:966'] + }, + { + criterion: Criterion.CHECK_INTENT_AMOUNT, + args: { currency: '*', operator: 'lte', value: '1000000000000000000' } + }, + { + criterion: Criterion.CHECK_APPROVALS, + args: [ + { + approvalCount: 2, + countPrincipal: false, + approvalEntityType: 'Narval::User', + entityIds: ['aa@narval.xyz', 'bb@narval.xyz'] + }, + { + approvalCount: 1, + countPrincipal: false, + approvalEntityType: 'Narval::UserRole', + entityIds: ['admin'] + } + ] + } + ] +} + +export const exampleForbidPolicy: PolicyCriterionBuilder = { + then: Then.FORBID, + name: 'exampleForbidPolicy', + when: [ + { + criterion: Criterion.CHECK_TRANSFER_RESOURCE_INTEGRITY, + args: null + }, + { + criterion: Criterion.CHECK_NONCE_EXISTS, + args: null + }, + { + criterion: Criterion.CHECK_ACTION, + args: [Action.SIGN_TRANSACTION] + }, + { + criterion: Criterion.CHECK_PRINCIPAL_ID, + args: ['matt@narval.xyz'] + }, + { + criterion: Criterion.CHECK_WALLET_ID, + args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] + }, + { + criterion: Criterion.CHECK_INTENT_TYPE, + args: [Intents.TRANSFER_NATIVE] + }, + { + criterion: Criterion.CHECK_INTENT_TOKEN, + args: ['eip155:137/slip44:966'] + }, + { + criterion: Criterion.CHECK_SPENDING_LIMIT, + args: { + limit: '1000000000000000000', + timeWindow: { + type: 'rolling', + value: 12 * 60 * 60 + }, + filters: { + tokens: ['eip155:137/slip44:966'], + users: ['matt@narval.xyz'] + } + } + } + ] +} + +export const policies = { + policies: [examplePermitPolicy, exampleForbidPolicy] +} diff --git a/apps/authz/src/opa/template/script.ts b/apps/authz/src/opa/template/script.ts new file mode 100644 index 000000000..c6f50eefc --- /dev/null +++ b/apps/authz/src/opa/template/script.ts @@ -0,0 +1,73 @@ +import { Criterion, Then } from '@app/authz/shared/types/policy-builder.type' +import { readFileSync, writeFileSync } from 'fs' +import Handlebars from 'handlebars' +import { isEmpty } from 'lodash' +import { policies } from './mockData' + +Handlebars.registerHelper('criterion', function (item, options) { + const criterion: Criterion = item.criterion + const args = item.args + + if (args === null) { + return `${criterion}` + } + + if (!isEmpty(args)) { + if (Array.isArray(args)) { + if (typeof args[0] === 'string') { + return `${criterion}({${args.map((el) => `"${el}"`).join(', ')}})` + } + + if (criterion === Criterion.CHECK_APPROVALS) { + return `approvals = ${criterion}([${args.map((el) => JSON.stringify(el)).join(', ')}])` + } + + return `${criterion}([${args.map((el) => JSON.stringify(el)).join(', ')}])` + } + + return `${criterion}(${JSON.stringify(args)})` + } +}) + +Handlebars.registerHelper('reason', function (item, options) { + if (item.then === Then.PERMIT) { + const reason = [ + `"type": "${item.then}"`, + `"policyId": "${item.name}"`, + '"approvalsSatisfied": approvals.approvalsSatisfied', + '"approvalsMissing": approvals.approvalsMissing' + ] + return `reason = {${reason.join(', ')}}` + } + + if (item.then === Then.FORBID) { + const reason = { + type: item.then, + policyId: item.name, + approvalsSatisfied: [], + approvalsMissing: [] + } + return `reason = ${JSON.stringify(reason)}` + } +}) + +// Read the template file +const templateSource = readFileSync( + '/Users/samuel/Documents/narval/narval/apps/authz/src/opa/template/template.hbs', + 'utf-8' +) + +// Compile the template +const template = Handlebars.compile(templateSource) + +// Generate Rego file content +const regoContent = template(policies) + +// Write the content to a Rego file +writeFileSync( + '/Users/samuel/Documents/narval/narval/apps/authz/src/opa/rego/policies/generatedPolicies.rego', + regoContent, + 'utf-8' +) + +console.log('Policy .rego file generated successfully.') diff --git a/apps/authz/src/opa/template/template.hbs b/apps/authz/src/opa/template/template.hbs new file mode 100644 index 000000000..bca14bd9b --- /dev/null +++ b/apps/authz/src/opa/template/template.hbs @@ -0,0 +1,11 @@ +package main + +{{#each policies}} +{{ then }}[{"policyId": "{{ name }}" }] = reason { + {{#each when}} + {{#criterion this}}{{/criterion}} + {{/each}} + {{#reason this}}{{/reason}} +} + +{{/each}} diff --git a/apps/authz/src/shared/types/policy-builder.type.ts b/apps/authz/src/shared/types/policy-builder.type.ts index 4bb228d52..537a63a7f 100644 --- a/apps/authz/src/shared/types/policy-builder.type.ts +++ b/apps/authz/src/shared/types/policy-builder.type.ts @@ -1,45 +1,46 @@ import { AccountId, AccountType, Action, Address, Alg, AssetId, Hex } from '@narval/authz-shared' import { Intents } from '@narval/transaction-request-intent' -enum Then { +export enum Then { PERMIT = 'permit', FORBID = 'forbid' } -enum Criterion { - ACTION = 'action', - PRINCIPAL_ID = 'principalId', - PRINCIPAL_ROLE = 'principalRole', - PRINCIPAL_GROUP = 'principalGroup', - WALLET_ID = 'walletId', - WALLET_ADDRESS = 'walletAddress', - WALLET_ACCOUNT_TYPE = 'walletAccountType', - WALLET_CHAIN_ID = 'walletChainId', - WALLET_GROUP = 'walletGroup', - INTENT_TYPE = 'intentType', - INTENT_DESTINATION_ID = 'intentDestinationId', - INTENT_DESTINATION_ADDRESS = 'intentDestinationAddress', - INTENT_DESTINATION_ACCOUNT_TYPE = 'intentDestinationAccountType', - INTENT_DESTINATION_CLASSIFICATION = 'intentDestinationClassification', - INTENT_CONTRACT = 'intentContract', - INTENT_TOKEN = 'intentToken', - INTENT_SPENDER = 'intentSpender', - INTENT_CHAIN_ID = 'intentChainId', - INTENT_HEX_SIGNATURE = 'intentHexSignature', - INTENT_AMOUNT = 'intentAmount', - INTENT_ERC721_TOKEN_ID = 'intentERC721TokenId', - INTENT_ERC1155_TOKEN_ID = 'intentERC1155TokenId', - INTENT_ERC1155_TRANSFERS = 'intentERC1155Transfers', - INTENT_SIGN_MESSAGE = 'intentSignMessage', - INTENT_SIGN_RAW_PAYLOAD = 'intentSignRawPayload', - INTENT_SIGN_RAW_PAYLOAD_ALGORITHM = 'intentSignRawPayloadAlgorithm', - INTENT_SIGN_TYPED_DATA_DOMAIN = 'intentSignTypedDataDomain', - INTENT_PERMIT_DEADLINE = 'intentPermitDeadline', - TRANSACTION_REQUEST_GAS_FEES = 'transactionRequestGasFees', - TRANSACTION_REQUEST_NONCE_REQUIRED = 'transactionRequestNonceRequired', - TRANSACTION_REQUEST_NONCE_NOT_REQUIRED = 'transactionRequestNonceNotRequired', - APPROVALS = 'approvals', - SPENDING_LIMIT = 'spendingLimit' +export enum Criterion { + CHECK_ACTION = 'checkAction', + CHECK_TRANSFER_RESOURCE_INTEGRITY = 'checkTransferResourceIntegrity', + CHECK_PRINCIPAL_ID = 'checkPrincipalId', + CHECK_PRINCIPAL_ROLE = 'checkPrincipalRole', + CHECK_PRINCIPAL_GROUP = 'checkPrincipalGroup', + CHECK_WALLET_ID = 'checkWalletId', + CHECK_WALLET_ADDRESS = 'checkWalletAddress', + CHECK_WALLET_ACCOUNT_TYPE = 'checkWalletAccountType', + CHECK_WALLET_CHAIN_ID = 'checkWalletChainId', + CHECK_WALLET_GROUP = 'checkWalletGroup', + CHECK_INTENT_TYPE = 'checkIntentType', + CHECK_DESTINATION_ID = 'checkDestinationId', + CHECK_DESTINATION_ADDRESS = 'checkDestinationAddress', + CHECK_DESTINATION_ACCOUNT_TYPE = 'checkDestinationAccountType', + CHECK_DESTINATION_CLASSIFICATION = 'checkDestinationClassification', + CHECK_INTENT_CONTRACT = 'checkIntentContract', + CHECK_INTENT_TOKEN = 'checkIntentToken', + CHECK_INTENT_SPENDER = 'checkIntentSpender', + CHECK_INTENT_CHAIN_ID = 'checkIntentChainId', + CHECK_INTENT_HEX_SIGNATURE = 'checkIntentHexSignature', + CHECK_INTENT_AMOUNT = 'checkIntentAmount', + CHECK_ERC721_TOKEN_ID = 'checkERC721TokenId', + CHECK_ERC1155_TOKEN_ID = 'checkERC1155TokenId', + CHECK_ERC1155_TRANSFERS = 'checkERC1155Transfers', + CHECK_INTENT_MESSAGE = 'checkIntentMessage', + CHECK_INTENT_PAYLOAD = 'checkIntentPayload', + CHECK_INTENT_ALGORITHM = 'checkIntentAlgorithm', + CHECK_INTENT_DOMAIN = 'checkIntentDomain', + CHECK_PERMIT_DEADLINE = 'checkPermitDeadline', + CHECK_GAS_FEE_AMOUNT = 'checkGasFeeAmount', + CHECK_NONCE_EXISTS = 'checkNonceExists', + CHECK_NONCE_NOT_EXISTS = 'checkNonceNotExists', + CHECK_APPROVALS = 'checkApprovals', + CHECK_SPENDING_LIMIT = 'checkSpendingLimit' } type Wildcard = '*' @@ -106,170 +107,178 @@ type SpendingLimitCondition = { } type ActionCriterion = { - criterion: Criterion.ACTION + criterion: Criterion.CHECK_ACTION args: Action[] } +type TransferResourceIntegrityCriterion = { + criterion: Criterion.CHECK_TRANSFER_RESOURCE_INTEGRITY + args: null +} + type PrincipalIdCriterion = { - criterion: Criterion.PRINCIPAL_ID + criterion: Criterion.CHECK_PRINCIPAL_ID args: string[] } type PrincipalRoleCriterion = { - criterion: Criterion.PRINCIPAL_ROLE + criterion: Criterion.CHECK_PRINCIPAL_ROLE args: string[] } type PrincipalGroupCriterion = { - criterion: Criterion.PRINCIPAL_GROUP + criterion: Criterion.CHECK_PRINCIPAL_GROUP args: string[] } type WalletIdCriterion = { - criterion: Criterion.WALLET_ID - args: AccountId[] + criterion: Criterion.CHECK_WALLET_ID + args: string[] } type WalletAddressCriterion = { - criterion: Criterion.WALLET_ADDRESS + criterion: Criterion.CHECK_WALLET_ADDRESS args: string[] } type WalletAccountTypeCriterion = { - criterion: Criterion.WALLET_ACCOUNT_TYPE + criterion: Criterion.CHECK_WALLET_ACCOUNT_TYPE args: AccountType[] } type WalletChainIdCriterion = { - criterion: Criterion.WALLET_CHAIN_ID + criterion: Criterion.CHECK_WALLET_CHAIN_ID args: string[] } type WalletGroupCriterion = { - criterion: Criterion.WALLET_GROUP + criterion: Criterion.CHECK_WALLET_GROUP args: string[] } type IntentTypeCriterion = { - criterion: Criterion.INTENT_TYPE + criterion: Criterion.CHECK_INTENT_TYPE args: Intents[] } -type IntentDestinationIdCriterion = { - criterion: Criterion.INTENT_DESTINATION_ID +type DestinationIdCriterion = { + criterion: Criterion.CHECK_DESTINATION_ID args: AccountId[] } -type IntentDestinationAddressCriterion = { - criterion: Criterion.INTENT_DESTINATION_ADDRESS +type DestinationAddressCriterion = { + criterion: Criterion.CHECK_DESTINATION_ADDRESS args: string[] } -type IntentDestinationAccountTypeCriterion = { - criterion: Criterion.INTENT_DESTINATION_ACCOUNT_TYPE +type DestinationAccountTypeCriterion = { + criterion: Criterion.CHECK_DESTINATION_ACCOUNT_TYPE args: AccountType[] } -type IntentDestinationClassificationCriterion = { - criterion: Criterion.INTENT_DESTINATION_CLASSIFICATION +type DestinationClassificationCriterion = { + criterion: Criterion.CHECK_DESTINATION_CLASSIFICATION args: string[] } type IntentContractCriterion = { - criterion: Criterion.INTENT_CONTRACT + criterion: Criterion.CHECK_INTENT_CONTRACT args: AccountId[] } type IntentTokenCriterion = { - criterion: Criterion.INTENT_TOKEN + criterion: Criterion.CHECK_INTENT_TOKEN args: AccountId[] } type IntentSpenderCriterion = { - criterion: Criterion.INTENT_SPENDER + criterion: Criterion.CHECK_INTENT_SPENDER args: AccountId[] } type IntentChainIdCriterion = { - criterion: Criterion.INTENT_CHAIN_ID + criterion: Criterion.CHECK_INTENT_CHAIN_ID args: string[] } type IntentHexSignatureCriterion = { - criterion: Criterion.INTENT_HEX_SIGNATURE + criterion: Criterion.CHECK_INTENT_HEX_SIGNATURE args: Hex[] } type IntentAmountCriterion = { - criterion: Criterion.INTENT_AMOUNT + criterion: Criterion.CHECK_INTENT_AMOUNT args: AmountCondition } -type IntentERC721TokenIdCriterion = { - criterion: Criterion.INTENT_ERC721_TOKEN_ID +type ERC721TokenIdCriterion = { + criterion: Criterion.CHECK_ERC721_TOKEN_ID args: AssetId[] } -type IntentERC1155TokenIdCriterion = { - criterion: Criterion.INTENT_ERC1155_TOKEN_ID +type ERC1155TokenIdCriterion = { + criterion: Criterion.CHECK_ERC1155_TOKEN_ID args: AssetId[] } -type IntentERC1155TransfersCriterion = { - criterion: Criterion.INTENT_ERC1155_TRANSFERS +type ERC1155TransfersCriterion = { + criterion: Criterion.CHECK_ERC1155_TRANSFERS args: ERC1155AmountCondition[] } -type IntentSignMessageCriterion = { - criterion: Criterion.INTENT_SIGN_MESSAGE +type IntentMessageCriterion = { + criterion: Criterion.CHECK_INTENT_MESSAGE args: SignMessageCondition } -type IntentSignRawPayloadCriterion = { - criterion: Criterion.INTENT_SIGN_RAW_PAYLOAD +type IntentPayloadCriterion = { + criterion: Criterion.CHECK_INTENT_PAYLOAD args: string[] } -type IntentSignRawPayloadAlgorithmCriterion = { - criterion: Criterion.INTENT_SIGN_RAW_PAYLOAD_ALGORITHM +type IntentAlgorithmCriterion = { + criterion: Criterion.CHECK_INTENT_ALGORITHM args: Alg[] } -type IntentSignTypedDataDomainCriterion = { - criterion: Criterion.INTENT_SIGN_TYPED_DATA_DOMAIN +type IntentDomainCriterion = { + criterion: Criterion.CHECK_INTENT_DOMAIN args: SignTypedDataDomainCondition } -type IntentPermitDeadlineCriterion = { - criterion: Criterion.INTENT_PERMIT_DEADLINE +type PermitDeadlineCriterion = { + criterion: Criterion.CHECK_PERMIT_DEADLINE args: PermitDeadlineCondition } -type TransactionRequestGasFeesCriterion = { - criterion: Criterion.TRANSACTION_REQUEST_GAS_FEES +type GasFeeAmountCriterion = { + criterion: Criterion.CHECK_GAS_FEE_AMOUNT args: AmountCondition } -type TransactionRequestNonceRequiredCriterion = { - criterion: Criterion.TRANSACTION_REQUEST_NONCE_REQUIRED +type NonceRequiredCriterion = { + criterion: Criterion.CHECK_NONCE_EXISTS + args: null } -type TransactionRequestNonceNotRequiredCriterion = { - criterion: Criterion.TRANSACTION_REQUEST_NONCE_NOT_REQUIRED +type NonceNotRequiredCriterion = { + criterion: Criterion.CHECK_NONCE_NOT_EXISTS + args: null } -type ApprovalCriterion = { - criterion: Criterion.APPROVALS +type ApprovalsCriterion = { + criterion: Criterion.CHECK_APPROVALS args: ApprovalCondition[] } -type AccumulationCriterion = { - criterion: Criterion.SPENDING_LIMIT +type SpendingLimitCriterion = { + criterion: Criterion.CHECK_SPENDING_LIMIT args: SpendingLimitCondition } -type PolicyCriterion = +export type PolicyCriterion = | ActionCriterion + | TransferResourceIntegrityCriterion | PrincipalIdCriterion | PrincipalRoleCriterion | PrincipalGroupCriterion @@ -279,160 +288,32 @@ type PolicyCriterion = | WalletChainIdCriterion | WalletGroupCriterion | IntentTypeCriterion - | IntentDestinationIdCriterion - | IntentDestinationAddressCriterion - | IntentDestinationAccountTypeCriterion - | IntentDestinationClassificationCriterion + | DestinationIdCriterion + | DestinationAddressCriterion + | DestinationAccountTypeCriterion + | DestinationClassificationCriterion | IntentContractCriterion | IntentTokenCriterion | IntentSpenderCriterion | IntentChainIdCriterion | IntentHexSignatureCriterion | IntentAmountCriterion - | IntentERC721TokenIdCriterion - | IntentERC1155TokenIdCriterion - | IntentERC1155TransfersCriterion - | IntentSignMessageCriterion - | IntentSignRawPayloadCriterion - | IntentSignRawPayloadAlgorithmCriterion - | IntentSignTypedDataDomainCriterion - | IntentPermitDeadlineCriterion - | TransactionRequestGasFeesCriterion - | TransactionRequestNonceRequiredCriterion - | TransactionRequestNonceNotRequiredCriterion - | ApprovalCriterion - | AccumulationCriterion + | ERC721TokenIdCriterion + | ERC1155TokenIdCriterion + | ERC1155TransfersCriterion + | IntentMessageCriterion + | IntentPayloadCriterion + | IntentAlgorithmCriterion + | IntentDomainCriterion + | PermitDeadlineCriterion + | GasFeeAmountCriterion + | NonceRequiredCriterion + | NonceNotRequiredCriterion + | ApprovalsCriterion + | SpendingLimitCriterion export type PolicyCriterionBuilder = { name: string - type: Then + then: Then when: PolicyCriterion[] } - -export const regoCriterionMapping = { - [Criterion.ACTION]: 'checkAction', - [Criterion.PRINCIPAL_ID]: 'checkPrincipalId', - [Criterion.PRINCIPAL_ROLE]: 'checkPrincipalRole', - [Criterion.PRINCIPAL_GROUP]: 'checkPrincipalGroup', - [Criterion.WALLET_ID]: 'checkWalletId', - [Criterion.WALLET_ADDRESS]: 'checkWalletAddress', - [Criterion.WALLET_ACCOUNT_TYPE]: 'checkWalletAccountType', - [Criterion.WALLET_CHAIN_ID]: 'checkWalletChainId', - [Criterion.WALLET_GROUP]: 'checkWalletGroup', - [Criterion.INTENT_TYPE]: 'checkIntentType', - [Criterion.INTENT_DESTINATION_ID]: 'checkDestinationId', - [Criterion.INTENT_DESTINATION_ADDRESS]: 'checkIntentDestinationAddress', - [Criterion.INTENT_DESTINATION_ACCOUNT_TYPE]: 'checkIntentDestinationAccountType', - [Criterion.INTENT_DESTINATION_CLASSIFICATION]: 'checkDestinationClassification', - [Criterion.INTENT_CONTRACT]: 'checkIntentContract', - [Criterion.INTENT_TOKEN]: 'checkIntentToken', - [Criterion.INTENT_SPENDER]: 'checkIntentSpender', - [Criterion.INTENT_CHAIN_ID]: 'checkIntentChainId', - [Criterion.INTENT_HEX_SIGNATURE]: 'checkIntentHexSignature', - [Criterion.INTENT_AMOUNT]: 'checkIntentAmount', - [Criterion.INTENT_ERC721_TOKEN_ID]: 'checkERC721TokenId', - [Criterion.INTENT_ERC1155_TOKEN_ID]: 'checkERC1155TokenId', - [Criterion.INTENT_ERC1155_TRANSFERS]: 'checkERC1155Transfers', - [Criterion.INTENT_SIGN_MESSAGE]: 'checkIntentMessage', - [Criterion.INTENT_SIGN_RAW_PAYLOAD]: 'checkIntentPayload', - [Criterion.INTENT_SIGN_RAW_PAYLOAD_ALGORITHM]: 'checkIntentAlgorithm', - [Criterion.INTENT_SIGN_TYPED_DATA_DOMAIN]: 'checkIntentDomain', - [Criterion.INTENT_PERMIT_DEADLINE]: 'checkPermitDeadline', - [Criterion.TRANSACTION_REQUEST_GAS_FEES]: 'checkGasFeeAmount', - [Criterion.TRANSACTION_REQUEST_NONCE_REQUIRED]: 'checkNonceExists', - [Criterion.TRANSACTION_REQUEST_NONCE_NOT_REQUIRED]: 'checkNonceNotExists', - [Criterion.APPROVALS]: 'checkApprovals', - [Criterion.SPENDING_LIMIT]: 'checkSpendingLimit' -} - -// const examplePermitPolicy: PolicyCriterionBuilder = { -// type: Then.PERMIT, -// name: 'examplePermitPolicy', -// when: [ -// { -// criterion: Criterion.ACTION, -// args: [Action.SIGN_TRANSACTION] -// }, -// { -// criterion: Criterion.TRANSACTION_REQUEST_NONCE_REQUIRED -// }, -// { -// criterion: Criterion.PRINCIPAL_ID, -// args: ['matt@narval.xyz'] -// }, -// { -// criterion: Criterion.WALLET_ID, -// args: ['eip155:137/0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] -// }, -// { -// criterion: Criterion.INTENT_TYPE, -// args: [Intents.TRANSFER_NATIVE] -// }, -// { -// criterion: Criterion.INTENT_TOKEN, -// args: ['eip155:137/slip44:966'] -// }, -// { -// criterion: Criterion.INTENT_AMOUNT, -// args: { currency: '*', operator: 'lte', value: '1000000000000000000' } -// }, -// { -// criterion: Criterion.APPROVALS, -// args: [ -// { -// approvalCount: 2, -// countPrincipal: false, -// approvalEntityType: 'Narval::User', -// entityIds: ['aa@narval.xyz', 'bb@narval.xyz'] -// }, -// { -// approvalCount: 1, -// countPrincipal: false, -// approvalEntityType: 'Narval::UserRole', -// entityIds: ['admin'] -// } -// ] -// } -// ] -// } - -// const exampleForbidPolicy: PolicyCriterionBuilder = { -// type: Then.FORBID, -// name: 'exampleForbidPolicy', -// when: [ -// { -// criterion: Criterion.ACTION, -// args: [Action.SIGN_TRANSACTION] -// }, -// { -// criterion: Criterion.TRANSACTION_REQUEST_NONCE_REQUIRED -// }, -// { -// criterion: Criterion.PRINCIPAL_ID, -// args: ['matt@narval.xyz'] -// }, -// { -// criterion: Criterion.WALLET_ID, -// args: ['eip155:137/0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] -// }, -// { -// criterion: Criterion.INTENT_TYPE, -// args: [Intents.TRANSFER_NATIVE] -// }, -// { -// criterion: Criterion.INTENT_TOKEN, -// args: ['eip155:137/slip44:966'] -// }, -// { -// criterion: Criterion.SPENDING_LIMIT, -// args: { -// limit: '1000000000000000000', -// filters: { -// startDate: 12 * 60 * 60, -// tokens: ['eip155:137/slip44:966'], -// users: ['matt@narval.xyz'] -// } -// } -// } -// ] -// } diff --git a/package-lock.json b/package-lock.json index bb4484d0e..81d98c464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "class-validator": "^0.14.0", "clsx": "^1.2.1", "date-fns": "^3.3.1", + "handlebars": "^4.7.8", "lodash": "^4.17.21", "prism-react-renderer": "^2.3.1", "react": "18.2.0", @@ -16903,6 +16904,26 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -27836,6 +27857,18 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -29161,6 +29194,11 @@ "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 8149b74bc..ebc07341b 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "class-validator": "^0.14.0", "clsx": "^1.2.1", "date-fns": "^3.3.1", + "handlebars": "^4.7.8", "lodash": "^4.17.21", "prism-react-renderer": "^2.3.1", "react": "18.2.0", From aa9630cf56ee935159820d9e429a41880c927d97 Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 6 Feb 2024 14:34:40 +0100 Subject: [PATCH 2/4] fix --- apps/authz/src/opa/rego/policies/e2e.rego | 27 +++++++++++++++++++++++ apps/authz/src/opa/template/script.ts | 6 +---- 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 apps/authz/src/opa/rego/policies/e2e.rego diff --git a/apps/authz/src/opa/rego/policies/e2e.rego b/apps/authz/src/opa/rego/policies/e2e.rego new file mode 100644 index 000000000..7858e2f45 --- /dev/null +++ b/apps/authz/src/opa/rego/policies/e2e.rego @@ -0,0 +1,27 @@ +package main + +permit[{"policyId": "examplePermitPolicy" }] = reason { + checkTransferResourceIntegrity + checkNonceExists + checkAction({"signTransaction"}) + checkPrincipalId({"matt@narval.xyz"}) + checkWalletId({"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"}) + checkIntentType({"transferNative"}) + checkIntentToken({"eip155:137/slip44:966"}) + checkIntentAmount({"currency":"*","operator":"lte","value":"1000000000000000000"}) + approvals = checkApprovals([{"approvalCount":2,"countPrincipal":false,"approvalEntityType":"Narval::User","entityIds":["aa@narval.xyz","bb@narval.xyz"]}, {"approvalCount":1,"countPrincipal":false,"approvalEntityType":"Narval::UserRole","entityIds":["admin"]}]) + reason = {"type": "permit", "policyId": "examplePermitPolicy", "approvalsSatisfied": approvals.approvalsSatisfied, "approvalsMissing": approvals.approvalsMissing} +} + +forbid[{"policyId": "exampleForbidPolicy" }] = reason { + checkTransferResourceIntegrity + checkNonceExists + checkAction({"signTransaction"}) + checkPrincipalId({"matt@narval.xyz"}) + checkWalletId({"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"}) + checkIntentType({"transferNative"}) + checkIntentToken({"eip155:137/slip44:966"}) + checkSpendingLimit({"limit":"1000000000000000000","timeWindow":{"type":"rolling","value":43200},"filters":{"tokens":["eip155:137/slip44:966"],"users":["matt@narval.xyz"]}}) + reason = {"type":"forbid","policyId":"exampleForbidPolicy","approvalsSatisfied":[],"approvalsMissing":[]} +} + diff --git a/apps/authz/src/opa/template/script.ts b/apps/authz/src/opa/template/script.ts index c6f50eefc..576940e3f 100644 --- a/apps/authz/src/opa/template/script.ts +++ b/apps/authz/src/opa/template/script.ts @@ -64,10 +64,6 @@ const template = Handlebars.compile(templateSource) const regoContent = template(policies) // Write the content to a Rego file -writeFileSync( - '/Users/samuel/Documents/narval/narval/apps/authz/src/opa/rego/policies/generatedPolicies.rego', - regoContent, - 'utf-8' -) +writeFileSync('/Users/samuel/Documents/narval/narval/apps/authz/src/opa/rego/policies/e2e.rego', regoContent, 'utf-8') console.log('Policy .rego file generated successfully.') From 4fa73d032ac6f871ca08eba5c1b82d478a68c8c2 Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 6 Feb 2024 14:45:11 +0100 Subject: [PATCH 3/4] fix --- .../opa/rego/__test__/policies/e2e_test.rego | 28 ++++++++++++------- apps/authz/src/opa/rego/policies/e2e.rego | 13 ++++----- apps/authz/src/opa/template/template.hbs | 14 +++++----- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego b/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego index 3c73125b1..28d2e315b 100644 --- a/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego +++ b/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego @@ -199,27 +199,35 @@ e2e_entities = { } test_mattCanTransferLessThanOneMaticWithTwoApprovals { - res = permit[{"policyId": "test-permit-policy-1"}] with input as e2e_req with data.entities as e2e_entities + res = permit[{"policyId": "examplePermitPolicy"}] with input as e2e_req with data.entities as e2e_entities res == { "type": "permit", - "policyId": "test-permit-policy-1", + "policyId": "examplePermitPolicy", "approvalsMissing": [], - "approvalsSatisfied": [{ - "approvalCount": 2, - "approvalEntityType": "Narval::User", - "countPrincipal": false, - "entityIds": ["aa@narval.xyz", "bb@narval.xyz"], - }], + "approvalsSatisfied": [ + { + "approvalCount": 2, + "approvalEntityType": "Narval::User", + "countPrincipal": false, + "entityIds": ["aa@narval.xyz", "bb@narval.xyz"], + }, + { + "approvalCount": 1, + "countPrincipal": false, + "approvalEntityType": "Narval::UserRole", + "entityIds": ["admin"], + }, + ], } } test_mattCantTransferMoreThanOneMaticOnTwelveHoursRollingBasis { - res = forbid[{"policyId": "test-forbid-policy-1"}] with input as e2e_req with data.entities as e2e_entities + res = forbid[{"policyId": "exampleForbidPolicy"}] with input as e2e_req with data.entities as e2e_entities res == { "type": "forbid", - "policyId": "test-forbid-policy-1", + "policyId": "exampleForbidPolicy", "approvalsSatisfied": [], "approvalsMissing": [], } diff --git a/apps/authz/src/opa/rego/policies/e2e.rego b/apps/authz/src/opa/rego/policies/e2e.rego index 7858e2f45..55859f7e2 100644 --- a/apps/authz/src/opa/rego/policies/e2e.rego +++ b/apps/authz/src/opa/rego/policies/e2e.rego @@ -1,6 +1,6 @@ package main -permit[{"policyId": "examplePermitPolicy" }] = reason { +permit[{"policyId": "examplePermitPolicy"}] = reason { checkTransferResourceIntegrity checkNonceExists checkAction({"signTransaction"}) @@ -8,12 +8,12 @@ permit[{"policyId": "examplePermitPolicy" }] = reason { checkWalletId({"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"}) checkIntentType({"transferNative"}) checkIntentToken({"eip155:137/slip44:966"}) - checkIntentAmount({"currency":"*","operator":"lte","value":"1000000000000000000"}) - approvals = checkApprovals([{"approvalCount":2,"countPrincipal":false,"approvalEntityType":"Narval::User","entityIds":["aa@narval.xyz","bb@narval.xyz"]}, {"approvalCount":1,"countPrincipal":false,"approvalEntityType":"Narval::UserRole","entityIds":["admin"]}]) + checkIntentAmount({"currency": "*", "operator": "lte", "value": "1000000000000000000"}) + approvals = checkApprovals([{"approvalCount": 2, "countPrincipal": false, "approvalEntityType": "Narval::User", "entityIds": ["aa@narval.xyz", "bb@narval.xyz"]}, {"approvalCount": 1, "countPrincipal": false, "approvalEntityType": "Narval::UserRole", "entityIds": ["admin"]}]) reason = {"type": "permit", "policyId": "examplePermitPolicy", "approvalsSatisfied": approvals.approvalsSatisfied, "approvalsMissing": approvals.approvalsMissing} } -forbid[{"policyId": "exampleForbidPolicy" }] = reason { +forbid[{"policyId": "exampleForbidPolicy"}] = reason { checkTransferResourceIntegrity checkNonceExists checkAction({"signTransaction"}) @@ -21,7 +21,6 @@ forbid[{"policyId": "exampleForbidPolicy" }] = reason { checkWalletId({"eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b"}) checkIntentType({"transferNative"}) checkIntentToken({"eip155:137/slip44:966"}) - checkSpendingLimit({"limit":"1000000000000000000","timeWindow":{"type":"rolling","value":43200},"filters":{"tokens":["eip155:137/slip44:966"],"users":["matt@narval.xyz"]}}) - reason = {"type":"forbid","policyId":"exampleForbidPolicy","approvalsSatisfied":[],"approvalsMissing":[]} + checkSpendingLimit({"limit": "1000000000000000000", "timeWindow": {"type": "rolling", "value": 43200}, "filters": {"tokens": ["eip155:137/slip44:966"], "users": ["matt@narval.xyz"]}}) + reason = {"type": "forbid", "policyId": "exampleForbidPolicy", "approvalsSatisfied": [], "approvalsMissing": []} } - diff --git a/apps/authz/src/opa/template/template.hbs b/apps/authz/src/opa/template/template.hbs index bca14bd9b..055c1a2a9 100644 --- a/apps/authz/src/opa/template/template.hbs +++ b/apps/authz/src/opa/template/template.hbs @@ -1,11 +1,11 @@ package main {{#each policies}} -{{ then }}[{"policyId": "{{ name }}" }] = reason { - {{#each when}} - {{#criterion this}}{{/criterion}} - {{/each}} - {{#reason this}}{{/reason}} -} + {{then}}[{"policyId": "{{name}}" }] = reason { + {{#each when}} + {{#criterion this}}{{/criterion}} + {{/each}} + {{#reason this}}{{/reason}} + } -{{/each}} +{{/each}} \ No newline at end of file From 919591d274c1c30a89b32f7943c1c419dbb49ef6 Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 6 Feb 2024 14:47:35 +0100 Subject: [PATCH 4/4] fix --- apps/authz/src/opa/template/script.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/authz/src/opa/template/script.ts b/apps/authz/src/opa/template/script.ts index 576940e3f..3ee5b953c 100644 --- a/apps/authz/src/opa/template/script.ts +++ b/apps/authz/src/opa/template/script.ts @@ -4,7 +4,7 @@ import Handlebars from 'handlebars' import { isEmpty } from 'lodash' import { policies } from './mockData' -Handlebars.registerHelper('criterion', function (item, options) { +Handlebars.registerHelper('criterion', function (item) { const criterion: Criterion = item.criterion const args = item.args @@ -29,7 +29,7 @@ Handlebars.registerHelper('criterion', function (item, options) { } }) -Handlebars.registerHelper('reason', function (item, options) { +Handlebars.registerHelper('reason', function (item) { if (item.then === Then.PERMIT) { const reason = [ `"type": "${item.then}"`,