Creating a list of public endpoints that anonymous users can access.

OPA controls access to all endpoints and the list of authenticated resources and public URLs is defined in a single place in that file.

The url_allow function in acl.rego is an overloaded function that replicates the OR condition in Rego. Either the user is authenticated and has permissions to access those resources, or the URL is public and accessible by any user.
This commit is contained in:
Arpit Mohan 2019-12-17 09:28:59 +05:30
parent 3e109d4d28
commit bcba9d3415
5 changed files with 31 additions and 7 deletions

View File

@ -39,20 +39,26 @@ public class AclService {
this.groupService = groupService;
}
public Mono<OpaResponse> evaluateAcl(HttpMethod httpMethod, String resource) {
public Mono<OpaResponse> evaluateAcl(HttpMethod httpMethod, String resource, String requestUrl) {
JSONObject requestBody = new JSONObject();
JSONObject input = new JSONObject();
JSONObject jsonUser = new JSONObject();
input.put("user", jsonUser);
input.put("method", httpMethod.name());
input.put("resource", resource);
input.put("url", requestUrl); // The url is required for OPA to gate keep only public URLs for anonymous users
requestBody.put("input", input);
Mono<User> user = sessionUserService.getCurrentUser();
return user
.map(u -> {
// This is when the user doesn't have an existing session. The user is anonymous
.switchIfEmpty(Mono.defer(() -> {
User anonymous = new User();
return Mono.just(anonymous);
}))
.flatMap(u -> {
Set<String> globalPermissions = new HashSet<>();
Set<String> groupSet = u.getGroupIds();
globalPermissions.addAll(u.getPermissions());
@ -62,7 +68,6 @@ public class AclService {
.collectList()
.thenReturn(globalPermissions);
})
.flatMap(obj -> obj)
.flatMap(permissions -> {
jsonUser.put("permissions", permissions);
String finalUrl = url + pkgName;

View File

@ -82,6 +82,8 @@ public class SecurityConfig {
.cors().and()
.csrf().disable()
.authorizeExchange()
// All public URLs that should be served to anonymous users should be defined in acl.rego file
// This list of matchers is only the list of URLs that shouldn't return 401 unauthorized
.matchers(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/login"))
.permitAll()
.pathMatchers("/public/**").permitAll()

View File

@ -44,7 +44,8 @@ public class AclFilter implements WebFilter {
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpMethod httpMethod = request.getMethod();
String[] urlParts = request.getPath().value().split("/");
String url = request.getPath().value();
String[] urlParts = url.split("/");
// This is because all the urls are of the form /api/v1/{resource}. When we split by "/", the resource is always
// the 4th element in the result array
@ -55,7 +56,7 @@ public class AclFilter implements WebFilter {
String resource = urlParts[3];
Mono<OpaResponse> aclResponse = aclService.evaluateAcl(httpMethod, resource);
Mono<OpaResponse> aclResponse = aclService.evaluateAcl(httpMethod, resource, url);
return aclResponse
.map(acl -> {
log.debug("Got ACL response: {}", acl);

View File

@ -5,18 +5,34 @@ default resource_allow = false
# This rule allows the user to access endpoints based on the permissions that they are assigned.
# The httpMethod and resource are also an integral part of accessing this ACL
# We overload the rule url_allow thereby making it an OR condition. Either the user is able to access the resource
# via their authenticated session, or it's a public url that is accessible to everybody.
url_allow = true {
op = allowed_operations[_]
op = authenticated_operations[_]
input.method = op.method
input.resource = op.resource
p = input.user.permissions[_]
p = op.permission
}
url_allow = true {
op = public_operations[_]
input.url = op.url
input.method = op.method
}
# All public URLs must go into this list. Anything not in this list requires an authenticated session to access
public_operations = [
{"method" : "POST", "url" : "/api/v1/users/forgotPassword" },
{"method" : "POST", "url" : "/api/v1/users" },
{"method" : "POST", "url" : "/api/v1/users/verifyPasswordResetToken" },
{"method" : "POST", "url" : "/api/v1/users/resetPassword" },
]
# This is a global list of all the routes for all controllers. Any new controller that is written must
# carry an entry in this array. OPA performs ACL based on an intersection of these entries and permissions
# for a user + permissions inherited via the groups that the user is a part of.
allowed_operations = [
authenticated_operations = [
{"method": "POST", "resource": "users", "permission": "create:users"},
{"method": "GET", "resource": "users", "permission": "read:users"},
{"method": "PUT", "resource": "users", "permission": "update:users"},