-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Authorization gateway - basic features (#138)
implemented full flow with basic features Implement local policy attachment caching for all resource repositories
- Loading branch information
Showing
22 changed files
with
848 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// @ts-ignore opa-wasm already has TS typings merged, but not yet published on npm | ||
import * as Rego from '@open-policy-agent/opa-wasm'; | ||
import {getCompiledFilename} from '../../opaHelper'; | ||
import {PolicyExecutionContext, PolicyExecutionResult, QueriesResults, JwtInput} from './types'; | ||
import {PolicyArgsObject} from '../../resource-repository'; | ||
|
||
export async function evaluate(ctx: PolicyExecutionContext): Promise<PolicyExecutionResult> { | ||
const policy = await getWasmPolicy(ctx); | ||
const input = getInput(ctx); | ||
|
||
const result = policy.evaluate(input)?.[0]?.result; | ||
|
||
return {done: true, allow: result?.allow}; | ||
} | ||
|
||
async function getWasmPolicy(ctx: PolicyExecutionContext): Promise<any> { | ||
const filename = getCompiledFilename({namespace: ctx.namespace, name: ctx.name}); | ||
const wasm = ctx.repo.getPolicyAttachment(filename); | ||
|
||
const rego = new Rego(); | ||
return rego.load_policy(wasm); | ||
} | ||
|
||
function getInput(ctx: PolicyExecutionContext): PolicyInput { | ||
const input: PolicyInput = {}; | ||
|
||
if (ctx.jwt) input.jwt = ctx.jwt; | ||
if (ctx.args) input.args = ctx.args; | ||
if (ctx.queries) input.queries = ctx.queries; | ||
|
||
return input; | ||
} | ||
|
||
type PolicyInput = { | ||
jwt?: JwtInput; | ||
args?: PolicyArgsObject; | ||
queries?: QueriesResults; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import {GraphQLResolveInfo} from 'graphql'; | ||
import {RequestContext} from '../../context'; | ||
import {Policy, GraphQLArguments} from './types'; | ||
import {ResourceRepository, Policy as PolicyDefinition, PolicyArgsObject} from '../../resource-repository'; | ||
import {evaluate as evaluateOpa} from './opa'; | ||
import {injectParameters} from '../../paramInjection'; | ||
|
||
const typeEvaluators = { | ||
opa: evaluateOpa, | ||
}; | ||
|
||
export class PolicyExecutor { | ||
static repo: ResourceRepository; | ||
private policyDefinitions: PolicyDefinition[]; | ||
|
||
constructor( | ||
protected policies: Policy[], | ||
protected parent: any, | ||
protected args: GraphQLArguments, | ||
protected context: RequestContext, | ||
protected info: GraphQLResolveInfo | ||
) { | ||
// TODO: add jwt data | ||
this.policyDefinitions = PolicyExecutor.repo.getResourceGroup().policies; | ||
} | ||
|
||
async validatePolicies() { | ||
await Promise.all(this.policies.map(r => this.validatePolicy(r))); | ||
} | ||
|
||
async validatePolicy(policy: Policy) { | ||
const policyDefinition = this.getPolicyDefinition(policy.namespace, policy.name); | ||
|
||
const args = policyDefinition.args && this.preparePolicyArgs(policyDefinition.args, policy); | ||
// TODO: evaluate queries | ||
|
||
const evaluate = typeEvaluators[policyDefinition.type]; | ||
if (!evaluate) throw new Error(`Unsupported policy type ${policyDefinition.type}`); | ||
|
||
const {done, allow} = await evaluate({...policy, args, repo: PolicyExecutor.repo}); | ||
if (!done) throw new Error('in-line query evaluation not yet supported'); | ||
|
||
if (!allow) throw new Error(`Unauthorized by policy ${policy.name} in namespace ${policy.namespace}`); | ||
} | ||
|
||
private preparePolicyArgs(supportedPolicyArgs: PolicyArgsObject, policy: Policy): PolicyArgsObject { | ||
return Object.keys(supportedPolicyArgs).reduce<PolicyArgsObject>((policyArgs, policyArgName) => { | ||
if (policy?.args?.[policyArgName] === undefined) | ||
throw new Error( | ||
`Missing arg ${policyArgName} for policy ${policy.name} in namespace ${policy.namespace}` | ||
); | ||
|
||
let policyArgValue = policy.args[policyArgName]; | ||
if (typeof policyArgValue === 'string') { | ||
policyArgValue = injectParameters(policyArgValue, this.parent, this.args, this.context, this.info) | ||
.value; | ||
} | ||
|
||
policyArgs[policyArgName] = policyArgValue; | ||
return policyArgs; | ||
}, {}); | ||
} | ||
|
||
private getPolicyDefinition(namespace: string, name: string) { | ||
const policyDefinition = this.policyDefinitions.find(({metadata}) => { | ||
return metadata.namespace === namespace && metadata.name === name; | ||
}); | ||
|
||
if (!policyDefinition) throw new Error(`The policy ${name} in namespace ${namespace} was not found`); | ||
return policyDefinition; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import {GraphQLResolveInfo} from 'graphql'; | ||
import {RequestContext} from '../../context'; | ||
import {SchemaDirectiveVisitor} from 'graphql-tools'; | ||
import {GraphQLField, defaultFieldResolver} from 'graphql'; | ||
import {gql} from 'apollo-server-core'; | ||
import {PolicyExecutor} from './policy-executor'; | ||
|
||
export class PolicyDirective extends SchemaDirectiveVisitor { | ||
visitFieldDefinition(field: GraphQLField<any, any>) { | ||
const originalResolve = field.resolve || defaultFieldResolver; | ||
const policies = this.args.policies; | ||
|
||
field.resolve = async (parent: any, args: any, context: RequestContext, info: GraphQLResolveInfo) => { | ||
const executor = new PolicyExecutor(policies, parent, args, context, info); | ||
await executor.validatePolicies(); | ||
|
||
return originalResolve.call(field, parent, args, context, info); | ||
}; | ||
} | ||
} | ||
|
||
export const sdl = gql` | ||
input PolicyDirectivePolicy { | ||
namespace: String! | ||
name: String! | ||
args: JSONObject | ||
} | ||
directive @policy(policies: [PolicyDirectivePolicy!]!) on FIELD_DEFINITION | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {PolicyArgsObject, ResourceRepository} from '../../resource-repository/types'; | ||
|
||
export type Policy = { | ||
namespace: string; | ||
name: string; | ||
args?: PolicyArgsObject; | ||
}; | ||
|
||
// args here contain the final values after param injection | ||
export type PolicyExecutionContext = { | ||
namespace: string; | ||
name: string; | ||
repo: ResourceRepository; | ||
jwt?: JwtInput; | ||
args?: PolicyArgsObject; | ||
queries?: QueriesResults; | ||
}; | ||
|
||
export type QueriesResults = { | ||
[name: string]: string; | ||
}; | ||
|
||
export type JwtInput = { | ||
[name: string]: string; | ||
}; | ||
|
||
export type PolicyExecutionResult = { | ||
done: boolean; | ||
allow?: boolean; | ||
query?: { | ||
type: string; | ||
code: string; | ||
}; | ||
}; | ||
|
||
export type GraphQLArguments = { | ||
[name: string]: any; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.