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

Feature / Common concerns framework for gRPC middleware #503

Merged
merged 29 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
078bec0
Metadata classes for storing request and user metadata related to the…
martin-traverse Jan 23, 2025
4edf1b7
Intercept to add request metadata to incoming calls
martin-traverse Jan 23, 2025
bb75d4d
Use new request and user metadata in the metadata write service
martin-traverse Jan 23, 2025
358890f
User new user metadata in logging interceptor
martin-traverse Jan 23, 2025
ad0d129
Set Java proto class names for API proto files
martin-traverse Jan 27, 2025
a17b868
Set Java proto class names for internal API proto files
martin-traverse Jan 27, 2025
3f79e2b
Update proto class names in service validators
martin-traverse Jan 27, 2025
28b69f5
Update proto file names in gateway builders
martin-traverse Jan 27, 2025
34f6e0a
Update proto file names in meta and orch service classes
martin-traverse Jan 27, 2025
2e4d295
Update proto file names in data service class
martin-traverse Jan 27, 2025
0043646
Interfaces for providing common concerns in gRPC services and the gat…
martin-traverse Jan 27, 2025
902147d
Allow extensions to add common concerns
martin-traverse Jan 27, 2025
b876ad2
Implement common service config as a set of common concerns
martin-traverse Jan 27, 2025
b39a255
Use new common concerns framework in data svc, service class impls
martin-traverse Jan 27, 2025
4b65cc3
Use common concerns in data API impl
martin-traverse Jan 27, 2025
dedd361
Rename validation interceptor
martin-traverse Jan 27, 2025
1350945
Provide validation as a gRPC concern
martin-traverse Jan 27, 2025
43195e4
Use common concerns to set up gRPC interceptors in the data service
martin-traverse Jan 27, 2025
6bb9d56
Fix registering extensions in platform test
martin-traverse Jan 27, 2025
812dc42
Use new common concerns in the metadata service
martin-traverse Jan 27, 2025
36521a3
Make data service use the same common concern config as metadata service
martin-traverse Jan 27, 2025
e684e0c
Make key state classes serializable
martin-traverse Jan 27, 2025
463bfb9
Allow restoring config state for grpc client call concerns
martin-traverse Jan 27, 2025
51113fd
Support restoring client state for auth concern and the top level com…
martin-traverse Jan 27, 2025
3578b31
Use new common concerns framework for job processing in the orchestrator
martin-traverse Jan 27, 2025
52e25dd
Set up common concerns in the orchestrator service and API classes
martin-traverse Jan 27, 2025
48d4c66
Fix logging of user metadata
martin-traverse Jan 27, 2025
2f6fcfa
Use common concerns for client config in orch service
martin-traverse Jan 27, 2025
da95e02
use service class instead of API class for core concerns setup
martin-traverse Jan 27, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ syntax = "proto3";
package tracdap.api;

option java_package = "org.finos.tracdap.api";
option java_outer_classname = "DataServiceProto";
option java_multiple_files = true;

import "tracdap/metadata/object_id.proto";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ syntax = "proto3";
package tracdap.api;

option java_package = "org.finos.tracdap.api";
option java_outer_classname = "ErrorDetailsProto";
option java_multiple_files = true;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ syntax = "proto3";
package tracdap.api.internal;

option java_package = "org.finos.tracdap.api.internal";
option java_outer_classname = "MetadataTrustedProto";
option java_multiple_files = true;

import "tracdap/metadata/object_id.proto";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package tracdap.api.internal;

option java_package = "org.finos.tracdap.api.internal";
option java_outer_classname = "RuntimeServiceProto";
option java_multiple_files = true;

import "tracdap/metadata/object_id.proto";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ syntax = "proto3";
package tracdap.api;

option java_package = "org.finos.tracdap.api";
option java_outer_classname = "MetadataServiceProto";
option java_multiple_files = true;

import "tracdap/metadata/common.proto";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ syntax = "proto3";
package tracdap.api;

option java_package = "org.finos.tracdap.api";
option java_outer_classname = "OrchestratorServiceProto";
option java_multiple_files = true;

import "tracdap/metadata/object_id.proto";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.finos.tracdap.common.auth;

import io.grpc.Context;
import org.finos.tracdap.common.exception.EAuthorization;


