authz: translate gRPC authz policy to Envoy RBAC proto (#8710)

This commit is contained in:
Ashitha Santhosh 2022-02-14 07:10:18 -08:00 committed by GitHub
parent bb3365731f
commit 1b88065f9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 851 additions and 0 deletions

77
authz/build.gradle Normal file
View File

@ -0,0 +1,77 @@
plugins {
id "java-library"
id "maven-publish"
id "com.github.johnrengelman.shadow"
id "com.google.protobuf"
id "ru.vyarus.animalsniffer"
}
description = "gRPC: Authorization"
dependencies {
implementation project(':grpc-protobuf'),
project(':grpc-core')
annotationProcessor libraries.autovalue
compileOnly libraries.javax_annotation
testImplementation project(':grpc-testing'),
project(':grpc-testing-proto')
testImplementation (libraries.guava_testlib) {
exclude group: 'junit', module: 'junit'
}
def xdsDependency = implementation project(':grpc-xds')
shadow configurations.implementation.getDependencies().minus([xdsDependency])
shadow project(path: ':grpc-xds', configuration: 'shadow')
signature "org.codehaus.mojo.signature:java17:1.0@signature"
}
jar {
classifier = 'original'
}
// TODO(ashithasantosh): Remove javadoc exclusion on adding authorization
// interceptor implementations.
javadoc {
exclude "io/grpc/authz/*"
}
shadowJar {
classifier = null
dependencies {
exclude(dependency {true})
}
relocate 'io.grpc.xds', 'io.grpc.xds.shaded.io.grpc.xds'
relocate 'udpa.annotations', 'io.grpc.xds.shaded.udpa.annotations'
relocate 'com.github.udpa', 'io.grpc.xds.shaded.com.github.udpa'
relocate 'envoy.annotations', 'io.grpc.xds.shaded.envoy.annotations'
relocate 'io.envoyproxy', 'io.grpc.xds.shaded.io.envoyproxy'
relocate 'com.google.api.expr', 'io.grpc.xds.shaded.com.google.api.expr'
}
publishing {
publications {
maven(MavenPublication) {
// We want this to throw an exception if it isn't working
def originalJar = artifacts.find { dep -> dep.classifier == 'original'}
artifacts.remove(originalJar)
pom.withXml {
def dependenciesNode = new Node(null, 'dependencies')
project.configurations.shadow.allDependencies.each { dep ->
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dep.group)
dependencyNode.appendNode('artifactId', dep.name)
dependencyNode.appendNode('version', dep.version)
dependencyNode.appendNode('scope', 'compile')
}
asNode().dependencies[0].replaceNode(dependenciesNode)
}
}
}
}
[publishMavenPublicationToMavenRepository]*.onlyIf {false}

View File

