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

Add Authentication and Authorization for feast serving #865

Merged
merged 8 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions serving/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>sh.ory.keto</groupId>
<artifactId>keto-client</artifactId>
<version>0.4.4-alpha.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import io.opentracing.Span;
import io.opentracing.Tracer;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import net.devh.boot.grpc.server.service.GrpcService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -83,11 +85,15 @@ public void getOnlineFeatures(
Span span = tracer.buildSpan("getOnlineFeatures").start();
try (Scope scope = tracer.scopeManager().activate(span, false)) {
// authorize for the project in request object.
this.authorizationService.authorizeRequest(
SecurityContextHolder.getContext(), request.getProject());
// authorize for projects set in feature list, backward compatibility for
// <=v0.5.X
this.checkProjectAccess(request.getFeaturesList());
if (request.getProject() != null && !request.getProject().isEmpty()) {
// project set at root level overrides the project set at feature set level
this.authorizationService.authorizeRequest(
SecurityContextHolder.getContext(), request.getProject());
} else {
// authorize for projects set in feature list, backward compatibility for
// <=v0.5.X
this.checkProjectAccess(request.getFeaturesList());
}
RequestHelper.validateOnlineRequest(request);
GetOnlineFeaturesResponse onlineFeatures = servingService.getOnlineFeatures(request);
responseObserver.onNext(onlineFeatures);
Expand Down Expand Up @@ -149,11 +155,17 @@ public void getJob(GetJobRequest request, StreamObserver<GetJobResponse> respons
}

private void checkProjectAccess(List<FeatureReference> featureList) {
featureList.stream()
.forEach(
featureRef -> {
this.authorizationService.authorizeRequest(
SecurityContextHolder.getContext(), featureRef.getProject());
});
Set<String> projectList =
featureList.stream().map(FeatureReference::getProject).collect(Collectors.toSet());
if (projectList.isEmpty()) {
authorizationService.authorizeRequest(SecurityContextHolder.getContext(), "default");
} else {
projectList.stream()
.forEach(
project -> {
this.authorizationService.authorizeRequest(
SecurityContextHolder.getContext(), project);
});
}
}
}
2 changes: 1 addition & 1 deletion serving/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ feast:
jwkEndpointURI: "https://www.googleapis.com/oauth2/v3/certs"
authorization:
enabled: false
provider: none
provider: http
options:
basePath: http://localhost:3000

Expand Down
72 changes: 72 additions & 0 deletions serving/src/test/java/feast/serving/it/AuthTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import io.grpc.ManagedChannelBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
Expand All @@ -51,9 +53,17 @@
import okhttp3.Response;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.runners.model.InitializationError;
import sh.ory.keto.ApiClient;
import sh.ory.keto.ApiException;
import sh.ory.keto.Configuration;
import sh.ory.keto.api.EnginesApi;
import sh.ory.keto.model.OryAccessControlPolicy;
import sh.ory.keto.model.OryAccessControlPolicyRole;

public class AuthTestUtils {

private static final String DEFAULT_FLAVOR = "glob";

static SourceProto.Source defaultSource =
createSource("kafka:9092,localhost:9094", "feast-features");

Expand Down Expand Up @@ -208,4 +218,66 @@ public static void seedHydra(
throw new InitializationError(response.message());
}
}

public static void seedKeto(String url, String project, String subjectInProject, String admin)
throws ApiException {
ApiClient ketoClient = Configuration.getDefaultApiClient();
ketoClient.setBasePath(url);
EnginesApi enginesApi = new EnginesApi(ketoClient);

// Add policies
OryAccessControlPolicy adminPolicy = getAdminPolicy();
enginesApi.upsertOryAccessControlPolicy(DEFAULT_FLAVOR, adminPolicy);

OryAccessControlPolicy projectPolicy = getMyProjectMemberPolicy(project);
enginesApi.upsertOryAccessControlPolicy(DEFAULT_FLAVOR, projectPolicy);

// Add policy roles
OryAccessControlPolicyRole adminPolicyRole = getAdminPolicyRole(admin);
enginesApi.upsertOryAccessControlPolicyRole(DEFAULT_FLAVOR, adminPolicyRole);

OryAccessControlPolicyRole myProjectMemberPolicyRole =
getMyProjectMemberPolicyRole(project, subjectInProject);
enginesApi.upsertOryAccessControlPolicyRole(DEFAULT_FLAVOR, myProjectMemberPolicyRole);
}

private static OryAccessControlPolicyRole getMyProjectMemberPolicyRole(
String project, String subjectInProject) {
OryAccessControlPolicyRole role = new OryAccessControlPolicyRole();
role.setId(String.format("roles:%s-project-members", project));
role.setMembers(Collections.singletonList("users:" + subjectInProject));
return role;
}

private static OryAccessControlPolicyRole getAdminPolicyRole(String subjectIsAdmin) {
OryAccessControlPolicyRole role = new OryAccessControlPolicyRole();
role.setId("roles:admin");
role.setMembers(Collections.singletonList("users:" + subjectIsAdmin));
return role;
}

private static OryAccessControlPolicy getAdminPolicy() {
OryAccessControlPolicy policy = new OryAccessControlPolicy();
policy.setId("policies:admin");
policy.subjects(Collections.singletonList("roles:admin"));
policy.resources(Collections.singletonList("resources:**"));
policy.actions(Collections.singletonList("actions:**"));
policy.effect("allow");
policy.conditions(null);
return policy;
}

private static OryAccessControlPolicy getMyProjectMemberPolicy(String project) {
OryAccessControlPolicy policy = new OryAccessControlPolicy();
policy.setId(String.format("policies:%s-project-members-policy", project));
policy.subjects(Collections.singletonList(String.format("roles:%s-project-members", project)));
policy.resources(
Arrays.asList(
String.format("resources:projects:%s", project),
String.format("resources:projects:%s:**", project)));
policy.actions(Collections.singletonList("actions:**"));
policy.effect("allow");
policy.conditions(null);
return policy;
}
}
6 changes: 1 addition & 5 deletions serving/src/test/java/feast/serving/it/BaseAuthIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
Expand All @@ -45,7 +43,6 @@ public class BaseAuthIT {
static final String CORE = "core_1";

static final String HYDRA = "hydra_1";
static final Map<String, String> options = new HashMap<>();
static final int HYDRA_PORT = 4445;

static CoreSimpleAPIClient insecureApiClient;
Expand All @@ -56,7 +53,7 @@ public class BaseAuthIT {
static final int FEAST_SERVING_PORT = 6566;

@DynamicPropertySource
static void initialize(DynamicPropertyRegistry registry) throws UnknownHostException {
static void properties(DynamicPropertyRegistry registry) {
registry.add("feast.stores[0].name", () -> "online");
registry.add("feast.stores[0].type", () -> "REDIS");
// Redis needs to accessible by both core and serving, hence using host address
Expand All @@ -66,7 +63,6 @@ static void initialize(DynamicPropertyRegistry registry) throws UnknownHostExcep
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.junit.ClassRule;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -52,6 +53,8 @@
@Testcontainers
public class ServingServiceOauthAuthenticationIT extends BaseAuthIT {

static final Map<String, String> options = new HashMap<>();

@ClassRule @Container
public static DockerComposeContainer environment =
new DockerComposeContainer(
Expand Down
Loading