Skip to content

Commit

Permalink
Add rego templating using handlebars
Browse files Browse the repository at this point in the history
  • Loading branch information
samuel committed Feb 6, 2024
1 parent aa12b51 commit 5e7b232
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 304 deletions.
9 changes: 9 additions & 0 deletions apps/authz/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 12 additions & 2 deletions apps/authz/src/opa/rego/lib/criteria/resource.rego
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -31,3 +36,8 @@ checkWalletGroup(values) {
group = walletGroups[_]
group in values
}

extractAddressFromCaip10(caip10) = result {
arr = split(caip10, ":")
result = arr[count(arr) - 1]
}
69 changes: 0 additions & 69 deletions apps/authz/src/opa/rego/policies/e2e.rego

This file was deleted.

112 changes: 112 additions & 0 deletions apps/authz/src/opa/template/mockData.ts
Original file line number Diff line number Diff line change
@@ -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: ['[email protected]']
},
{
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: ['[email protected]', '[email protected]']
},
{
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: ['[email protected]']
},
{
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: ['[email protected]']
}
}
}
]
}

export const policies = {
policies: [examplePermitPolicy, exampleForbidPolicy]
}
73 changes: 73 additions & 0 deletions apps/authz/src/opa/template/script.ts
Original file line number Diff line number Diff line change
@@ -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.')
11 changes: 11 additions & 0 deletions apps/authz/src/opa/template/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

{{#each policies}}
{{ then }}[{"policyId": "{{ name }}" }] = reason {
{{#each when}}
{{#criterion this}}{{/criterion}}
{{/each}}
{{#reason this}}{{/reason}}
}

{{/each}}
Loading

0 comments on commit 5e7b232

Please sign in to comment.