Skip to content

Commit

Permalink
Merge branch 'dashboard-wf-ux' of https://github.com/littlehorse-ente…
Browse files Browse the repository at this point in the history
…rprises/littlehorse into dashboard-wf-ux
  • Loading branch information
HazimAr committed Nov 25, 2024
2 parents ab25772 + eaf337f commit 356f6d0
Show file tree
Hide file tree
Showing 21 changed files with 198 additions and 131 deletions.
2 changes: 1 addition & 1 deletion local-dev/issue-certificates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand Down
23 changes: 10 additions & 13 deletions server/src/main/java/io/littlehorse/common/LHServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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");
}
Expand All @@ -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);
Expand Down
6 changes: 2 additions & 4 deletions server/src/main/java/io/littlehorse/server/LHServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,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;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import io.grpc.ServerInterceptor;
import io.littlehorse.common.LHConstants;

public interface ServerAuthorizer extends ServerInterceptor {
/**
* Wrapper over io.grpc.ServerInterceptor. Doesn't do anything other than put
* a few constants into scope without imports in order to reduce the scope of
* how we expose the `Metadata.Key` stuff.
*/
public interface LHServerInterceptor extends ServerInterceptor {

String INTERNAL_PREFIX = "_";

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RequestAuthorizer implements ServerAuthorizer {
public class RequestAuthorizer implements LHServerInterceptor {

private final CoreStoreProvider coreStoreProvider;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 <ReqT, RespT> Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {

headers.put(CLIENT_ID, LHConstants.ANONYMOUS_PRINCIPAL);
return next.startCall(call, headers);
}

public static InsecureAuthenticator create() {
return new InsecureAuthenticator();
}
}
Original file line number Diff line number Diff line change
@@ -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 <ReqT, RespT> Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> 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<String> 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<>() {};
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String> AUTHORIZATION_HEADER_KEY =
Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.littlehorse.server.auth;
package io.littlehorse.server.auth.internalport;

import io.grpc.Context;
import io.grpc.Contexts;
Expand All @@ -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<RequestExecutionContext> executionContextKey;
private final CoreStoreProvider coreStoreProvider;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Loading

0 comments on commit 356f6d0

Please sign in to comment.