Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arrow Flight Auth 1 and 2 #2713

Merged
merged 5 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 0 additions & 53 deletions Util/src/main/java/io/deephaven/util/auth/AuthContext.java

This file was deleted.

52 changes: 52 additions & 0 deletions authentication/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Deephaven's Approach to Authentication
======================================

Deephaven integrates with both the original and the re-envisioned versions of Flight Auth.

Configuration
-------------

Add `-DAuthHandlers=$CLASS_1,$CLASS_2,etc...` to your jvm command line to enable authentication for each supplied
class.

Flight Basic Auth
-----------------

Implement `io.deephaven.auth.BasicAuthMarshaller.Handler` and add to the list of enabled authenticators.
Deephaven will check both Auth1 and Auth2 payloads to see if the request is via Flight's `BasicAuth` before moving
to other authenticators.

More Complex Auth
-----------------

Implement `io.deephaven.auth.AuthenticationRequestHandler` and add to the list of enabled authenticators. User are
not required to implement both `login` methods unless they want to support clients using both Auth1 and Auth2. The
interface includes a `String getAuthType()` which is used to route requests from clients to the proper
authentication authority.

Flight Auth1 `FlightService#Handshake`
--------------------------------------
Flight's gRPC call for `Handshake()` takes a `HandshakeRequest`, which provides both a payload in bytes and a
protocol version int64. The protocol version value is never written by current FlightClient implementations, leaving
servers to only recognize the authentication details by the payload bytes.

As a result, our implementation tries to ignore protocol version. We felt that the API did not provided sufficient
means for supporting multiple forms of authentication simulatenously. Aside from the `BasicAuth` support, we expect
the `Handshake` payload to be a `WrappedAuthenticationRequest`. This is a tuple of `type` and `payload`. The `type`
is used to route the request to the instance of `io.deephaven.auth.AuthenticationRequestHandler` where `getAuthType`
is an exact match.


Flight Auth2 `Authentication` Header
------------------------------------
Flight Auth was reenvisioned for the client to send an `Authentication` header to identify themselves. The header
contains a prefix such as `Basic` or `Bearer` followed by a text payload. This prefix is used to route the request
to the instance of `io.deephaven.auth.AuthenticationRequestHandler` where `getAuthType` is an exact match.

Session Bearer Token
--------------------

Deephaven's server builds a Session around each authentication. An identification token is provided to the client
in the form of a bearer token. This token needs to be supplied with all subsequent gRPC requests to match RPCs to
a session and therefore an authorization context. As clients tend to be emphemeral, the server requires that the
client rotates this bearer token and in return will ensure oustanding state continues to be available to that client.
14 changes: 14 additions & 0 deletions authentication/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id 'java-library'
id 'io.deephaven.project.register'
}

description 'authentication: Deephaven authentication and identity'

dependencies {
api project(':Base')
api project(':proto:proto-backplane-grpc')
implementation project(':log-factory')

Classpaths.inheritArrow(project, 'flight-core', 'implementation')
}
1 change: 1 addition & 0 deletions authentication/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.deephaven.project.ProjectType=JAVA_PUBLIC
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.deephaven.auth;

import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.Logger;

import java.nio.ByteBuffer;
import java.util.Optional;

/**
* Handler to accept an empty payload and accept that user as anonymous. To prevent anonymous access, do not enable this
* authentication handler.
*/
public class AnonymousAuthenticationHandler implements AuthenticationRequestHandler {
private final static Logger log = LoggerFactory.getLogger(AnonymousAuthenticationHandler.class);

@Override
public String getAuthType() {
return "Anonymous";
}

@Override
public void initialize(String targetUrl) {
for (int ii = 0; ii < 5; ++ii) {
log.warn().endl();
}
log.warn().append("================================================================================").endl();
log.warn().append("WARNING! Anonymous authentication is enabled. This is not recommended!").endl();
log.warn().append(" Listening on ").append(targetUrl).endl();
log.warn().append("================================================================================").endl();
for (int ii = 0; ii < 5; ++ii) {
log.warn().endl();
}
}

@Override
public Optional<AuthContext> login(long protocolVersion, ByteBuffer payload, HandshakeResponseListener listener) {
if (!payload.hasRemaining()) {
return Optional.of(new AuthContext.Anonymous());
}
return Optional.empty();
}

@Override
public Optional<AuthContext> login(String payload, MetadataResponseListener listener) {
if (payload.length() == 0) {
return Optional.of(new AuthContext.Anonymous());
}
return Optional.empty();
}
}
36 changes: 36 additions & 0 deletions authentication/src/main/java/io/deephaven/auth/AuthContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.auth;

