gRPC Server Security

Security integration of the gRPC server

Maven Coordinates

To enable gRPC Server Security add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.security.integration</groupId>
    <artifactId>helidon-security-integration-grpc</artifactId>
</dependency>
Copied

Bootstrapping

There are two steps to configure security with gRPC server:

  1. Create security instance and register it with server
  2. Protect gRPC services of server with various security features
Example using builders
// gRPC server's routing
GrpcRouting.builder()
    // This is step 1 - register security instance with gRPC server processing
    // security - instance of security either from config or from a builder
    // securityDefaults - default enforcement for each service that has a security definition
    .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
    // this is step 2 - protect a service
    // register and protect this service with authentication (from defaults) and role "user"
    .register(greetService, GrpcSecurity.rolesAllowed("user"))
    .build();
Copied
Example using builders for more fine grained method level security
// create the service descriptor
ServiceDescriptor greetService = ServiceDescriptor.builder(new GreetService())
        // Add an instance of gRPC security that will apply to all methods of
        // the service - in this case require the "user" role
        .intercept(GrpcSecurity.rolesAllowed("user"))
        // Add an instance of gRPC security that will apply to the "SetGreeting"
        // method of the service - in this case require the "admin" role
        .intercept("SetGreeting", GrpcSecurity.rolesAllowed("admin"))
        .build();

// Create the gRPC server's routing
GrpcRouting.builder()
    // This is step 1 - register security instance with gRPC server processing
    // security - instance of security either from config or from a builder
    // securityDefaults - default enforcement for each service that has a security definition
    .intercept(GrpcSecurity.create(security).securityDefaults(GrpcSecurity.authenticate()))
    // this is step 2 - add the service descriptor
    .register(greetService)
    .build();
Copied
Example using configuration
GrpcRouting.builder()
    // helper method to load both security and gRPC server security from configuration
    .intercept(GrpcSecurity.create(config))
    // continue with gRPC server route configuration...
    .register(new GreetService())
    .build();
Copied
Example using configuration - configuration (HOCON)
# This may change in the future - to align with gRPC server configuration,
# once it is supported
security
  grpc-server:
    # Configuration of integration with gRPC server
    defaults:
        authenticate: true
    # Configuration security for individual services
    services:
    - name: "GreetService"
      defaults:
      roles-allowed: ["user"]
      # Configuration security for individual methods of the service
      methods:
      - name: "SetGreeting"
        roles-allowed: ["admin"]
Copied

Client security

When using the Helidon SE gRPC client API security can be configured for a gRPC service or at the individual method level. The client API has a custom CallCredentials implementation that integrates with the Helidon security APIs.

Example configuring client security for a service
Security security = Security.builder()  
        .addProvider(HttpBasicAuthProvider.create(config.get("http-basic-auth")))
        .build();

GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) 
        .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
        .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
        .build();

ClientServiceDescriptor descriptor = ClientServiceDescriptor 
        .builder(StringService.class)
        .unary("Lower")
        .callCredentials(clientSecurity)                     
        .build();

GrpcServiceClient client = GrpcServiceClient.create(channel, descriptor); 

String response = client.blockingUnary("Lower", "ABCD"); 
Copied
  • Create the Helidon Security instance (in this case using the basic auth provider)
  • Create the GrpcClientSecurity gRPC CallCredentials adding the user and password property expected by the basic auth provider.
  • Create the gRPC ClientServiceDescriptor for the StringService gRPC service.
  • Set the GrpcClientSecurity instance as the call credentials for all methods of the service
  • Create a GrpcServiceClient that will allow methods to be called on the service
  • Call the "Lower" method which will use the configured basic auth credentials
Example configuring client security for a specific method
GrpcClientSecurity clientSecurity = GrpcClientSecurity.builder(security.createContext("test.client")) 
        .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, user)
        .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, password)
        .build();

ClientServiceDescriptor descriptor = ClientServiceDescriptor 
        .builder(StringService.class)
        .unary("Lower")
        .unary("Upper", rules -> rules.callCredentials(clientSecurity)) 
        .build();
Copied
  • Create the GrpcClientSecurity call credentials in the same way as above.
  • Create the ClientServiceDescriptor, this time with two unary methods, "Lower" and "Upper".
  • The "Upper" method is configured to use the GrpcClientSecurity call credentials, the "Lower" method will be called without any credentials.

Outbound security

Outbound security covers three scenarios:

  • Calling a secure gRPC service from inside a gRPC service method handler

  • Calling a secure gRPC service from inside a web server method handler

  • Calling a secure web endpoint from inside a gRPC service method handler

Within each scenario credentials can be propagated if the gRPC/http method handler is executing within a security context or credentials can be overridden to provide a different set of credentials to use to call the outbound endpoint.

Example calling a secure gRPC service from inside a gRPC service method handler
// Obtain the SecurityContext from the current gRPC call Context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();

// Create a gRPC CallCredentials that will use the current request's
// security context to configure outbound credentials
GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(securityContext);

// Create the gRPC stub using the CallCredentials
EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);
Copied
Example calling a secure gRPC service from inside a web server method handler
private static void propagateCredentialsWebRequest(ServerRequest req, ServerResponse res) {
    try {
        // Create a gRPC CallCredentials that will use the current request's
        // security context to configure outbound credentials
        GrpcClientSecurity clientSecurity = GrpcClientSecurity.create(req);

        // Create the gRPC stub using the CallCredentials
        EchoServiceGrpc.EchoServiceBlockingStub stub = noCredsEchoStub.withCallCredentials(clientSecurity);

        String message = req.queryParams().first("message").orElse(null);
        Echo.EchoResponse echoResponse = stub.echo(Echo.EchoRequest.newBuilder().setMessage(message).build());
        res.send(echoResponse.getMessage());
    } catch (StatusRuntimeException e) {
        res.status(GrpcHelper.toHttpResponseStatus(e)).send();
    } catch (Throwable thrown) {
        res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send();
    }
}
Copied
Example calling a secure web endpoint from inside a gRPC service method handler
// Obtain the SecurityContext from the gRPC call Context
SecurityContext securityContext = GrpcSecurity.SECURITY_CONTEXT.get();

// Use the SecurityContext as normal to make a http request
Response webResponse = client.target(url)
        .path("/test")
        .request()
        .property(ClientSecurity.PROPERTY_CONTEXT, securityContext)
        .get();
Copied