Skip to content

Commit 83d3a65

Browse files
committed
Replace Keto Auth with external HTTP Auth
1 parent bc7f3fb commit 83d3a65

8 files changed

Lines changed: 294 additions & 117 deletions

File tree

auth/pom.xml

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<artifactId>feast-parent</artifactId>
88
<version>${revision}</version>
99
</parent>
10+
<properties>
11+
<external.auth.client.package.name>feast.auth.generated.client</external.auth.client.package.name>
12+
</properties>
1013
<artifactId>feast-auth</artifactId>
1114

1215
<name>Feast Authentication and Authorization</name>
@@ -32,11 +35,6 @@
3235
<artifactId>spring-security-oauth2-jose</artifactId>
3336
<version>5.3.0.RELEASE</version>
3437
</dependency>
35-
<dependency>
36-
<groupId>sh.ory.keto</groupId>
37-
<artifactId>keto-client</artifactId>
38-
<version>0.4.4-alpha.1</version>
39-
</dependency>
4038
<dependency>
4139
<groupId>org.projectlombok</groupId>
4240
<artifactId>lombok</artifactId>
@@ -46,6 +44,48 @@
4644
<artifactId>hibernate-validator</artifactId>
4745
<version>6.1.2.Final</version>
4846
</dependency>
47+
<dependency>
48+
<groupId>com.fasterxml.jackson.core</groupId>
49+
<artifactId>jackson-databind</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>junit</groupId>
53+
<artifactId>junit</artifactId>
54+
</dependency>
4955
</dependencies>
56+
<build>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.openapitools</groupId>
60+
<artifactId>openapi-generator-maven-plugin</artifactId>
61+
<version>4.3.1</version>
62+
<executions>
63+
<execution>
64+
<goals>
65+
<goal>generate</goal>
66+
</goals>
67+
<configuration>
68+
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
69+
<generatorName>java</generatorName>
70+
<packageName>${external.auth.client.package.name}</packageName>
71+
<modelPackage>${external.auth.client.package.name}.model</modelPackage>
72+
<apiPackage>${external.auth.client.package.name}.api</apiPackage>
73+
<invokerPackage>${external.auth.client.package.name}.invoker</invokerPackage>
74+
<configOptions>
75+
<groupId>${project.groupId}</groupId>
76+
<artifactId>${project.artifactId}</artifactId>
77+
<artifactVersion>${project.version}</artifactVersion>
78+
<java8>true</java8>
79+
<dateLibrary>java8</dateLibrary>
80+
<licenseName>Apache 2.0</licenseName>
81+
<licenseUrl>https://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
82+
<output>${project.build.directory}/generated-sources</output>
83+
</configOptions>
84+
</configuration>
85+
</execution>
86+
</executions>
87+
</plugin>
88+
</plugins>
89+
</build>
5090