Expand All @@ -42,6 +43,26 @@ public static UserInfo currentUser() {
return currentAuthUser();
}

public static UserInfo currentAuthUser(Context callContext) {

var authUser = AuthConstants.TRAC_AUTH_USER_KEY.get(callContext);

if (authUser != null)
return authUser;

throw new EAuthorization("User details are not available");
}

public static UserInfo currentUser(Context callContext) {

var delegate = AuthConstants.TRAC_DELEGATE_KEY.get(callContext);

if (delegate != null)
return delegate;

return currentAuthUser(callContext);
}

public static String printCurrentUser() {

var authUser = AuthConstants.TRAC_AUTH_USER_KEY.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.finos.tracdap.common.auth;

import org.finos.tracdap.common.exception.EStartup;
import org.finos.tracdap.common.grpc.UserMetadata;
import org.finos.tracdap.config.AuthenticationConfig;

import io.grpc.*;
Expand Down Expand Up @@ -62,7 +63,11 @@ public GrpcAuthValidator(AuthenticationConfig authConfig, JwtValidator jwt) {
userInfo.setUserId(AUTH_DISABLED_USER_ID);
userInfo.setDisplayName(AUTH_DISABLED_USER_NAME);

var ctx = Context.current()
var userMetadata = new UserMetadata(
userInfo.getUserId(), userInfo.getDisplayName());

var ctx = UserMetadata
.set(Context.current(), userMetadata)
.withValue(AuthConstants.TRAC_AUTH_USER_KEY, userInfo);

return Contexts.interceptCall(ctx, call, headers, next);
Expand Down Expand Up @@ -109,6 +114,10 @@ public GrpcAuthValidator(AuthenticationConfig authConfig, JwtValidator jwt) {
var userInfo = sessionInfo.getUserInfo();
var delegate = sessionInfo.getDelegate();

var userMetadata = delegate == null
? new UserMetadata(userInfo.getUserId(), userInfo.getDisplayName())
: new UserMetadata(userInfo.getUserId(), userInfo.getDisplayName(), delegate.getUserId(), delegate.getDisplayName());

if (authConfig.getDisableSigning()) {
log.warn("AUTHENTICATE: {}() [{}] SUCCEEDED WITHOUT VALIDATION",
call.getMethodDescriptor().getBareMethodName(),
Expand All @@ -122,7 +131,8 @@ public GrpcAuthValidator(AuthenticationConfig authConfig, JwtValidator jwt) {

// Auth complete, put details into the current call context

var ctx = Context.current()
var ctx = UserMetadata
.set(Context.current(), userMetadata)
.withValue(AuthConstants.TRAC_AUTH_USER_KEY, userInfo)
.withValue(AuthConstants.TRAC_DELEGATE_KEY, delegate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@

package org.finos.tracdap.common.auth;

import java.io.Serializable;
import java.time.Instant;

public class SessionInfo {
public class SessionInfo implements Serializable {

private final static long serialVersionUID = 1L;

private boolean valid;
private String errorMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
package org.finos.tracdap.common.grpc;

import io.grpc.*;
import org.finos.tracdap.common.auth.GrpcAuthHelpers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class LoggingServerInterceptor implements ServerInterceptor {

private final Logger log;
Expand All @@ -41,13 +41,13 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(

log.info("API CALL START: {}() [{}]",
method.getBareMethodName(),
GrpcAuthHelpers.printCurrentUser());
logCurrentUser());
}
else {

log.info("API CALL START: {}() [{}] ({})",
method.getBareMethodName(),
GrpcAuthHelpers.printCurrentUser(),
logCurrentUser(),
method.getType());
}

Expand Down Expand Up @@ -94,4 +94,18 @@ else if (status.getCause() != null) {
delegate().close(status, trailers);
}
}

private String logCurrentUser() {

var userMetadata = UserMetadata.get(Context.current());

if (userMetadata.hasDelegate())
return String.format("%s <%s> on behalf of %s <%s>",
userMetadata.userName(), userMetadata.userId(),
userMetadata.delegateName(), userMetadata.delegateId());

else
return String.format("%s <%s>",
userMetadata.userName(), userMetadata.userId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Fintech Open Source Foundation (FINOS) under one or
* more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* FINOS licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.finos.tracdap.common.grpc;

import io.grpc.Context;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;


/**
* Provide a mechanism for passing request metadata into gRPC service calls.
* <br/>
*
* In the default configuration, TRAC will set request metadata for each incoming request.
* Service extensions can override or add to request metadata using custom interceptors.
*/
public class RequestMetadata implements Serializable {

private static final long serialVersionUID = 1L;

public static final String UNKNOWN_REQUEST_ID = "#unknown_request_id";
public static final OffsetDateTime UNKNOWN_REQUEST_TIMESTAMP = Instant.EPOCH.atOffset(ZoneOffset.UTC);

private static final RequestMetadata REQUEST_METADATA_NOT_SET = new RequestMetadata(UNKNOWN_REQUEST_ID, UNKNOWN_REQUEST_TIMESTAMP);
private static final Context.Key<RequestMetadata> REQUEST_METADATA_KEY = Context.keyWithDefault("trac-request-metadata", REQUEST_METADATA_NOT_SET);

public static Context set(Context context, RequestMetadata requestMetadata) {

return context.withValue(REQUEST_METADATA_KEY, requestMetadata);
}

public static RequestMetadata get(Context context) {

return REQUEST_METADATA_KEY.get(context);
}

private final String requestId;
private final OffsetDateTime requestTimestamp;

public RequestMetadata(@Nonnull String requestId, @Nonnull OffsetDateTime requestTimestamp) {
this.requestId = requestId;
this.requestTimestamp = requestTimestamp;
}

public String requestId() {
return requestId;
}

public OffsetDateTime requestTimestamp() {
return requestTimestamp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to the Fintech Open Source Foundation (FINOS) under one or
* more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* FINOS licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.finos.tracdap.common.grpc;

import io.grpc.*;

import java.time.Instant;
import java.time.ZoneOffset;
import java.util.UUID;


public class RequestMetadataInterceptor implements ServerInterceptor {

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT>
interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {

var requestId = UUID.randomUUID().toString();
var requestTimestamp = Instant.now().atOffset(ZoneOffset.UTC);
var requestMetadata = new RequestMetadata(requestId, requestTimestamp);

var context = RequestMetadata.set(Context.current(), requestMetadata);

return Contexts.interceptCall(context, call, headers, next);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Fintech Open Source Foundation (FINOS) under one or
* more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* FINOS licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.finos.tracdap.common.grpc;

import io.grpc.Context;

import javax.annotation.Nonnull;
import java.io.Serializable;


/**
* Provide a mechanism for passing user metadata into gRPC service calls.
* <br/>
*
* Service extensions can set user info to make it available inside service calls.
* This is intended for passing user info to the TRAC metadata store, or
* to add user annotations to jobs, service logs etc. It is not intended
* as a way to manage authentication or access control.
*/
public class UserMetadata implements Serializable {

private static final long serialVersionUID = 1L;

public static final String UNKNOWN_USER_ID = "#trac_unknown";
public static final String UNKNOWN_USER_NAME = "Unknown User";

private static final UserMetadata USER_METADATA_NOT_SET = new UserMetadata(UNKNOWN_USER_ID, UNKNOWN_USER_NAME);
private static final Context.Key<UserMetadata> USER_METADATA_KEY = Context.keyWithDefault("trac-user-metadata", USER_METADATA_NOT_SET);

public static Context set(Context context, UserMetadata userMetadata) {

return context.withValue(USER_METADATA_KEY, userMetadata);
}

public static UserMetadata get(Context context) {

return USER_METADATA_KEY.get(context);
}

private final String userId;
private final String userName;
private final String delegateId;
private final String delegateName;

public UserMetadata(@Nonnull String userId, @Nonnull String userName) {
this.userId = userId;
this.userName = userName;
this.delegateId = null;
this.delegateName = null;
}

public UserMetadata(@Nonnull String userId, @Nonnull String userName, @Nonnull String delegateId, @Nonnull String delegateName) {
this.userId = userId;
this.userName = userName;
this.delegateId = delegateId;
this.delegateName = delegateName;
}

public String userId() {
return userId;
}

public String userName() {
return userName;
}

public boolean hasDelegate() {
return delegateId != null;
}

public String delegateId() {
return delegateId;
}

public String delegateName() {
return delegateName;
}
}
Loading
Loading