Skip to content

Commit 6b6d43f

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
feat(oci): support region-first endpoint policies
1 parent 2864a4d commit 6b6d43f

3 files changed

Lines changed: 133 additions & 3 deletions

File tree

apps/sim/lib/internal/oci/client.server.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,57 @@ describe('credential-bound OCI client', () => {
773773
expect(mocks.secureFetch).toHaveBeenCalledOnce()
774774
})
775775

776+
it('invokes a Functions endpoint discovered through the same client and management policy', async () => {
777+
const managementPolicy = createOciStaticEndpointPolicy({
778+
serviceId: OCI_SERVICE_ID,
779+
serviceName: 'functions',
780+
hostnameTemplate: 'regional-oci',
781+
})
782+
const invocationPolicy = createOciDiscoveredEndpointPolicy({
783+
serviceId: OCI_SERVICE_ID,
784+
serviceName: 'functions',
785+
hostnameTemplate: 'region-first-oci',
786+
responsePolicy: managementPolicy,
787+
source: { kind: 'json', path: ['invokeEndpoint'] },
788+
})
789+
const invokeOrigin = 'https://fixture.us-ashburn-1.functions.oci.oraclecloud.com'
790+
mocks.secureFetch
791+
.mockResolvedValueOnce(
792+
secureResponse({ body: JSON.stringify({ invokeEndpoint: invokeOrigin }) })
793+
)
794+
.mockResolvedValueOnce(secureResponse({ body: 'invoked' }))
795+
const { client } = await createPreparedClient()
796+
const managementEndpoint = await client.prepareStaticEndpoint(managementPolicy)
797+
const response = await client.request({
798+
endpoint: managementEndpoint,
799+
method: 'GET',
800+
encodedPath: '/20181201/functions/synthetic-function',
801+
timeoutMs: 10_000,
802+
maxResponseBytes: 1024,
803+
})
804+
const invocationEndpoint = await client.prepareDiscoveredEndpoint(invocationPolicy, response)
805+
const body = new TextEncoder().encode('{"message":"hello"}')
806+
const result = await client.request({
807+
endpoint: invocationEndpoint,
808+
method: 'POST',
809+
encodedPath: '/20181201/functions/synthetic-function/actions/invoke',
810+
body,
811+
contentType: 'application/json',
812+
timeoutMs: 10_000,
813+
maxResponseBytes: 1024,
814+
})
815+
expect(mocks.secureFetch).toHaveBeenCalledTimes(2)
816+
expect(mocks.secureFetch.mock.calls[0][0]).toBe(
817+
'https://functions.us-ashburn-1.oci.oraclecloud.com/20181201/functions/synthetic-function'
818+
)
819+
expect(mocks.secureFetch.mock.calls[1][0]).toBe(
820+
`${invokeOrigin}/20181201/functions/synthetic-function/actions/invoke`
821+
)
822+
expect(mocks.secureFetch.mock.calls[1][2]).toMatchObject({ method: 'POST', body })
823+
expect(authorizationFromLastRequest()).toMatch(/^Signature version="1"/)
824+
expect(new TextDecoder().decode(result.body)).toBe('invoked')
825+
})
826+
776827
it('rejects fabricated and cross-client authenticated discovery responses', async () => {
777828
const policy = createOciDiscoveredEndpointPolicy({
778829
serviceId: OCI_SERVICE_ID,

apps/sim/lib/internal/oci/endpoints.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ describe('OCI region registry', () => {
4141
expect(regionalOciHostname('objectstorage', region, 'regional')).toBe(
4242
`objectstorage.${id}.${region.realm.domain}`
4343
)
44+
expect(regionalOciHostname('functions', region, 'region-first-oci')).toBe(
45+
`${id}.functions.oci.${region.realm.domain}`
46+
)
4447
}
4548
})
4649

@@ -60,6 +63,79 @@ describe('OCI region registry', () => {
6063

6164
describe('OCI endpoint policies', () => {
6265
const region = getOciRegion('us-ashburn-1')
66+
const functionsManagementPolicy = createOciStaticEndpointPolicy({
67+
serviceId: OCI_SERVICE_ID,
68+
serviceName: 'functions',
69+
hostnameTemplate: 'regional-oci',
70+
})
71+
const functionsInvocationPolicy = createOciDiscoveredEndpointPolicy({
72+
serviceId: OCI_SERVICE_ID,
73+
serviceName: 'functions',
74+
hostnameTemplate: 'region-first-oci',
75+
responsePolicy: functionsManagementPolicy,
76+
source: { kind: 'json', path: ['invokeEndpoint'] },
77+
})
78+
79+
it.each(['us-ashburn-1', 'us-gov-ashburn-1'])(
80+
'supports region-first policies and authenticated Functions discovery in %s',
81+
(regionId) => {
82+
const selectedRegion = getOciRegion(regionId)
83+
const hostname = `${regionId}.functions.oci.${selectedRegion.realm.domain}`
84+
expect(
85+
resolveStaticOciEndpoint(
86+
createOciStaticEndpointPolicy({
87+
serviceId: OCI_SERVICE_ID,
88+
serviceName: 'functions',
89+
hostnameTemplate: 'region-first-oci',
90+
}),
91+
selectedRegion
92+
).origin
93+
).toBe(`https://${hostname}`)
94+
expect(
95+
resolveDiscoveredOciEndpoint(
96+
functionsInvocationPolicy,
97+
selectedRegion,
98+
`https://resource.${hostname}`
99+
)
100+
).toMatchObject({
101+
origin: `https://resource.${hostname}`,
102+
serviceId: OCI_SERVICE_ID,
103+
provenance: 'authenticated-discovery',
104+
})
105+
expect(Object.isFrozen(functionsInvocationPolicy)).toBe(true)
106+
expect(functionsInvocationPolicy.hostnameTemplate).toBe('region-first-oci')
107+
expect(functionsInvocationPolicy.allowRegionalHost).toBe(false)
108+
expect(
109+
resolveDiscoveredOciEndpoint(
110+
createOciDiscoveredEndpointPolicy({
111+
...functionsInvocationPolicy,
112+
allowRegionalHost: true,
113+
}),
114+
selectedRegion,
115+
`https://${hostname}`
116+
).hostname
117+
).toBe(hostname)
118+
}
119+
)
120+
121+
it.each([
122+
'https://resource.functions.us-ashburn-1.oci.oraclecloud.com',
123+
'https://resource.eu-frankfurt-1.functions.oci.oraclecloud.com',
124+
'https://resource.us-ashburn-1.functions.oci.oraclegovcloud.com',
125+
'https://resource.us-ashburn-1.database.oci.oraclecloud.com',
126+
'https://resource.us-ashburn-1.functions.oci.oraclecloud.com.attacker.example',
127+
'https://resourceus-ashburn-1.functions.oci.oraclecloud.com',
128+
'https://us-ashburn-1.functions.oci.oraclecloud.com',
129+
'http://resource.us-ashburn-1.functions.oci.oraclecloud.com',
130+
'https://resource.us-ashburn-1.functions.oci.oraclecloud.com:8443',
131+
'https://user:password@resource.us-ashburn-1.functions.oci.oraclecloud.com',
132+
'https://resource.us-ashburn-1.functions.oci.oraclecloud.com/path',
133+
'https://resource.us-ashburn-1.functions.oci.oraclecloud.com?query=1',
134+
'https://resource.us-ashburn-1.functions.oci.oraclecloud.com#fragment',
135+
'https://127.0.0.1',
136+
])('rejects invalid Functions invocation origins: %s', (origin) => {
137+
expect(() => resolveDiscoveredOciEndpoint(functionsInvocationPolicy, region, origin)).toThrow()
138+
})
63139

64140
it('freezes declarative policies and derives exact static origins', () => {
65141
expect(Object.isFrozen(staticPolicy)).toBe(true)

apps/sim/lib/internal/oci/endpoints.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isIpLiteral, unwrapIpv6Brackets } from '@sim/security/ssrf'
22
import type { OAuthService } from '@/lib/oauth/types'
33

44
export type OciDestinationProvenance = 'static' | 'authenticated-discovery'
5-
export type OciHostnameTemplate = 'regional' | 'regional-oci'
5+
export type OciHostnameTemplate = 'regional' | 'regional-oci' | 'region-first-oci'
66

77
export interface OciRealm {
88
readonly id: string
@@ -216,7 +216,7 @@ function assertServiceName(value: string): void {
216216
}
217217

218218
function assertHostnameTemplate(value: OciHostnameTemplate): void {
219-
if (value !== 'regional' && value !== 'regional-oci') {
219+
if (value !== 'regional' && value !== 'regional-oci' && value !== 'region-first-oci') {
220220
throw new Error('OCI endpoint policy hostname template is invalid')
221221
}
222222
}
@@ -295,7 +295,10 @@ export function regionalOciHostname(
295295
assertServiceName(serviceName)
296296
assertHostnameTemplate(hostnameTemplate)
297297
const ociLabel = hostnameTemplate === 'regional-oci' ? '.oci' : ''
298-
const hostname = `${serviceName}.${region.id}${ociLabel}.${region.realm.domain}`
298+
const hostname =
299+
hostnameTemplate === 'region-first-oci'
300+
? `${region.id}.${serviceName}.oci.${region.realm.domain}`
301+
: `${serviceName}.${region.id}${ociLabel}.${region.realm.domain}`
299302
if (hostname.length > MAX_HOSTNAME_LENGTH) {
300303
throw new Error('OCI endpoint policy hostname is invalid')
301304
}

0 commit comments

Comments
 (0)