@ -0,0 +1,198 @@
/*
* Copyright 2021 The gRPC Authors
*
* Licensed 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 io.grpc.authz;
import com.google.common.collect.ImmutableList;
import io.envoyproxy.envoy.config.rbac.v3.Permission;
import io.envoyproxy.envoy.config.rbac.v3.Policy;
import io.envoyproxy.envoy.config.rbac.v3.Principal;
import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated;
import io.envoyproxy.envoy.config.rbac.v3.RBAC;
import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action;
import io.envoyproxy.envoy.config.route.v3.HeaderMatcher;
import io.envoyproxy.envoy.type.matcher.v3.PathMatcher;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import io.grpc.internal.JsonParser;
import io.grpc.internal.JsonUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Translates a gRPC authorization policy in JSON string to Envoy RBAC policies.
*/
class AuthorizationPolicyTranslator {
private static final ImmutableList<String> UNSUPPORTED_HEADERS = ImmutableList.of(
"host", "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
"te", "trailer", "transfer-encoding", "upgrade");
private static StringMatcher getStringMatcher(String value) {
if (value.equals("*")) {
return StringMatcher.newBuilder().setSafeRegex(
RegexMatcher.newBuilder().setRegex(".+").build()).build();
} else if (value.startsWith("*")) {
return StringMatcher.newBuilder().setSuffix(value.substring(1)).build();
} else if (value.endsWith("*")) {
return StringMatcher.newBuilder().setPrefix(value.substring(0, value.length() - 1)).build();
}
return StringMatcher.newBuilder().setExact(value).build();
}
private static Principal parseSource(Map<String, ?> source) {
List<String> principalsList = JsonUtil.getListOfStrings(source, "principals");
if (principalsList == null || principalsList.isEmpty()) {
return Principal.newBuilder().setAny(true).build();
}
Principal.Set.Builder principalsSet = Principal.Set.newBuilder();
for (String principal: principalsList) {
principalsSet.addIds(
Principal.newBuilder().setAuthenticated(
Authenticated.newBuilder().setPrincipalName(
getStringMatcher(principal)).build()).build());
}
return Principal.newBuilder().setOrIds(principalsSet.build()).build();
}
private static Permission parseHeader(Map<String, ?> header) throws IllegalArgumentException {
String key = JsonUtil.getString(header, "key");
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("\"key\" is absent or empty");
}
if (key.charAt(0) == ':'
|| key.startsWith("grpc-")
|| UNSUPPORTED_HEADERS.contains(key.toLowerCase())) {
throw new IllegalArgumentException(String.format("Unsupported \"key\" %s", key));
}
List<String> valuesList = JsonUtil.getListOfStrings(header, "values");
if (valuesList == null || valuesList.isEmpty()) {
throw new IllegalArgumentException("\"values\" is absent or empty");
}
Permission.Set.Builder orSet = Permission.Set.newBuilder();
for (String value: valuesList) {
orSet.addRules(
Permission.newBuilder().setHeader(
HeaderMatcher.newBuilder()
.setName(key)
.setStringMatch(getStringMatcher(value)).build()).build());
}
return Permission.newBuilder().setOrRules(orSet.build()).build();
}
private static Permission parseRequest(Map<String, ?> request) throws IllegalArgumentException {
Permission.Set.Builder andSet = Permission.Set.newBuilder();
List<String> pathsList = JsonUtil.getListOfStrings(request, "paths");
if (pathsList != null && !pathsList.isEmpty()) {
Permission.Set.Builder pathsSet = Permission.Set.newBuilder();
for (String path: pathsList) {
pathsSet.addRules(
Permission.newBuilder().setUrlPath(
PathMatcher.newBuilder().setPath(
getStringMatcher(path)).build()).build());
}
andSet.addRules(Permission.newBuilder().setOrRules(pathsSet.build()).build());
}
List<Map<String, ?>> headersList = JsonUtil.getListOfObjects(request, "headers");
if (headersList != null && !headersList.isEmpty()) {
Permission.Set.Builder headersSet = Permission.Set.newBuilder();
for (Map<String, ?> header: headersList) {
headersSet.addRules(parseHeader(header));
}
andSet.addRules(Permission.newBuilder().setAndRules(headersSet.build()).build());
}
if (andSet.getRulesCount() == 0) {
return Permission.newBuilder().setAny(true).build();
}
return Permission.newBuilder().setAndRules(andSet.build()).build();
}
private static Map<String, Policy> parseRules(
List<Map<String, ?>> objects, String name) throws IllegalArgumentException {
Map<String, Policy> policies = new LinkedHashMap<String, Policy>();
for (Map<String, ?> object: objects) {
String policyName = JsonUtil.getString(object, "name");
if (policyName == null || policyName.isEmpty()) {
throw new IllegalArgumentException("rule \"name\" is absent or empty");
}
List<Principal> principals = new ArrayList<>();
Map<String, ?> source = JsonUtil.getObject(object, "source");
if (source != null) {
principals.add(parseSource(source));
} else {
principals.add(Principal.newBuilder().setAny(true).build());
}
List<Permission> permissions = new ArrayList<>();
Map<String, ?> request = JsonUtil.getObject(object, "request");
if (request != null) {
permissions.add(parseRequest(request));
} else {
permissions.add(Permission.newBuilder().setAny(true).build());
}
Policy policy =
Policy.newBuilder()
.addAllPermissions(permissions)
.addAllPrincipals(principals)
.build();
policies.put(name + "_" + policyName, policy);
}
return policies;
}
/**
* Translates a gRPC authorization policy in JSON string to Envoy RBAC policies.
* On success, will return one of the following -
* 1. One allow RBAC policy or,
* 2. Two RBAC policies, deny policy followed by allow policy.
* If the policy cannot be parsed or is invalid, an exception will be thrown.
*/
public static List<RBAC> translate(String authorizationPolicy)
throws IllegalArgumentException, IOException {
Object jsonObject = JsonParser.parse(authorizationPolicy);
if (!(jsonObject instanceof Map)) {
throw new IllegalArgumentException(
"Authorization policy should be a JSON object. Found: "
+ (jsonObject == null ? null : jsonObject.getClass()));
}
@SuppressWarnings("unchecked")
Map<String, ?> json = (Map<String, ?>)jsonObject;
String name = JsonUtil.getString(json, "name");
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("\"name\" is absent or empty");
}
List<RBAC> rbacs = new ArrayList<>();
List<Map<String, ?>> objects = JsonUtil.getListOfObjects(json, "deny_rules");
if (objects != null && !objects.isEmpty()) {
rbacs.add(
RBAC.newBuilder()
.setAction(Action.DENY)
.putAllPolicies(parseRules(objects, name))
.build());
}
objects = JsonUtil.getListOfObjects(json, "allow_rules");
if (objects == null || objects.isEmpty()) {
throw new IllegalArgumentException("\"allow_rules\" is absent");
}
rbacs.add(
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putAllPolicies(parseRules(objects, name))
.build());
return rbacs;
}
}

View File

@ -0,0 +1,574 @@
/*
* Copyright 2021 The gRPC Authors
*
* Licensed 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 io.grpc.authz;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import io.envoyproxy.envoy.config.rbac.v3.Permission;
import io.envoyproxy.envoy.config.rbac.v3.Policy;
import io.envoyproxy.envoy.config.rbac.v3.Principal;
import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated;
import io.envoyproxy.envoy.config.rbac.v3.RBAC;
import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action;
import io.envoyproxy.envoy.config.route.v3.HeaderMatcher;
import io.envoyproxy.envoy.type.matcher.v3.PathMatcher;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class AuthorizationPolicyTranslatorTest {
@Test
public void invalidPolicy() throws Exception {
String policy = "{ \"name\": \"abc\",, }";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IOException ioe) {
assertThat(ioe).hasMessageThat().isEqualTo(
"Use JsonReader.setLenient(true) to accept malformed JSON"
+ " at line 1 column 18 path $.name");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingAuthorizationPolicyName() throws Exception {
String policy = "{}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("\"name\" is absent or empty");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void incorrectAuthorizationPolicyName() throws Exception {
String policy = "{ \"name\": [\"abc\"] }";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (ClassCastException cce) {
assertThat(cce).hasMessageThat().isEqualTo(
"value '[abc]' for key 'name' in '{name=[abc]}' is not String");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingAllowRules() throws Exception {
String policy = "{ \"name\": \"authz\" }";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("\"allow_rules\" is absent");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingRuleName() throws Exception {
String policy = "{"
+ " \"name\" : \"abc\" ,"
+ " \"allow_rules\" : ["
+ " {}"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("rule \"name\" is absent or empty");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingSourceAndRequest() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\" : ["
+ " {"
+ " \"name\": \"allow_all\""
+ " }"
+ " ]"
+ "}";
List<RBAC> rbacs = AuthorizationPolicyTranslator.translate(policy);
assertEquals(1, rbacs.size());
RBAC expected_rbac =
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies("authz_allow_all",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder().setAny(true))
.addPermissions(Permission.newBuilder().setAny(true))
.build())
.build();
assertEquals(expected_rbac, rbacs.get(0));
}
@Test
public void emptySourceAndRequest() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\" : ["
+ " {"
+ " \"name\": \"allow_all\","
+ " \"source\": {},"
+ " \"request\": {}"
+ " }"
+ " ]"
+ "}";
List<RBAC> rbacs = AuthorizationPolicyTranslator.translate(policy);
assertEquals(1, rbacs.size());
RBAC expected_rbac =
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies("authz_allow_all",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder().setAny(true))
.addPermissions(Permission.newBuilder().setAny(true))
.build())
.build();
assertEquals(expected_rbac, rbacs.get(0));
}
@Test
public void incorrectRulesType() throws Exception {
String policy = "{"
+ " \"name\" : \"abc\" ,"
+ " \"allow_rules\" : {}"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (ClassCastException cce) {
assertThat(cce).hasMessageThat().isEqualTo(
"value '{}' for key 'allow_rules' in '{name=abc, allow_rules={}}' is not List");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void parseSourceSuccess() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"deny_rules\": ["
+ " {"
+ " \"name\": \"deny_users\","
+ " \"source\": {"
+ " \"principals\": ["
+ " \"spiffe://foo.com\","
+ " \"spiffe://bar*\","
+ " \"*baz\","
+ " \"spiffe://*.com\""
+ " ]"
+ " }"
+ " }"
+ " ],"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_any\","
+ " \"source\": {"
+ " \"principals\": ["
+ " \"*\""
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
List<RBAC> rbacs = AuthorizationPolicyTranslator.translate(policy);
assertEquals(2, rbacs.size());
RBAC expected_deny_rbac =
RBAC.newBuilder()
.setAction(Action.DENY)
.putPolicies("authz_deny_users",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder()
.setOrIds(
Principal.Set.newBuilder()
.addIds(Principal.newBuilder()
.setAuthenticated(Authenticated.newBuilder()
.setPrincipalName(StringMatcher.newBuilder()
.setExact("spiffe://foo.com").build()).build()).build())
.addIds(Principal.newBuilder()
.setAuthenticated(Authenticated.newBuilder()
.setPrincipalName(StringMatcher.newBuilder()
.setPrefix("spiffe://bar").build()).build()).build())
.addIds(Principal.newBuilder()
.setAuthenticated(Authenticated.newBuilder()
.setPrincipalName(StringMatcher.newBuilder()
.setSuffix("baz").build()).build()).build())
.addIds(Principal.newBuilder()
.setAuthenticated(Authenticated.newBuilder()
.setPrincipalName(StringMatcher.newBuilder()
.setExact("spiffe://*.com").build()).build()).build())
.build()).build())
.addPermissions(Permission.newBuilder().setAny(true))
.build()).build();
RBAC expected_allow_rbac =
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies("authz_allow_any",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder()
.setOrIds(
Principal.Set.newBuilder()
.addIds(Principal.newBuilder()
.setAuthenticated(Authenticated.newBuilder()
.setPrincipalName(StringMatcher.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder()
.setRegex(".+").build()).build()).build())
.build())
.build()).build())
.addPermissions(Permission.newBuilder().setAny(true))
.build()).build();
assertEquals(expected_deny_rbac, rbacs.get(0));
assertEquals(expected_allow_rbac, rbacs.get(1));
}
@Test
public void unsupportedPseudoHeaders() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_access\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \":method\","
+ " \"values\": ["
+ " \"foo\""
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" :method");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void unsupportedGrpcPrefixHeaders() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_access\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"grpc-xxx\","
+ " \"values\": ["
+ " \"foo\""
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" grpc-xxx");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void unsupportedHostHeaders() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_access\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"Host\","
+ " \"values\": ["
+ " \"foo\""
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" Host");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingHeaderKey() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_dev\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {}"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("\"key\" is absent or empty");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void missingHeaderValues() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_dev\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"dev-path\""
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("\"values\" is absent or empty");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void emptyHeaderValues() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_dev\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"dev-path\","
+ " \"values\": []"
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
try {
AuthorizationPolicyTranslator.translate(policy);
fail("exception expected");
} catch (IllegalArgumentException iae) {
assertThat(iae).hasMessageThat().isEqualTo("\"values\" is absent or empty");
} catch (Exception e) {
throw new AssertionError("the test failed ", e);
}
}
@Test
public void parseRequestSuccess() throws Exception {
String policy = "{"
+ " \"name\" : \"authz\" ,"
+ " \"deny_rules\": ["
+ " {"
+ " \"name\": \"deny_access\","
+ " \"request\": {"
+ " \"paths\": ["
+ " \"/pkg.service/foo\","
+ " \"/pkg.service/bar*\""
+ " ],"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"dev-path\","
+ " \"values\": [\"/dev/path/*\"]"
+ " }"
+ " ]"
+ " }"
+ " }"
+ " ],"
+ " \"allow_rules\": ["
+ " {"
+ " \"name\": \"allow_access1\","
+ " \"request\": {"
+ " \"headers\": ["
+ " {"
+ " \"key\": \"key-1\","
+ " \"values\": ["
+ " \"foo\","
+ " \"*bar\""
+ " ]"
+ " },"
+ " {"
+ " \"key\": \"key-2\","
+ " \"values\": ["
+ " \"*\""
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " },"
+ " {"
+ " \"name\": \"allow_access2\","
+ " \"request\": {"
+ " \"paths\": ["
+ " \"*baz\""
+ " ]"
+ " }"
+ " }"
+ " ]"
+ "}";
List<RBAC> rbacs = AuthorizationPolicyTranslator.translate(policy);
assertEquals(2, rbacs.size());
RBAC expected_deny_rbac =
RBAC.newBuilder()
.setAction(Action.DENY)
.putPolicies("authz_deny_access",
Policy.newBuilder()
.addPermissions(Permission.newBuilder()
.setAndRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setOrRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setUrlPath(PathMatcher.newBuilder()
.setPath(StringMatcher.newBuilder()
.setExact("/pkg.service/foo").build()).build()).build())
.addRules(Permission.newBuilder()
.setUrlPath(PathMatcher.newBuilder()
.setPath(StringMatcher.newBuilder()
.setPrefix("/pkg.service/bar").build()).build()).build())
.build()).build())
.addRules(Permission.newBuilder()
.setAndRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setOrRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setHeader(HeaderMatcher.newBuilder()
.setName("dev-path")
.setStringMatch(StringMatcher.newBuilder()
.setPrefix("/dev/path/").build())
.build())
.build())
.build()).build())
.build()).build())
.build()))
.addPrincipals(Principal.newBuilder().setAny(true))
.build()).build();
RBAC expected_allow_rbac =
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies("authz_allow_access1",
Policy.newBuilder()
.addPermissions(Permission.newBuilder()
.setAndRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setAndRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setOrRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setHeader(HeaderMatcher.newBuilder()
.setName("key-1")
.setStringMatch(StringMatcher.newBuilder()
.setExact("foo").build())
.build())
.build())
.addRules(Permission.newBuilder()
.setHeader(HeaderMatcher.newBuilder()
.setName("key-1")
.setStringMatch(StringMatcher.newBuilder()
.setSuffix("bar").build())
.build())
.build())
.build()).build())
.addRules(Permission.newBuilder()
.setOrRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setHeader(HeaderMatcher.newBuilder()
.setName("key-2")
.setStringMatch(StringMatcher.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder()
.setRegex(".+").build()).build())
.build())
.build())
.build()).build()).build()).build()).build()))
.addPrincipals(Principal.newBuilder().setAny(true))
.build())
.putPolicies("authz_allow_access2",
Policy.newBuilder()
.addPermissions(Permission.newBuilder()
.setAndRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setOrRules(Permission.Set.newBuilder()
.addRules(Permission.newBuilder()
.setUrlPath(PathMatcher.newBuilder()
.setPath(StringMatcher.newBuilder()
.setSuffix("baz").build()).build()).build())
.build()).build())
.build()))
.addPrincipals(Principal.newBuilder().setAny(true))
.build())
.build();
assertEquals(expected_deny_rbac, rbacs.get(0));
assertEquals(expected_allow_rbac, rbacs.get(1));
}
}

View File

@ -49,6 +49,7 @@ include ":grpc-services"
include ":grpc-xds"
include ":grpc-bom"
include ":grpc-rls"
include ":grpc-authz"
include ":grpc-observability"
project(':grpc-api').projectDir = "$rootDir/api" as File
@ -74,6 +75,7 @@ project(':grpc-services').projectDir = "$rootDir/services" as File
project(':grpc-xds').projectDir = "$rootDir/xds" as File
project(':grpc-bom').projectDir = "$rootDir/bom" as File
project(':grpc-rls').projectDir = "$rootDir/rls" as File
project(':grpc-authz').projectDir = "$rootDir/authz" as File
project(':grpc-observability').projectDir = "$rootDir/observability" as File
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {