diff --git a/README.md b/README.md index bb1ec8250993bc54c6958e3a746b0b59f64a415d..f4c9330896c0bc3abac01244617f68fd73e1f678 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,8 @@ flowchart LR ### Policy Development +* [Policy Extensions Functions](./doc/policy_development.md) + Policies are written in the [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) language. Please refer to the [OPA documentation](https://www.openpolicyagent.org/docs/latest/) for detailed overview of Rego and OPA capabilities. @@ -120,12 +122,13 @@ for detailed overview of Rego and OPA capabilities. 1. The filename of the policy *must* follow rules for the naming and directory structure: the `group`, `policy name` and `version` are directories inside the Git repo and policy file *must* be named `policy.rego`. For example: `/gaiax/example/1.0/policy.rego`. -2. In the same directory there could be a data file containing static json, which is later passed to the -policy evaluation runtime. The file *must* be named `data.json`. Example: `/gaiax/example/1.0/data.json` +2. In the same directory there could be a data file containing static JSON, which is automatically +available for use during policy evaluation by using the `data` variable. The file *must* be named `data.json`. +Example: `/gaiax/example/1.0/data.json` 3. The policy package name inside the policy source code file *must* exactly match the `group` and `policy` (name) of the policy. -*What does all this mean?* +*What does it mean?* Let's see an example for the 1st convention. ``` @@ -166,14 +169,24 @@ can be mapped and used for evaluating all kinds of different policies. Without a package naming rule, there's no way the service can automatically generate HTTP endpoints for working with arbitrary dynamically uploaded policies. -## GDPR +### Policy Extensions Functions + +A brief documentation for the available Rego extensions functions +which can be used during policy development. + +[Policy Extensions Functions](./doc/policy_development.md) + +You can also look at the source code in package [`regofunc`](./internal/regofunc) to understand the +inner-working and capabilities of the extension functions. + +### GDPR [GDPR](GDPR.md) -## Dependencies +### Dependencies [Dependencies](go.mod) -## License +### License [Apache 2.0 license](LICENSE) diff --git a/doc/policy_development.md b/doc/policy_development.md new file mode 100644 index 0000000000000000000000000000000000000000..b78189aeafc16b90acac9b1cec91949c7adcf7d6 --- /dev/null +++ b/doc/policy_development.md @@ -0,0 +1,380 @@ +# Policy Development Extensions + +The policy service extends the standard Rego runtime with custom +built-in functions and some custom functionalities described here. + +### Pure Evaluation Results + +In the Rego language there is a blank identifier variable which +can be used for assignments. If used like it's shown below, the +result from the policy evaluation will not be embedded in variable, +but will be returned as *pure* result. + +> This is custom functionality developed in the policy service +> and is not standard behaviour of the OPA Rego runtime. + +Below are two examples for what it means. + +If the following policy is evaluated, the returned result will be +*embedded* in an attribute named `credential`: + +``` +package example.createProof + +credential := proof.create(input) +``` + +Result: +```json +{ + "credential": { + "@context": "...", + "type": "VerifiableCredential", + "credentialSubject": {...}, + "proof": {...} + } +} +``` + +If however a blank identifier is used for the assignment, the result +of the policy evaluation won't be embedded in an attribute named +`credential` but will be returned directly: + +``` +package example.createProof + +_ := proof.create(input) +``` + +Result: +```json +{ + "@context": "...", + "type": "VerifiableCredential", + "credentialSubject": {...}, + "proof": {...} +} +``` + +A policy developer can use the blank identifier assignment to skip the +mapping of a function call to a JSON attribute name. The result of the +function call will be returned directly as JSON. + +This is useful in case you want to return a DID document or Verifiable Credential +from policy evaluation, and the document must not be mapped to an upper level attribute. + +### Extension Functions + +A number of Rego extension functions are developed and injected in the +policy service Rego runtime. Here is a list with brief description for +each one of them. + +#### cache.get + +The function retrieves JSON data from the Cache service. It accepts +three parameters used to identify the underlying Cache key. Only the +first one named `key` is required, the other two may be empty. + +Example: +``` +package example.cacheGet + +data := cache.get("mykey", "", "") +``` + +#### cache.set + +The function inserts JSON data into the Cache service. It accepts +four parameters. First three are used to identify/construct the +underlying Cache key. The last one is the data to be stored. + +Example: +``` +package example.cacheSet + +result := cache.set("mykey", "", "", input.data) +``` + +#### did.resolve + +Resolve DID using the [Universal DID Resolver](https://github.com/decentralized-identity/universal-resolver) +and return the resolved DID document. + +Example: +``` +package example.didResolve + +result := did.resolve("did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6") +``` + +#### task.create + +Start asynchronous task and pass the given data as task input. The function accepts two +parameters: task name and the input data. + +Example: +``` +package example.taskCreate + +result := task.create("task-name", input.data) +``` + +#### tasklist.create + +Start asynchronous task list and pass the given data as input. The function accepts two +parameters: task list name and the input data. + +Example: +``` +package example.tasklist + +result := tasklist.create("task-list-name", input.data) +``` + +#### keys.get + +Retrieve a specific public key from the signer service. The function accepts one +argument which is the name of the key. The key is returned in JWK format +wrapped in a DID verification method envelope. + +Example: +``` +package example.getkey + +_ := keys.get("key1") +``` + +Result: +```json +{ + "id": "key1", + "publicKeyJwk": { + "crv": "P-256", + "kid": "key1", + "kty": "EC", + "x": "RTx_2cyYcGVSIRP_826S32BiZxSgnzyXgRYmKP8N2l0", + "y": "unnPzMAnbByBMq2l9WWKsDFE-MDvX6hYhrESsjAaT50" + }, + "type": "JsonWebKey2020" +} +``` + +#### keys.getAll + +Retrieve all public keys from the signer service. The result is JSON array of +keys in JWK format wrapped in a DID verification method envelope. + +Example: +``` +package example.getAllKeys + +_ := keys.getAll() +``` + +Result: +```json +[ + { + "id": "key1", + "publicKeyJwk": { + "crv": "P-256", + "kid": "key1", + "kty": "EC", + "x": "RTx_2cyYcGVSIRP_826S32BiZxSgnzyXgRYmKP8N2l0", + "y": "unnPzMAnbByBMq2l9WWKsDFE-MDvX6hYhrESsjAaT50" + }, + "type": "JsonWebKey2020" + }, + { + ... + } +] +``` + +#### issuer + +Retrieve DID issuer value configured in the signer service. + +Example: +``` +package example.getIssuer + +did := issuer().did +``` + +Result: +```json +{ + "did": "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6" +} +``` + +#### proof.create + +Create a proof for Verifiable Credential or Verifiable Presentation. +The function accepts one argument which represents a VC or VP in JSON format. +It calls the signer service to generate a proof and returns the response, +which is the same VC/VP but with proof section. + +Example Policy: +``` +package example.createProof + +_ := proof.create(input) +``` + +Example VC given to policy evaluation: +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "credentialSubject": { + "allow": true, + "id": "example/examplePolicy/1.0" + }, + "issuanceDate": "2022-07-12T13:59:35.246990412Z", + "issuer": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation", + "type": "VerifiableCredential" +} +``` + +Example Response: +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "credentialSubject": { + "allow": true, + "id": "example/examplePolicy/1.0" + }, + "issuanceDate": "2022-07-12T13:59:35.246990412Z", + "issuer": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation", + "proof": { + "created": "2022-07-21T09:57:37.761706653Z", + "jws": "eyJhbGciOiJKc29uV2ViU2lnbmF0dXJlMjAyMCIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MEUCIQCOuTbwqembXOv2wjPhPkjR5Minf27DhO_KbNmXdxRxKQIgK3DTaucbir5SYNi_5Xwj8mpKoXxoKzF5_ZYUJB98IBE", + "proofPurpose": "assertionMethod", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation#key1" + }, + "type": "VerifiableCredential" +} +``` + +#### proof.verify + +Verify a proof for Verifiable Credential or Verifiable Presentation. +The function accepts one argument which represents a VC or VP in JSON format. +It calls the signer service to validate the proof. + +Example Policy: +``` +package example.verifyProof + +valid := proof.verify(input) +``` + +Example VC given to policy evaluation: +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "credentialSubject": { + "allow": true, + "id": "example/examplePolicy/1.0" + }, + "issuanceDate": "2022-07-12T13:59:35.246990412Z", + "issuer": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation", + "proof": { + "created": "2022-07-21T09:57:37.761706653Z", + "jws": "eyJhbGciOiJKc29uV2ViU2lnbmF0dXJlMjAyMCIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..MEUCIQCOuTbwqembXOv2wjPhPkjR5Minf27DhO_KbNmXdxRxKQIgK3DTaucbir5SYNi_5Xwj8mpKoXxoKzF5_ZYUJB98IBE", + "proofPurpose": "assertionMethod", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:gaiax.vereign.com:tsa:policy:policy:example:returnDID:1.0:evaluation#key1" + }, + "type": "VerifiableCredential" +} +``` + +Result: +```json +{ + "valid": true +} +``` + +#### ocm.getLoginProofInvitation + +Get a Proof Invitation URL from OCM's "out-of-band" endpoint. +This function accepts two arguments. The first argument is an array of scopes used to identify +credential types in OCM. The second argument is a map between scopes and credential types +which is statically defined in a `data.json` file. + +Example request body: +```json +{ + "scope": ["openid", "email"] +} +``` + +Example `data.json` file containing "scope-to-credential-type" map: +```json +{ + "scopes": { + "openid": "principalMemberCredential", + "email": "universityCert" + } +} +``` + +Example policy: + +```rego +package example.GetLoginProofInvitation + +_ = ocm.getLoginProofInvitation(input.scope, data.scopes) +``` + +Result: + +```json +{ + "link": "https://ocm:443/didcomm/?d_m=eyJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vc9tbSJ9fQ", + "requestId": "851076fa-da78-444a-9127-e636c5102f40" +} +``` + +#### ocm.GetLoginProofResult + +Get a Proof Invitation result from OCM containing a flattened list of claims. +This function accepts one argument which is the `resuestId` from the +`ocm.getLoginProofInvitation` result. + +Example policy: + +```rego +package example.GetLoginProofResult + +_ = ocm.getLoginProofResult(input.requestId) +``` + +Result: +```json +{ + "name": "John Doe", + "given_name": "John", + "family_name": "Doe", + "email": "example@example.com", + "email_verified": true, + "preferred_username": "john", + "gender": "NA" +} +```