Skip to main content

OPA Java Tutorial: Hello World using gRPC

Enterprise OPA offers a gRPC API for application use cases demanding low-latency policy decisions. This tutorial will demonstrate how to build an integration against the gPRC API to make fast policy decisions from a Java application.

gRPC messages are serialized using Protocol Buffer definitions, as well as controlling the structure of messages they can also be used to generate SDKs to make integration easier in different languages - including Java.

To use the Enterprise OPA gRPC API from Java, we will make use of the SDKs generated from the Protobuf Schema published on Buf rather than the OPA Java SDK.

note

The gRPC API is only available in Enterprise OPA. If using open source OPA, or interested in integrating with the REST API instead, see the OPA Java Tutorial: Hello World.

Setup

First ensure you have installed the Enterprise OPA binary and have a valid key.

Trial License

To evaluate Enterprise OPA, you can obtain a trial license by Downloading eopa and running eopa license trial in the terminal.

Next, in a new directory, run gradle init. The prompts should be responded to as follows:

  • Select type of build to generate: Application
  • Select implementation language: Java
  • Enter target Java version: 21
  • Project name: docsample
  • Select application structure: Single application project
  • Select build script DSL: Groovy
  • Select test framework: JUnit Jupiter
  • Generate build using new APIs and behavior: no

Gradle should print a message like the one below if everything went right.

BUILD SUCCESSFUL in 3m 56s
1 actionable task: 1 executed

Before proceeding, verify that the project Gradle created for us is able to build and run. The output should look similar to the below.

./gradlew run

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 627ms
2 actionable tasks: 2 executed

Adding the gRPC SDK and dependencies

The app/build.gradle file must also be updated to pull in the Enterprise OPA Java gRPC SDK and necessary gRPC dependencies.

Under the repositories section, add the following to pull dependencies from buf.build:

  maven {
name = 'buf'
url 'https://buf.build/gen/maven'
}

Under the dependencies section, add the following:

    implementation("build.buf.gen:styra_enterprise-opa_grpc_java:+")
implementation("build.buf.gen:styra_enterprise-opa_protocolbuffers_java:+")
implementation("com.google.protobuf:protobuf-java-util:+")
implementation("io.grpc:grpc-okhttp:+")
warning

Using the version string + causes Gradle to pull in the latest version of that dependency. In a production environment, it may be preferable to pin a specific version of your dependencies instead.

Adding the OPA Java gRPC SDK

Next, modify the Java code to utilize the OPA Java gRPC SDK in app/src/main/java/org/example/App.java

/*
* This source file was generated by the Gradle 'init' task
*/
package org.example;

import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Struct;

import build.buf.gen.eopa.data.v1.*;
import build.buf.gen.eopa.data.v1.DataServiceGrpc.DataServiceBlockingStub;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.util.concurrent.TimeUnit;

public class App {
public String getGreeting() {
return "Hello World!";
}

public static void main(String[] args) throws InvalidProtocolBufferException, InterruptedException {
System.out.println(new App().getGreeting());

String jsonString = """
{
"user": "bob",
"action": "read",
"resource": "dog123"
}
""";

Struct.Builder structBuilder = Struct.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(jsonString, structBuilder);
Struct input = (Struct) structBuilder.build();

InputDocument inputDocument = InputDocument.newBuilder().setDocument(input).build();
GetDataResponse response;

GetDataRequest request = GetDataRequest.newBuilder().setPath("app/abac").setInput(inputDocument).build();

ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090).usePlaintext().build();
DataServiceBlockingStub stub = DataServiceGrpc.newBlockingStub(channel);
try {

System.out.println("Making request");
response = stub.getData(request);

System.out.println(response.getResult());
} catch (Exception e) {
System.out.println("RPC failed: " + e);
return;
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}

boolean allowed = response.getResult().getDocument().getStructValue().getFieldsMap().get("allow").getBoolValue();
System.out.println("Allowed: " + allowed);
}
}

Creating policies

The sample application is almost ready to run, but first an Enterprise OPA instance is needed for the gRPC SDK to communicate with. We will be using the ABAC example policy from the OPA Playground.

Create a policy/ folder and the following two files:

policy/app/abac.rego

