mirror of https://github.com/grpc/grpc-java.git
authz: translate gRPC authz policy to Envoy RBAC proto (#8710)
This commit is contained in:
parent
bb3365731f
commit
1b88065f9a
|
@ -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}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in New Issue