import io.deephaven.base.log.LogOutput;
import io.deephaven.base.log.LogOutputAppendable;
import io.deephaven.io.log.impl.LogOutputStringImpl;

public abstract class AuthContext implements LogOutputAppendable {

@Override
public abstract LogOutput append(LogOutput logOutput);

@Override
public final String toString() {
return new LogOutputStringImpl().append(this).toString();
}

/**
* A trivial auth context that allows a user to do everything the APIs allow.
*/
public static class SuperUser extends AuthContext {
@Override
public LogOutput append(LogOutput logOutput) {
return logOutput.append("SuperUser");
}
}

public static class Anonymous extends AuthContext {
@Override
public LogOutput append(LogOutput logOutput) {
return logOutput.append("Anonymous");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.deephaven.auth;

/**
* An error occurred and this handshake to authenticate has failed for some reason. Details are not provided to the
* user.
*/
public class AuthenticationException extends Exception {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.deephaven.auth;

import java.nio.ByteBuffer;
import java.util.Optional;

/**
* Simple interface to handle incoming authentication requests from flight/barrage clients, via Handshake or the Flight
* Authentication header. This is intended to be a low-level way to handle incoming payload bytes.
*/
public interface AuthenticationRequestHandler {

/**
* This handler can be referred to via both Arrow Flight's original Auth and Auth2.
*
* To use via the original Arrow Flight Handshake, the request should be sent in a
* {@link io.deephaven.proto.backplane.grpc.WrappedAuthenticationRequest} with this handler's identity string.
*
* To use via Arrow Flight Auth 2's metadata header, then the
* {@link org.apache.arrow.flight.auth2.Auth2Constants#AUTHORIZATION_HEADER} should be prefixed with this handler's
* identity string.
*
* @return the type string used to identify the handler
*/
String getAuthType();

/**
* Given a protocol version (very likely to be zero) and payload bytes, if possible authenticate this user. If the
* handler can correctly decode the payload and confirm the user's identity, an appropriate UserContext should be
* returned. If the payload is correctly decoded and definitely isn't a valid user, an exception may be thrown. If
* there is ambiguity in decoding the payload (leading to apparent "not a valid user") or the payload cannot be
* decoded, an empty optional should be returned.
*
* Note that regular arrow flight clients cannot specify the protocolVersion; to be compatible with flight auth
* assume protocolVersion will be zero.
*
* @param protocolVersion Mostly unused, this is an allowed field to set on HandshakeRequests from the Flight gRPC
* call.
* @param payload The byte payload of the handshake, such as an encoded protobuf.
* @param listener The handshake response observer, which enables multi-request authentication.
* @return AuthContext for this user if applicable else Empty
*/
Optional<AuthContext> login(long protocolVersion, ByteBuffer payload, HandshakeResponseListener listener)
throws AuthenticationException;

/**
* Given a payload string, if possible authenticate this user. If the handler can correctly decode the payload and
* confirm the user's identity, an appropriate UserContext should be returned. If the payload is correctly decoded
* and definitely isn't a valid user, an exception may be thrown. If there is ambiguity in decoding the payload
* (leading to apparent "not a valid user") or the payload cannot be decoded, an empty optional should be returned.
*
* Note that metadata can only be sent with the initial gRPC response; multi-message authentication via gRPC
* metadata headers require multiple gRPC call attempts.
*
* @param payload The byte payload of the {@code Authorization} header, such as an encoded protobuf or b64 encoded
* string.
* @param listener The metadata response observer, which enables multi-request authentication.
* @return AuthContext for this user if applicable else Empty
*/
Optional<AuthContext> login(String payload, MetadataResponseListener listener)
throws AuthenticationException;

/**
* Initialize request handler with the provided url.
*
* @param targetUrl the base url of the hosted UI
*/
void initialize(String targetUrl);

interface HandshakeResponseListener {
void respond(long protocolVersion, ByteBuffer payload);
}
interface MetadataResponseListener {
void addHeader(String key, String string);
}
}
Loading