5191
</project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.authorization;
18+
19+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
20+
import com.fasterxml.jackson.annotation.PropertyAccessor;
21+
import com.fasterxml.jackson.core.JsonProcessingException;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.databind.ObjectWriter;
24+
import java.util.Map;
25+
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
26+
import org.springframework.security.core.Authentication;
27+
import org.springframework.security.oauth2.jwt.Jwt;
28+
29+
public class AuthUtil {
30+
31+
/**
32+
* Get user email from their authentication object.
33+
*
34+
* @param authentication Spring Security Authentication object, used to extract user details
35+
* @return String user email
36+
*/
37+
public static String getEmailFromAuth(Authentication authentication) {
38+
Jwt principle = ((Jwt) authentication.getPrincipal());
39+
Map<String, Object> claims = principle.getClaims();
40+
String email = (String) claims.get("email");
41+
42+
if (email.isEmpty()) {
43+
throw new IllegalStateException("JWT does not have a valid email set.");
44+
}
45+
boolean validEmail = (new EmailValidator()).isValid(email, null);
46+
if (!validEmail) {
47+
throw new IllegalStateException("JWT contains an invalid email address");
48+
}
49+
return email;
50+
}
51+
52+
/**
53+
* Converts Spring Authentication object into Json String form.
54+
*
55+
* @param authentication Authentication object that contains request level authentication metadata
56+
* @return Json representation of authentication object
57+
*/
58+
public static String authenticationToJson(Authentication authentication) {
59+
ObjectWriter ow =
60+
new ObjectMapper()
61+
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
62+
.writer()
63+
.withDefaultPrettyPrinter();
64+
try {
65+
return ow.writeValueAsString(authentication);
66+
} catch (JsonProcessingException e) {
67+
throw new RuntimeException(
68+
String.format(
69+
"Could not convert Authentication object to JSON: %s", authentication.toString()));
70+
}
71+
}
72+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.authorization;
18+
19+
import static feast.auth.authorization.AuthUtil.getEmailFromAuth;
20+
21+
import feast.auth.generated.client.api.DefaultApi;
22+
import feast.auth.generated.client.invoker.ApiClient;
23+
import feast.auth.generated.client.invoker.ApiException;
24+
import feast.auth.generated.client.model.CheckProjectAccessRequest;
25+
import feast.auth.generated.client.model.CheckProjectAccessResponse;
26+
import java.util.Map;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.springframework.security.core.Authentication;
30+
31+
/** Authorization Provider implementation for external HTTP authorization server */
32+
public class HTTPAuthorizationProvider implements AuthorizationProvider {
33+
34+
private static final Logger log = LoggerFactory.getLogger(HTTPAuthorizationProvider.class);
35+
private final DefaultApi defaultApiClient;
36+
37+
/**
38+
* Initializes the HTTPAuthorizationProvider
39+
*
40+
* @param options String K/V pair of options to initialize the provider with. Expects at least a
41+
* "basePath" for the provider URL
42+
*/
43+
public HTTPAuthorizationProvider(Map<String, String> options) {
44+
if (options == null) {
45+
throw new IllegalArgumentException(
46+
"Cannot pass empty or null options to HTTPAuthorizationProvider");
47+
}
48+
49+
ApiClient apiClient = new ApiClient();
50+
apiClient.setBasePath(options.get("externalAuthUrl"));
51+
this.defaultApiClient = new DefaultApi(apiClient);
52+
}
53+
54+
/**
55+
* Validates whether a user has access to the project
56+
*
57+
* @param project Name of the Feast project
58+
* @param authentication Spring Security Authentication object
59+
* @return AuthorizationResult result of authorization query
60+
*/
61+
public AuthorizationResult checkAccess(String project, Authentication authentication) {
62+
String email = getEmailFromAuth(authentication);
63+
CheckProjectAccessRequest checkProjectAccessRequest =
64+
new CheckProjectAccessRequest().project(project).authentication(authentication);
65+
66+
try {
67+
// Make authorization request to external service
68+
CheckProjectAccessResponse response =
69+
defaultApiClient.checkProjectAccessPost(checkProjectAccessRequest);
70+
if (response == null || response.getAllowed() == null) {
71+
throw new RuntimeException(
72+
String.format(
73+
"Empty response returned for HTTP authorization, email %s, authentication %s",
74+
email, authentication.toString()));
75+
}
76+
if (response.getAllowed()) {
77+
// Successfully authenticated
78+
return AuthorizationResult.success();
79+
}
80+
// Could not determine project membership, deny access.
81+
return AuthorizationResult.failed(
82+
String.format(
83+
"Access denied to project %s for user %s with message %s",
84+
project, email, response.getMessage()));
85+
} catch (ApiException e) {
86+
log.error("API exception has occurred while authenticating user: {}", e.getMessage(), e);
87+
}
88+
89+
// Could not determine project membership, deny access.
90+
return AuthorizationResult.failed(
91+
String.format("Access denied to project %s for user %s", project, email));
92+
}
93+
}

auth/src/main/java/feast/auth/authorization/Keto/KetoAuthorizationProvider.java

Lines changed: 0 additions & 106 deletions
This file was deleted.

auth/src/main/java/feast/auth/config/SecurityConfig.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
import feast.auth.authentication.DefaultJwtAuthenticationProvider;
2020
import feast.auth.authorization.AuthorizationProvider;
21-
import feast.auth.authorization.Keto.KetoAuthorizationProvider;
21+
import feast.auth.authorization.HTTPAuthorizationProvider;
2222
import java.util.ArrayList;
2323
import java.util.List;
24+
import java.util.Map;
2425
import net.devh.boot.grpc.server.security.authentication.BearerAuthenticationReader;
2526
import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader;
2627
import net.devh.boot.grpc.server.security.check.AccessPredicateVoter;
@@ -107,8 +108,9 @@ AuthorizationProvider authorizationProvider() {
107108
if (securityProperties.getAuthentication().isEnabled()
108109
&& securityProperties.getAuthorization().isEnabled()) {
109110
switch (securityProperties.getAuthorization().getProvider()) {
110-
case "keto":
111-
return new KetoAuthorizationProvider(securityProperties.getAuthorization().getOptions());
111+
case "http":
112+
Map<String, String> options = securityProperties.getAuthorization().getOptions();
113+
return new HTTPAuthorizationProvider(options);
112114
default:
113115
throw new IllegalArgumentException(
114116
"Please configure an Authorization Provider if you have enabled authorization.");

auth/src/main/java/feast/auth/config/SecurityProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static class AuthorizationProperties {
5050
private boolean enabled;
5151

5252
// Named authorization provider to use.
53-
@OneOfStrings({"none", "keto"})
53+
@OneOfStrings({"none", "http"})
5454
private String provider;
5555

5656
// K/V options to initialize the provider with

0 commit comments

Comments
 (0)