# Attribute-based Access Control (ABAC)
# -------------------------------------
#
# This example implements ABAC for a Pet Store API. The Pet Store API allows
# users to look at pets, adopt them, update their stats, and so on. The policy
# controls which users can perform actions on which resources. The policy implements
# a Attribute-based Access Control model where users, resources, and actions have
# attributes and the policy makes decisions based on those attributes.
#
# This example shows how to:
#
# * Implement ABAC using Rego that leverages external data.
# * Define helper rules that provide useful abstractions (e.g., `user_is_senior`).
#
# For more information see:
#
# * Rego comparison to other systems: https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/
#
# Hint: The Coverage feature lets you view the policy statements that were executed
# when the policy was last evaluated. Try enabling Coverage and running evaluation
# with different inputs.

package app.abac

import rego.v1

default allow := false

allow if user_is_owner

allow if {
user_is_employee
action_is_read
}

allow if {
user_is_employee
user_is_senior
action_is_update
}

allow if {
user_is_customer
action_is_read
not pet_is_adopted
}

user_is_owner if data.user_attributes[input.user].title == "owner"

user_is_employee if data.user_attributes[input.user].title == "employee"

user_is_customer if data.user_attributes[input.user].title == "customer"

user_is_senior if data.user_attributes[input.user].tenure > 8

action_is_read if input.action == "read"

action_is_update if input.action == "update"

pet_is_adopted if data.pet_attributes[input.resource].adopted == true

policy/data.json

{
"user_attributes": {
"alice": {
"tenure": 20,
"title": "owner"
},
"bob": {
"tenure": 15,
"title": "employee"
},
"eve": {
"tenure": 5,
"title": "employee"
},
"dave": {
"tenure": 5,
"title": "customer"
}
},
"pet_attributes": {
"dog123": {
"adopted": true,
"age": 2,
"breed": "terrier",
"name": "toto"
},
"dog456": {
"adopted": false,
"age": 3,
"breed": "german-shepherd",
"name": "rintintin"
},
"dog789": {
"adopted": false,
"age": 2,
"breed": "collie",
"name": "lassie"
},
"cat123": {
"adopted": false,
"age": 1,
"breed": "fictitious",
"name": "cheshire"
}
}
}

Running Enterprise OPA

Next create a configuration file for your Enterprise OPA instance that enables gRPC.

config.yaml

plugins:
grpc:
addr:
localhost:9090

Next, run Enterprise OPA. You should see the following output:

eopa run --server --config-file ./config.yaml ./policy
{"addrs":["localhost:8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2024-07-11T14:36:34-07:00"}
{"level":"info","msg":"Starting gRPC server on port: localhost:9090","time":"2024-07-11T14:36:34-07:00"}

If you see errors related to not having a license key, follow any instructions to resolve.

tip

Consider running OPA in a separate background terminal, as it will be necessary to run more shell commands.

Verify that the data was loaded into Enterprise OPA correctly using the HTTP API, which is exposed by default on localhost:8181:

curl -Ss http://localhost:8181/v1/data/user_attributes/alice\?pretty\=true
{
"result": {
"tenure": 20,
"title": "owner"
}
}

Verify that the policy is working as expected using the HTTP API:

curl -Ss http://localhost:8181/v1/data/app/abac/allow -X POST -d '{"input": {"user": "bob", "action": "read", "resource": "dog123"}}'
{"result":true}

curl -Ss http://localhost:8181/v1/data/app/abac/allow -X POST -d '{"input": {"user": "dave", "action": "read", "resource": "dog123"}}'
{"result":false}

Running the application

Finally, run the Java program:

./gradlew run

> Task :app:run
Hello World!
Making request
path: "app/abac"
document {
struct_value {
fields {
key: "action_is_read"
value {
bool_value: true
}
}
fields {
key: "allow"
value {
bool_value: true
}
}
fields {
key: "pet_is_adopted"
value {
bool_value: true
}
}
fields {
key: "user_is_employee"
value {
bool_value: true
}
}
fields {
key: "user_is_senior"
value {
bool_value: true
}
}
}
}

Allowed: true

BUILD SUCCESSFUL in 1s
2 actionable tasks: 1 executed, 1 up-to-date

Refactoring the code

We can explore a bit how the Java gRPC SDK behaves by changing App.java:

        GetDataRequest request = GetDataRequest.newBuilder().setPath("app/abac").setInput(inputDocument).build();
GetDataRequest request = GetDataRequest.newBuilder().setPath("app/abac/allow").setInput(inputDocument).build();

...

boolean allowed = response.getResult().getDocument().getStructValue().getFieldsMap().get("allow").getBoolValue();
boolean allowed = response.getResult().getDocument().getBoolValue();

Re-running the application should return the following result:

./gradlew run

> Task :app:run
Hello World!
Making request
path: "app/abac/allow"
document {
bool_value: true
}

Allowed: true

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed