Skip to content

Commit

Permalink
HAWNG-902: Fixes ACL rules not being translated back to client
Browse files Browse the repository at this point in the history
* rbac.ts
 * Fixes the response format of a bulk request to correct deduplication
   and structure the json by context mbean name

* Adds tracing to help with debugging

* rbac.test.ts
 * Adds tests proving the response of a bulk request

* gateway-test-inputs.ts
 * Fixes expected responses of bulk request tests
  • Loading branch information
phantomjinx committed Nov 28, 2024
1 parent 4d6c29a commit 6a1fc1a
Show file tree
Hide file tree
Showing 5 changed files with 11,794 additions and 41 deletions.
85 changes: 45 additions & 40 deletions docker/gateway/src/gateway-test-inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,47 +119,52 @@ export const testData = {
],
},
value: {
'gc()': {
CanInvoke: true,
Method: 'gc()',
ObjectName: 'java.lang:type=Memory',
},
'addOrUpdateRoutesFromXml(java.lang.String)': {
CanInvoke: true,
Method: 'addOrUpdateRoutesFromXml(java.lang.String)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'addOrUpdateRoutesFromXml(java.lang.String,boolean)': {
CanInvoke: true,
Method: 'addOrUpdateRoutesFromXml(java.lang.String,boolean)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'dumpStatsAsXml(boolean)': {
CanInvoke: true,
Method: 'dumpStatsAsXml(boolean)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'getCamelId()': {
CanInvoke: true,
Method: 'getCamelId()',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'getRedeliveries()': {
CanInvoke: true,
Method: 'getRedeliveries()',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'sendStringBody(java.lang.String,java.lang.String)': {
CanInvoke: true,
Method: 'sendStringBody(java.lang.String,java.lang.String)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
'java.lang:type=Memory': {
'gc()': {
CanInvoke: true,
Method: 'gc()',
ObjectName: 'java.lang:type=Memory',
},
},
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context':
{
'addOrUpdateRoutesFromXml(java.lang.String)': {
CanInvoke: true,
Method: 'addOrUpdateRoutesFromXml(java.lang.String)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'addOrUpdateRoutesFromXml(java.lang.String,boolean)': {
CanInvoke: true,
Method: 'addOrUpdateRoutesFromXml(java.lang.String,boolean)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'dumpStatsAsXml(boolean)': {
CanInvoke: true,
Method: 'dumpStatsAsXml(boolean)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'getCamelId()': {
CanInvoke: true,
Method: 'getCamelId()',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'getRedeliveries()': {
CanInvoke: true,
Method: 'getRedeliveries()',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
'sendStringBody(java.lang.String,java.lang.String)': {
CanInvoke: true,
Method: 'sendStringBody(java.lang.String,java.lang.String)',
ObjectName:
'org.apache.camel:context=io.fabric8.quickstarts.karaf-camel-log-log-example-context,name="log-example-context",type=context',
},
},
},
timestamp: 1718286845551,
},
Expand Down
4 changes: 4 additions & 0 deletions docker/gateway/src/jolokia-agent/jolokia-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import * as RBAC from './rbac'

const aclFile = fs.readFileSync(process.env['HAWTIO_ONLINE_RBAC_ACL'] || `${__dirname}/ACL.yaml`, 'utf8')
const aclYaml = yaml.parse(aclFile)

logger.trace('=== imported ACL yaml ===')
logger.trace(aclYaml)

RBAC.initACL(aclYaml)

let isRbacEnabled = typeof process.env['HAWTIO_ONLINE_RBAC_ACL'] !== 'undefined'
Expand Down
43 changes: 43 additions & 0 deletions docker/gateway/src/jolokia-agent/rbac.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import * as yaml from 'yaml'
import * as fs from 'fs'
import * as rbac from './rbac'
import {
BulkValue,
MBeanInfoCache,
OptimisedJmxDomains,
OptimisedMBeanOperations,
hasMBeanOperation,
isOptimisedCachedDomains,
} from './globals'
import { ExecRequest } from 'jolokia.js'

const aclFile = fs.readFileSync(process.env['HAWTIO_ONLINE_RBAC_ACL'] || `${__dirname}/ACL.yaml`, 'utf8')
const aclYaml = yaml.parse(aclFile)
Expand Down Expand Up @@ -288,3 +290,44 @@ describe('parseProperties', function () {
})
})
})

describe('bulk-intercept-responses', () => {
it('should handle bulk intercepts correctly', () => {
const ctx1 = 'org.apache.camel:context=MyCamel,type=context,name="MyCamel"'
const ctx2 = 'org.apache.camel:context=MyCamel,type=consumers,name=TimerConsumer(0x6a04d2a4)'

const arg: Record<string, string[]> = {}
arg[ctx1] = ['stop()', 'getGlobalOptions()', 'reset()', 'reset(boolean)']
arg[ctx2] = ['getState()', 'stop()', 'getInflightExchanges()', 'getServiceType()', 'getRunLoggingLevel()']

const request: ExecRequest = {
type: 'exec',
mbean: 'hawtio:type=security,area=jmx,name=HawtioOnlineRBAC',
operation: 'canInvoke(java.util.Map)',
arguments: [arg],
}

const mbeansFile = fs.readFileSync(`${__dirname}/test.domainMBeans.json`, 'utf8')
const mbeans = JSON.parse(mbeansFile)

const intercepted = rbac.intercept(request, admin, mbeans)

expect(intercepted.response?.value).toBeDefined()
const value = intercepted.response?.value as Record<string, Record<string, BulkValue>>
expect(Object.getOwnPropertyNames(value)).toHaveLength(2)

expect(value[ctx1]).toBeDefined()
expect(Object.getOwnPropertyNames(value[ctx1])).toHaveLength(arg[ctx1].length)
let stopOp = value[ctx1]['stop()']
expect(stopOp).toBeDefined()
expect(stopOp.CanInvoke).toBeTruthy()
expect(stopOp.ObjectName).toBe(ctx1)

expect(value[ctx2]).toBeDefined()
expect(Object.getOwnPropertyNames(value[ctx2])).toHaveLength(arg[ctx2].length)
stopOp = value[ctx2]['stop()']
expect(stopOp).toBeDefined()
expect(stopOp.CanInvoke).toBeTruthy()
expect(stopOp.ObjectName).toBe(ctx2)
})
})
24 changes: 23 additions & 1 deletion docker/gateway/src/jolokia-agent/rbac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ function isExecRBACRegistryList(request: MBeanRequest) {

// ===== intercept =========================================
export function intercept(request: MBeanRequest, role: string, mbeans: JmxDomains): Intercepted {
logger.trace(`Calling intercept on mbean request`)
logger.trace(`Intercept role: ${role}`)
logger.trace(`Intercept request:`)
logger.trace(request)
logger.trace(`Intercept mbeans:`)
logger.trace(mbeans)

const intercepted = (value: unknown) => ({
intercepted: true,
request: request,
Expand All @@ -112,6 +119,8 @@ export function intercept(request: MBeanRequest, role: string, mbeans: JmxDomain

// Intercept client-side RBAC canInvoke(java.lang.String) request
if (isCanInvokeRequest(request) && isArgumentExecRequest(request)) {
logger.trace('Intercept: canInvokeRequest')

const args: unknown[] = request.arguments || []
if (args.length > 0) {
const mbean = args[0] as string
Expand Down Expand Up @@ -169,6 +178,9 @@ export function intercept(request: MBeanRequest, role: string, mbeans: JmxDomain

// Intercept client-side RBAC canInvoke(java.util.Map) request
if (isBulkCanInvokeRequest(request) && isArgumentExecRequest(request)) {
logger.trace(
`Intercept: processing a bulk request ${request.mbean} ${!request.arguments ? '<No arguments>' : request.arguments[0]}`,
)
const args: unknown[] = request.arguments || []
if (args.length > 0 && isRecord(args[0])) {
const argEntries = Object.entries(args[0])
Expand All @@ -178,20 +190,29 @@ export function intercept(request: MBeanRequest, role: string, mbeans: JmxDomain
const mbean = argEntry[0]
const operations = toStringArray(argEntry[1])

const opValues: Record<string, unknown> = {}
operations.forEach(operation => {
logger.trace(`Intercept: testing operation ${operation} => canInvoke: ${canInvoke(mbean, operation, role)}`)

const bulkValue: BulkValue = {
CanInvoke: canInvoke(mbean, operation, role),
Method: operation,
ObjectName: mbean,
}
value[operation] = bulkValue
opValues[operation] = bulkValue
})

value[mbean] = opValues
})

logger.trace('Intercept: bulk result')
logger.trace(intercepted(value))
return intercepted(value)
}
}

if (rbacRegistryEnabled) {
logger.trace(`Intercept: RBAC registry enabled: ${rbacRegistryEnabled}`)
// Intercept client-side RBACRegistry discovery request
if (isListRBACRegistry(request)) {
return intercepted({
Expand All @@ -209,6 +230,7 @@ export function intercept(request: MBeanRequest, role: string, mbeans: JmxDomain

// Intercept client-side optimised list MBeans request
if (isExecRBACRegistryList(request)) {
logger.trace(`Intercept: registry list request`)
return intercepted(optimisedMBeans(mbeans, role))
}
}
Expand Down
Loading

0 comments on commit 6a1fc1a

Please sign in to comment.