- gRPC Server Security
Security integration of the gRPC server
Prerequisites
Declare the following dependency in your project:
<dependency>
<groupId>io.helidon.security.integration</groupId>
<artifactId>helidon-security-integration-grpc</artifactId>
</dependency>Bootstrapping
There are two steps to configure security with gRPC server:
- Create security instance and register it with server
- Protect gRPC services of server with various security features
// 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();// 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();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();# 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"]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.
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"); - Create the Helidon
Securityinstance (in this case using the basic auth provider) - Create the
GrpcClientSecuritygRPCCallCredentialsadding the user and password property expected by the basic auth provider. - Create the gRPC
ClientServiceDescriptorfor theStringServicegRPC service. - Set the
GrpcClientSecurityinstance as the call credentials for all methods of the service - Create a
GrpcServiceClientthat will allow methods to be called on the service - Call the "Lower" method which will use the configured basic auth credentials
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();- Create the
GrpcClientSecuritycall 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
GrpcClientSecuritycall 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.
// 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);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();
}
}// 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(ClientSecurityFeature.PROPERTY_CONTEXT, securityContext)
.get();