diff --git a/local-dev/issue-certificates.sh b/local-dev/issue-certificates.sh index 0ba49a1a2..25668f7f3 100755 --- a/local-dev/issue-certificates.sh +++ b/local-dev/issue-certificates.sh @@ -51,7 +51,7 @@ echo "Creating Client Certificates" openssl req -newkey rsa:2048 -nodes \ -out "$CLIENT_PATH/client.csr" \ -keyout "$CLIENT_PATH/client.key" \ - -subj "/CN=localhost/O=client organization" \ + -subj "/CN=obiwan/O=client organization" \ -addext "subjectAltName = DNS:localhost" > /dev/null 2>&1 openssl x509 -req -sha256 -days 3650 \ -CA "$CA_PATH/ca.crt" \ diff --git a/server/src/main/java/io/littlehorse/common/LHServerConfig.java b/server/src/main/java/io/littlehorse/common/LHServerConfig.java index 4caff8eb5..cece0e29b 100644 --- a/server/src/main/java/io/littlehorse/common/LHServerConfig.java +++ b/server/src/main/java/io/littlehorse/common/LHServerConfig.java @@ -1060,17 +1060,12 @@ public ServerCredentials getInternalServerCreds() { String caCertFile = getOrSetDefault(INTERNAL_CA_CERT_KEY, null); String serverCertFile = getOrSetDefault(INTERNAL_SERVER_CERT_KEY, null); String serverKeyFile = getOrSetDefault(INTERNAL_SERVER_KEY_KEY, null); - return getCreds(caCertFile, serverCertFile, serverKeyFile); - } - public ChannelCredentials getInternalClientCreds() { - String caCertFile = getOrSetDefault(INTERNAL_CA_CERT_KEY, null); - String serverCertFile = getOrSetDefault(INTERNAL_SERVER_CERT_KEY, null); - String serverKeyFile = getOrSetDefault(INTERNAL_SERVER_KEY_KEY, null); if (caCertFile == null) { - log.info("No ca cert file, using plaintext internal client"); + log.info("No ca cert file found, deploying insecure!"); return null; } + if (serverCertFile == null || serverKeyFile == null) { throw new LHMisconfigurationException("CA cert file provided but missing cert or key"); } @@ -1079,21 +1074,24 @@ public ChannelCredentials getInternalClientCreds() { File rootCA = new File(caCertFile); try { - return TlsChannelCredentials.newBuilder() + return TlsServerCredentials.newBuilder() .keyManager(serverCert, serverKey) .trustManager(rootCA) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) .build(); } catch (IOException exn) { throw new RuntimeException(exn); } } - private ServerCredentials getCreds(String caCertFile, String serverCertFile, String serverKeyFile) { + public ChannelCredentials getInternalClientCreds() { + String caCertFile = getOrSetDefault(INTERNAL_CA_CERT_KEY, null); + String serverCertFile = getOrSetDefault(INTERNAL_SERVER_CERT_KEY, null); + String serverKeyFile = getOrSetDefault(INTERNAL_SERVER_KEY_KEY, null); if (caCertFile == null) { - log.info("No ca cert file found, deploying insecure!"); + log.info("No ca cert file, using plaintext internal client"); return null; } - if (serverCertFile == null || serverKeyFile == null) { throw new LHMisconfigurationException("CA cert file provided but missing cert or key"); } @@ -1102,10 +1100,9 @@ private ServerCredentials getCreds(String caCertFile, String serverCertFile, Str File rootCA = new File(caCertFile); try { - return TlsServerCredentials.newBuilder() + return TlsChannelCredentials.newBuilder() .keyManager(serverCert, serverKey) .trustManager(rootCA) - .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) .build(); } catch (IOException exn) { throw new RuntimeException(exn); diff --git a/server/src/main/java/io/littlehorse/server/LHServer.java b/server/src/main/java/io/littlehorse/server/LHServer.java index 13aaae957..9d450ddb4 100644 --- a/server/src/main/java/io/littlehorse/server/LHServer.java +++ b/server/src/main/java/io/littlehorse/server/LHServer.java @@ -20,9 +20,8 @@ import io.littlehorse.sdk.common.exception.LHMisconfigurationException; import io.littlehorse.sdk.common.proto.LHHostInfo; import io.littlehorse.sdk.common.proto.PollTaskResponse; -import io.littlehorse.server.auth.InternalCallCredentials; import io.littlehorse.server.auth.RequestAuthorizer; -import io.littlehorse.server.auth.RequestSanitizer; +import io.littlehorse.server.auth.internalport.InternalCallCredentials; import io.littlehorse.server.listener.ServerListenerConfig; import io.littlehorse.server.monitoring.HealthService; import io.littlehorse.server.streams.BackendInternalComms; @@ -121,8 +120,7 @@ private LHServerListener createListener(ServerListenerConfig listenerConfig) { List.of( new MetricCollectingServerInterceptor(healthService.getMeterRegistry()), new RequestAuthorizer(contextKey, metadataCache, coreStoreProvider, config), - listenerConfig.getServerAuthorizer(), - new RequestSanitizer()), + listenerConfig.getRequestAuthenticator()), contextKey); } diff --git a/server/src/main/java/io/littlehorse/server/LHServerListener.java b/server/src/main/java/io/littlehorse/server/LHServerListener.java index 85e3fb3bd..33193650a 100644 --- a/server/src/main/java/io/littlehorse/server/LHServerListener.java +++ b/server/src/main/java/io/littlehorse/server/LHServerListener.java @@ -199,7 +199,7 @@ import io.littlehorse.sdk.common.proto.WorkflowEventId; import io.littlehorse.sdk.common.proto.WorkflowEventIdList; import io.littlehorse.sdk.common.proto.WorkflowEventList; -import io.littlehorse.server.auth.InternalCallCredentials; +import io.littlehorse.server.auth.internalport.InternalCallCredentials; import io.littlehorse.server.listener.ServerListenerConfig; import io.littlehorse.server.streams.BackendInternalComms; import io.littlehorse.server.streams.lhinternalscan.PublicScanReply; diff --git a/server/src/main/java/io/littlehorse/server/auth/InsecureServerAuthorizer.java b/server/src/main/java/io/littlehorse/server/auth/InsecureServerAuthorizer.java deleted file mode 100644 index 035fb2059..000000000 --- a/server/src/main/java/io/littlehorse/server/auth/InsecureServerAuthorizer.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.littlehorse.server.auth; - -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCall.Listener; -import io.grpc.ServerCallHandler; - -public class InsecureServerAuthorizer implements ServerAuthorizer { - - @Override - public Listener interceptCall( - ServerCall call, Metadata headers, ServerCallHandler next) { - return next.startCall(call, headers); - } - - public static InsecureServerAuthorizer create() { - return new InsecureServerAuthorizer(); - } -} diff --git a/server/src/main/java/io/littlehorse/server/auth/ServerAuthorizer.java b/server/src/main/java/io/littlehorse/server/auth/LHServerInterceptor.java similarity index 71% rename from server/src/main/java/io/littlehorse/server/auth/ServerAuthorizer.java rename to server/src/main/java/io/littlehorse/server/auth/LHServerInterceptor.java index 1fe9df634..26cafd4d2 100644 --- a/server/src/main/java/io/littlehorse/server/auth/ServerAuthorizer.java +++ b/server/src/main/java/io/littlehorse/server/auth/LHServerInterceptor.java @@ -4,7 +4,11 @@ import io.grpc.ServerInterceptor; import io.littlehorse.common.LHConstants; -public interface ServerAuthorizer extends ServerInterceptor { +/** + * Wrapper over io.grpc.ServerInterceptor. I don't think this does anything other than put + * a few constants into scope without imports. + */ +public interface LHServerInterceptor extends ServerInterceptor { String INTERNAL_PREFIX = "_"; diff --git a/server/src/main/java/io/littlehorse/server/auth/MTLSServerAuthorizer.java b/server/src/main/java/io/littlehorse/server/auth/MTLSServerAuthorizer.java deleted file mode 100644 index 4ebd86e47..000000000 --- a/server/src/main/java/io/littlehorse/server/auth/MTLSServerAuthorizer.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.littlehorse.server.auth; - -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCall.Listener; -import io.grpc.ServerCallHandler; -import io.littlehorse.common.util.CertificateUtil; -import io.littlehorse.server.listener.MTLSConfig; - -public class MTLSServerAuthorizer implements ServerAuthorizer { - - private final String commonName; - - public MTLSServerAuthorizer(MTLSConfig mtlsConfig) { - try { - this.commonName = CertificateUtil.getCommonNameFromCertificate(mtlsConfig.getCaCertificate()); - } catch (Exception ex) { - throw new IllegalArgumentException("Certificate is not valid", ex); - } - } - - @Override - public Listener interceptCall( - ServerCall call, Metadata headers, ServerCallHandler next) { - headers.put(CLIENT_ID, this.commonName); - - return next.startCall(call, headers); - } -} diff --git a/server/src/main/java/io/littlehorse/server/auth/RequestAuthorizer.java b/server/src/main/java/io/littlehorse/server/auth/RequestAuthorizer.java index 06a46574e..dfda3d720 100644 --- a/server/src/main/java/io/littlehorse/server/auth/RequestAuthorizer.java +++ b/server/src/main/java/io/littlehorse/server/auth/RequestAuthorizer.java @@ -32,7 +32,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class RequestAuthorizer implements ServerAuthorizer { +public class RequestAuthorizer implements LHServerInterceptor { private final CoreStoreProvider coreStoreProvider; diff --git a/server/src/main/java/io/littlehorse/server/auth/RequestSanitizer.java b/server/src/main/java/io/littlehorse/server/auth/RequestSanitizer.java deleted file mode 100644 index bc1c5d903..000000000 --- a/server/src/main/java/io/littlehorse/server/auth/RequestSanitizer.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.littlehorse.server.auth; - -import io.grpc.Context; -import io.grpc.Contexts; -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCallHandler; - -public class RequestSanitizer implements ServerAuthorizer { - - @Override - public ServerCall.Listener interceptCall( - ServerCall call, Metadata headers, ServerCallHandler next) { - Context context = Context.current(); - headers.discardAll(CLIENT_ID); - return Contexts.interceptCall(context, call, headers, next); - } -} diff --git a/server/src/main/java/io/littlehorse/server/auth/authenticators/InsecureAuthenticator.java b/server/src/main/java/io/littlehorse/server/auth/authenticators/InsecureAuthenticator.java new file mode 100644 index 000000000..2a884ea27 --- /dev/null +++ b/server/src/main/java/io/littlehorse/server/auth/authenticators/InsecureAuthenticator.java @@ -0,0 +1,26 @@ +package io.littlehorse.server.auth.authenticators; + +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.littlehorse.common.LHConstants; +import io.littlehorse.server.auth.LHServerInterceptor; + +/** + * Authenticator for insecure server listeners. Sets the principal id to `anonymous` for all requests. + */ +public class InsecureAuthenticator implements LHServerInterceptor { + + @Override + public Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + + headers.put(CLIENT_ID, LHConstants.ANONYMOUS_PRINCIPAL); + return next.startCall(call, headers); + } + + public static InsecureAuthenticator create() { + return new InsecureAuthenticator(); + } +} diff --git a/server/src/main/java/io/littlehorse/server/auth/authenticators/MTLSAuthenticator.java b/server/src/main/java/io/littlehorse/server/auth/authenticators/MTLSAuthenticator.java new file mode 100644 index 000000000..df51dab2e --- /dev/null +++ b/server/src/main/java/io/littlehorse/server/auth/authenticators/MTLSAuthenticator.java @@ -0,0 +1,73 @@ +package io.littlehorse.server.auth.authenticators; + +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.Status; +import io.littlehorse.common.LHConstants; +import io.littlehorse.server.auth.LHServerInterceptor; +import java.security.Principal; +import java.util.List; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import lombok.extern.slf4j.Slf4j; + +/** + * Authenticator for requests on MTLS listeners. Sets the principal id to the common name of the client + * certificate. + */ +@Slf4j +public class MTLSAuthenticator implements LHServerInterceptor { + + @Override + public Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + + SSLSession clientTlsInfo = call.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION); + + if (clientTlsInfo == null) { + throw new IllegalStateException( + "MTLSAuthenticator should only be used on MTLS ports, so SSLSession should be present"); + } + + String commonName; + try { + // Determine commonName from the client certificate + Principal peerPrincipal = clientTlsInfo.getPeerPrincipal(); // NOT a littlehorse Principal + + LdapName ln = new LdapName(peerPrincipal.getName()); + + List commonNames = ln.getRdns().stream() + .filter(rdn -> rdn.getType().equalsIgnoreCase("CN")) + .map(Rdn::getValue) + .map(Object::toString) + .toList(); + + if (commonNames.size() == 0) { + // This happens when the client certificate does not have a CommonName. + // Note that the interceptor wouldn't even be called if the SSL handshake failed, + // so we know at this point that the client presented a valid certificate. + // + // Since they did not set a commonName on the certificate, we treat them as + // anonymous. + commonName = LHConstants.ANONYMOUS_PRINCIPAL; + } else { + commonName = commonNames.get(0); + } + + headers.put(CLIENT_ID, commonName); + log.trace("Got common name from client certificate: {}", commonName); + return next.startCall(call, headers); + } catch (InvalidNameException | SSLPeerUnverifiedException e) { + // close the call as unauthenticated + log.trace("Closing the call as unauthenticated due to certiciate exception", e); + call.close(Status.UNAUTHENTICATED.withDescription("Invalid certificate"), new Metadata()); + return new ServerCall.Listener<>() {}; + } + } +} diff --git a/server/src/main/java/io/littlehorse/server/auth/OAuthServerAuthenticator.java b/server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthAuthenticator.java similarity index 89% rename from server/src/main/java/io/littlehorse/server/auth/OAuthServerAuthenticator.java rename to server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthAuthenticator.java index c1c231fa0..ac6def30a 100644 --- a/server/src/main/java/io/littlehorse/server/auth/OAuthServerAuthenticator.java +++ b/server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthAuthenticator.java @@ -1,4 +1,4 @@ -package io.littlehorse.server.auth; +package io.littlehorse.server.auth.authenticators; import com.google.common.base.Strings; import com.google.common.cache.Cache; @@ -10,16 +10,21 @@ import io.grpc.ServerCallHandler; import io.grpc.Status; import io.littlehorse.sdk.common.auth.TokenStatus; +import io.littlehorse.server.auth.LHServerInterceptor; +import io.littlehorse.server.auth.OAuthConfig; +import io.littlehorse.server.auth.UnauthenticatedException; import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; /** + * Determines the Principal ID from the OAuth token. + * * Example: * https://github.com/grpc/grpc-java/blob/master/examples/example-oauth/src/main/java/io/grpc/examples/oauth/OAuth2ServerInterceptor.java */ @Slf4j -public class OAuthServerAuthenticator implements ServerAuthorizer { +public class OAuthAuthenticator implements LHServerInterceptor { private static final Metadata.Key AUTHORIZATION_HEADER_KEY = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); @@ -31,7 +36,7 @@ public class OAuthServerAuthenticator implements ServerAuthorizer { private final OAuthClient client; - public OAuthServerAuthenticator(OAuthConfig config) { + public OAuthAuthenticator(OAuthConfig config) { this.client = new OAuthClient(config); } diff --git a/server/src/main/java/io/littlehorse/server/auth/OAuthClient.java b/server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthClient.java similarity index 95% rename from server/src/main/java/io/littlehorse/server/auth/OAuthClient.java rename to server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthClient.java index 58cd814a6..cec2216b2 100644 --- a/server/src/main/java/io/littlehorse/server/auth/OAuthClient.java +++ b/server/src/main/java/io/littlehorse/server/auth/authenticators/OAuthClient.java @@ -1,4 +1,4 @@ -package io.littlehorse.server.auth; +package io.littlehorse.server.auth.authenticators; import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.Scope; @@ -13,6 +13,8 @@ import com.nimbusds.openid.connect.sdk.OIDCScopeValue; import io.littlehorse.sdk.common.auth.TokenStatus; import io.littlehorse.sdk.common.exception.EntityProviderException; +import io.littlehorse.server.auth.OAuthConfig; +import io.littlehorse.server.auth.UnauthenticatedException; import java.io.IOException; import java.time.Instant; import lombok.extern.slf4j.Slf4j; diff --git a/server/src/main/java/io/littlehorse/server/auth/InternalAuthorizer.java b/server/src/main/java/io/littlehorse/server/auth/internalport/InternalAuthorizer.java similarity index 82% rename from server/src/main/java/io/littlehorse/server/auth/InternalAuthorizer.java rename to server/src/main/java/io/littlehorse/server/auth/internalport/InternalAuthorizer.java index 93257fe3d..703676549 100644 --- a/server/src/main/java/io/littlehorse/server/auth/InternalAuthorizer.java +++ b/server/src/main/java/io/littlehorse/server/auth/internalport/InternalAuthorizer.java @@ -1,4 +1,4 @@ -package io.littlehorse.server.auth; +package io.littlehorse.server.auth.internalport; import io.grpc.Context; import io.grpc.Contexts; @@ -9,12 +9,20 @@ import io.littlehorse.common.model.getable.ObjectIdModel; import io.littlehorse.common.model.getable.objectId.PrincipalIdModel; import io.littlehorse.common.model.getable.objectId.TenantIdModel; +import io.littlehorse.server.auth.LHServerInterceptor; import io.littlehorse.server.streams.topology.core.CoreStoreProvider; import io.littlehorse.server.streams.topology.core.RequestExecutionContext; import io.littlehorse.server.streams.util.MetadataCache; import java.util.Objects; -public class InternalAuthorizer implements ServerAuthorizer { +/** + * ServerInterceptor to populate the RequestExecutionContext with the TenantId and PrincipalId + * for interactive queries between LH Servers. + * + * While the request is technically made from one LH Server to another, we want the receiving + * server to treat it as if it were made by the original client. + */ +public class InternalAuthorizer implements LHServerInterceptor { private final Context.Key executionContextKey; private final CoreStoreProvider coreStoreProvider; diff --git a/server/src/main/java/io/littlehorse/server/auth/InternalCallCredentials.java b/server/src/main/java/io/littlehorse/server/auth/internalport/InternalCallCredentials.java similarity index 85% rename from server/src/main/java/io/littlehorse/server/auth/InternalCallCredentials.java rename to server/src/main/java/io/littlehorse/server/auth/internalport/InternalCallCredentials.java index 941a8e85a..fb118366a 100644 --- a/server/src/main/java/io/littlehorse/server/auth/InternalCallCredentials.java +++ b/server/src/main/java/io/littlehorse/server/auth/internalport/InternalCallCredentials.java @@ -1,14 +1,20 @@ -package io.littlehorse.server.auth; +package io.littlehorse.server.auth.internalport; import io.grpc.CallCredentials; import io.grpc.Metadata; import io.grpc.Status; import io.littlehorse.common.AuthorizationContext; +import io.littlehorse.server.auth.RequestAuthorizer; import io.littlehorse.server.streams.topology.core.BackgroundContext; import io.littlehorse.server.streams.topology.core.ProcessorExecutionContext; import io.littlehorse.server.streams.topology.core.RequestExecutionContext; import java.util.concurrent.Executor; +/** + * Call Credentials that allows the LH Server to propagate information about the calling Tenant + * and Principal when making an Interactive Query between two different LH Servers on the + * internal port. + */ public class InternalCallCredentials extends CallCredentials { private final AuthorizationContext currentAuthorization; diff --git a/server/src/main/java/io/littlehorse/server/listener/ServerListenerConfig.java b/server/src/main/java/io/littlehorse/server/listener/ServerListenerConfig.java index d1aa0bf25..2279bd818 100644 --- a/server/src/main/java/io/littlehorse/server/listener/ServerListenerConfig.java +++ b/server/src/main/java/io/littlehorse/server/listener/ServerListenerConfig.java @@ -6,10 +6,10 @@ import io.grpc.TlsServerCredentials; import io.littlehorse.common.LHServerConfig; import io.littlehorse.server.auth.AuthorizationProtocol; -import io.littlehorse.server.auth.InsecureServerAuthorizer; -import io.littlehorse.server.auth.MTLSServerAuthorizer; -import io.littlehorse.server.auth.OAuthServerAuthenticator; -import io.littlehorse.server.auth.ServerAuthorizer; +import io.littlehorse.server.auth.LHServerInterceptor; +import io.littlehorse.server.auth.authenticators.InsecureAuthenticator; +import io.littlehorse.server.auth.authenticators.MTLSAuthenticator; +import io.littlehorse.server.auth.authenticators.OAuthAuthenticator; import java.io.IOException; import lombok.Builder; import lombok.Getter; @@ -67,11 +67,11 @@ public ServerCredentials getCredentials() { } } - public ServerAuthorizer getServerAuthorizer() { + public LHServerInterceptor getRequestAuthenticator() { return switch (authorizationProtocol) { - case OAUTH -> new OAuthServerAuthenticator(config.getOAuthConfig()); - case MTLS -> new MTLSServerAuthorizer(config.getMTLSConfiguration(name)); - default -> InsecureServerAuthorizer.create(); + case OAUTH -> new OAuthAuthenticator(config.getOAuthConfig()); + case MTLS -> new MTLSAuthenticator(); + default -> InsecureAuthenticator.create(); }; } } diff --git a/server/src/main/java/io/littlehorse/server/streams/BackendInternalComms.java b/server/src/main/java/io/littlehorse/server/streams/BackendInternalComms.java index 075002398..402d58989 100644 --- a/server/src/main/java/io/littlehorse/server/streams/BackendInternalComms.java +++ b/server/src/main/java/io/littlehorse/server/streams/BackendInternalComms.java @@ -53,8 +53,8 @@ import io.littlehorse.sdk.common.proto.LHHostInfo; import io.littlehorse.sdk.common.proto.WorkflowEvent; import io.littlehorse.server.GlobalExceptionHandler; -import io.littlehorse.server.auth.InternalAuthorizer; -import io.littlehorse.server.auth.InternalCallCredentials; +import io.littlehorse.server.auth.internalport.InternalAuthorizer; +import io.littlehorse.server.auth.internalport.InternalCallCredentials; import io.littlehorse.server.listener.AdvertisedListenerConfig; import io.littlehorse.server.streams.lhinternalscan.InternalScan; import io.littlehorse.server.streams.lhinternalscan.publicrequests.scanfilter.ScanFilterModel; diff --git a/server/src/main/java/io/littlehorse/server/streams/topology/core/ProcessorExecutionContext.java b/server/src/main/java/io/littlehorse/server/streams/topology/core/ProcessorExecutionContext.java index 07a540b80..07b283a73 100644 --- a/server/src/main/java/io/littlehorse/server/streams/topology/core/ProcessorExecutionContext.java +++ b/server/src/main/java/io/littlehorse/server/streams/topology/core/ProcessorExecutionContext.java @@ -12,7 +12,7 @@ import io.littlehorse.common.proto.Command; import io.littlehorse.sdk.common.proto.LHHostInfo; import io.littlehorse.server.LHServer; -import io.littlehorse.server.auth.InternalCallCredentials; +import io.littlehorse.server.auth.internalport.InternalCallCredentials; import io.littlehorse.server.streams.ServerTopology; import io.littlehorse.server.streams.storeinternals.GetableManager; import io.littlehorse.server.streams.storeinternals.ReadOnlyMetadataManager; diff --git a/server/src/test/java/io/littlehorse/server/auth/RequestSanitizerTest.java b/server/src/test/java/io/littlehorse/server/auth/InsecureAuthenticatorTest.java similarity index 66% rename from server/src/test/java/io/littlehorse/server/auth/RequestSanitizerTest.java rename to server/src/test/java/io/littlehorse/server/auth/InsecureAuthenticatorTest.java index a830ac0b5..3c021628a 100644 --- a/server/src/test/java/io/littlehorse/server/auth/RequestSanitizerTest.java +++ b/server/src/test/java/io/littlehorse/server/auth/InsecureAuthenticatorTest.java @@ -12,15 +12,17 @@ import io.grpc.ServerInterceptors; import io.grpc.ServerMethodDefinition; import io.grpc.ServerServiceDefinition; +import io.littlehorse.common.LHConstants; import io.littlehorse.sdk.common.proto.LittleHorseGrpc; +import io.littlehorse.server.auth.authenticators.InsecureAuthenticator; import java.util.Collection; import lombok.Getter; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -class RequestSanitizerTest { +class InsecureAuthenticatorTest { - private RequestSanitizer sanitizer = new RequestSanitizer(); + private InsecureAuthenticator authenticator = new InsecureAuthenticator(); private TrackableInterceptor trackableInterceptor = new TrackableInterceptor(); private final ServerServiceDefinition testServiceDefinition = buildTestServiceDefinition( ServerServiceDefinition.builder(LittleHorseGrpc.getServiceDescriptor()), @@ -30,19 +32,30 @@ class RequestSanitizerTest { private final Metadata requestHeaders = new Metadata(); @Test - public void shouldRemoveInternalHeader() { - requestHeaders.put(ServerAuthorizer.CLIENT_ID, "root"); - requestHeaders.put(ServerAuthorizer.TENANT_ID, "my-tenant"); + public void shouldSetPrincipalToAnonymousWhenProvidedByUser() { + requestHeaders.put(LHServerInterceptor.CLIENT_ID, "root"); + requestHeaders.put(LHServerInterceptor.TENANT_ID, "my-tenant"); startCall(); Metadata resolvedHeaders = trackableInterceptor.getHeaders(); Assertions.assertThat(resolvedHeaders).isNotNull(); - Assertions.assertThat(resolvedHeaders.get(ServerAuthorizer.CLIENT_ID)).isNull(); - Assertions.assertThat(resolvedHeaders.get(ServerAuthorizer.TENANT_ID)).isEqualTo("my-tenant"); + Assertions.assertThat(resolvedHeaders.get(LHServerInterceptor.CLIENT_ID)) + .isEqualTo(LHConstants.ANONYMOUS_PRINCIPAL); + Assertions.assertThat(resolvedHeaders.get(LHServerInterceptor.TENANT_ID)) + .isEqualTo("my-tenant"); + } + + @Test + public void shouldSetPrincipalToAnonymous() { + startCall(); + Metadata resolvedHeaders = trackableInterceptor.getHeaders(); + Assertions.assertThat(resolvedHeaders).isNotNull(); + Assertions.assertThat(resolvedHeaders.get(LHServerInterceptor.CLIENT_ID)) + .isEqualTo(LHConstants.ANONYMOUS_PRINCIPAL); } private void startCall() { ServerServiceDefinition intercept = - ServerInterceptors.intercept(testServiceDefinition, sanitizer, trackableInterceptor); + ServerInterceptors.intercept(testServiceDefinition, authenticator, trackableInterceptor); @SuppressWarnings("unchecked") ServerMethodDefinition def = (ServerMethodDefinition) Iterables.get(intercept.getMethods(), 0); @@ -58,7 +71,7 @@ private ServerServiceDefinition buildTestServiceDefinition( } @Getter - private static class TrackableInterceptor implements ServerAuthorizer { + private static class TrackableInterceptor implements LHServerInterceptor { private Metadata headers; @Override diff --git a/server/src/test/java/io/littlehorse/server/auth/RequestAuthorizerTest.java b/server/src/test/java/io/littlehorse/server/auth/RequestAuthorizerTest.java index 5676fc808..74d7efd04 100644 --- a/server/src/test/java/io/littlehorse/server/auth/RequestAuthorizerTest.java +++ b/server/src/test/java/io/littlehorse/server/auth/RequestAuthorizerTest.java @@ -89,7 +89,7 @@ private void startCall() { @Test public void supportAnonymousPrincipalForDefaultTenant() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn(null); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn(null); startCall(); assertThat(resolvedAuthContext).isNotNull(); @@ -102,7 +102,7 @@ public void supportAnonymousPrincipalForDefaultTenant() { @Test public void supportAnonymousPrincipalWhenClientIdIsNotFound() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("principal-id"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("principal-id"); startCall(); assertThat(resolvedAuthContext.principalId().getId()).isEqualTo(LHConstants.ANONYMOUS_PRINCIPAL); assertThat(resolvedAuthContext.acls()) @@ -111,8 +111,8 @@ public void supportAnonymousPrincipalWhenClientIdIsNotFound() { @Test public void supportPrincipalForSpecificTenant() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("principal-id"); - when(mockMetadata.get(ServerAuthorizer.TENANT_ID)).thenReturn("my-tenant"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("principal-id"); + when(mockMetadata.get(LHServerInterceptor.TENANT_ID)).thenReturn("my-tenant"); PrincipalModel newPrincipal = new PrincipalModel(); newPrincipal.setId(new PrincipalIdModel("principal-id")); newPrincipal.setGlobalAcls(TestUtil.singleAdminAcl("name")); @@ -126,8 +126,8 @@ public void supportPrincipalForSpecificTenant() { @Test public void supportPermissionDeniedForNonExistingTenants() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("principal-id"); - when(mockMetadata.get(ServerAuthorizer.TENANT_ID)).thenReturn("my-missing-tenant"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("principal-id"); + when(mockMetadata.get(LHServerInterceptor.TENANT_ID)).thenReturn("my-missing-tenant"); PrincipalModel newPrincipal = new PrincipalModel(); newPrincipal.setId(new PrincipalIdModel("principal-id")); newPrincipal.setGlobalAcls(TestUtil.singleAdminAcl("name")); @@ -138,7 +138,7 @@ public void supportPermissionDeniedForNonExistingTenants() { @Test public void supportAnonymousPrincipalWhenPrincipalIdIsNotFound() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("principal-id"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("principal-id"); metadataManager.put(new TenantModel("my-tenant")); startCall(); assertThat(resolvedAuthContext.principalId().getId()).isEqualTo(LHConstants.ANONYMOUS_PRINCIPAL); @@ -164,7 +164,7 @@ public void setup() { @Test public void supportRequestAuthorizationForAdminPrincipals() { - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("admin-principal"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("admin-principal"); startCall(); assertThat(resolvedAuthContext).isNotNull(); } @@ -174,7 +174,7 @@ public void supportRequiredAclValidation() { MethodDescriptor mockMethod = mock(); when(mockCall.getMethodDescriptor()).thenReturn(mockMethod); when(mockMethod.getBareMethodName()).thenReturn("PutTaskDef"); - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("limited-principal"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("limited-principal"); startCall(); Mockito.verify(mockCall).close(any(), eq(mockMetadata)); } @@ -184,7 +184,7 @@ public void shouldRejectTenantResourceRequestFromTenantAdminPrincipal() { MethodDescriptor mockMethod = mock(); when(mockCall.getMethodDescriptor()).thenReturn(mockMethod); when(mockMethod.getBareMethodName()).thenReturn("PutTenant"); - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("tenant-admin-principal"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("tenant-admin-principal"); startCall(); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); Mockito.verify(mockCall).close(statusCaptor.capture(), eq(mockMetadata)); @@ -199,7 +199,7 @@ public void shouldRejectPrincipalResourceRequestFromTenantAdminPrincipal() { MethodDescriptor mockMethod = mock(); when(mockCall.getMethodDescriptor()).thenReturn(mockMethod); when(mockMethod.getBareMethodName()).thenReturn("PutPrincipal"); - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("tenant-admin-principal"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("tenant-admin-principal"); startCall(); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); Mockito.verify(mockCall).close(statusCaptor.capture(), eq(mockMetadata)); @@ -214,8 +214,8 @@ public void supportTenantAdmins() { MethodDescriptor mockMethod = mock(); when(mockCall.getMethodDescriptor()).thenReturn(mockMethod); when(mockMethod.getBareMethodName()).thenReturn("PutTaskDef"); - when(mockMetadata.get(ServerAuthorizer.CLIENT_ID)).thenReturn("tenant-admin-principal"); - when(mockMetadata.get(ServerAuthorizer.TENANT_ID)).thenReturn("my-tenant"); + when(mockMetadata.get(LHServerInterceptor.CLIENT_ID)).thenReturn("tenant-admin-principal"); + when(mockMetadata.get(LHServerInterceptor.TENANT_ID)).thenReturn("my-tenant"); startCall(); assertThat(resolvedAuthContext).isNotNull(); } @@ -261,7 +261,7 @@ public MethodDescriptor getMethodDescriptor() { } }; final Metadata mockMetadata = new Metadata(); - mockMetadata.put(ServerAuthorizer.CLIENT_ID, principalId); + mockMetadata.put(LHServerInterceptor.CLIENT_ID, principalId); PrincipalModel newPrincipal = new PrincipalModel(); newPrincipal.setId(new PrincipalIdModel(principalId)); newPrincipal.setGlobalAcls(TestUtil.singleAdminAcl("name")); @@ -313,7 +313,7 @@ private ServerServiceDefinition buildContextPropagationVerifierServiceDefinition definitionBuilder = definitionBuilder.addMethod(method, (call, headers) -> { String principalId = contextKey.get().authorization().principalId().toString(); - assertThat(principalId).isEqualTo(headers.get(ServerAuthorizer.CLIENT_ID)); + assertThat(principalId).isEqualTo(headers.get(LHServerInterceptor.CLIENT_ID)); return new NoopServerCall.NoopServerCallListener<>(); }); } diff --git a/test-utils/src/main/java/io/littlehorse/test/internal/TestBootstrapper.java b/test-utils/src/main/java/io/littlehorse/test/internal/TestBootstrapper.java index f89c0a967..6416ca5dd 100644 --- a/test-utils/src/main/java/io/littlehorse/test/internal/TestBootstrapper.java +++ b/test-utils/src/main/java/io/littlehorse/test/internal/TestBootstrapper.java @@ -6,7 +6,7 @@ import io.littlehorse.common.model.getable.objectId.TenantIdModel; import io.littlehorse.sdk.common.config.LHConfig; import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub; -import io.littlehorse.server.auth.ServerAuthorizer; +import io.littlehorse.server.auth.LHServerInterceptor; import java.util.concurrent.Executor; public interface TestBootstrapper { @@ -28,7 +28,7 @@ public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, Met try { Metadata headers = new Metadata(); if (tenantId != null && tenantId.getId() != null) { - headers.put(ServerAuthorizer.TENANT_ID, tenantId.getId()); + headers.put(LHServerInterceptor.TENANT_ID, tenantId.getId()); metadataApplier.apply(headers); } } catch (Exception e) {