mirror of https://github.com/grpc/grpc-java.git
xds: Added a CEL-based Authorization Engine (#7191)
* xds: add a CEL-based authorization engine that uses the mock CEL library
This commit is contained in:
parent
6593fc8d35
commit
cd0cc95553
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The AuthorizationDecision class holds authorization decision
|
||||
* returned by CEL-based Authorization Engine.
|
||||
*/
|
||||
public class AuthorizationDecision {
|
||||
/** Output represents the possible decisions generated by CEL-based Authorization Engine.*/
|
||||
public enum Output {
|
||||
/**
|
||||
* ALLOW indicates that CEL Evaluate Engine
|
||||
* had authorized the gRPC call and allowed the gRPC call to go through.
|
||||
*/
|
||||
ALLOW,
|
||||
/**
|
||||
* DENY indicates that CEL Evaluate Engine
|
||||
* had authorized the gRPC call and denied the gRPC call from going through.
|
||||
*/
|
||||
DENY,
|
||||
/**
|
||||
* UNKNOWN indicates that CEL Evaluate Engine
|
||||
* did not have enough information to authorize the gRPC call.
|
||||
* */
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
private final Output decision;
|
||||
private final ImmutableList<String> policyNames;
|
||||
|
||||
/**
|
||||
* Creates a new authorization decision using the input {@code decision}
|
||||
* for resolving authorization decision
|
||||
* and {@code policyNames} for resolving authorization context.
|
||||
*/
|
||||
public AuthorizationDecision(Output decision, List<String> policyNames) {
|
||||
this.decision = decision;
|
||||
this.policyNames = ImmutableList.copyOf(policyNames);
|
||||
}
|
||||
|
||||
/** Returns the authorization decision. */
|
||||
public Output getDecision() {
|
||||
return this.decision;
|
||||
}
|
||||
|
||||
/** Returns the policy list. */
|
||||
public ImmutableList<String> getPolicyNames() {
|
||||
return this.policyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder authzStr = new StringBuilder();
|
||||
switch (this.decision) {
|
||||
case ALLOW:
|
||||
authzStr.append("Authorization Decision: ALLOW. \n");
|
||||
break;
|
||||
case DENY:
|
||||
authzStr.append("Authorization Decision: DENY. \n");
|
||||
break;
|
||||
case UNKNOWN:
|
||||
authzStr.append("Authorization Decision: UNKNOWN. \n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (String policyName : this.policyNames) {
|
||||
authzStr.append(policyName + "; \n");
|
||||
}
|
||||
return authzStr.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.api.expr.v1alpha1.Expr;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.protobuf.Descriptors.Descriptor;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.Policy;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Activation;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.DefaultDispatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.DefaultInterpreter;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.DescriptorMessageProvider;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Dispatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.IncompleteData;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Interpretable;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Interpreter;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.InterpreterException;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.RuntimeTypeProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* CEL-based Authorization Engine is part of the authorization framework in gRPC.
|
||||
* CEL-based Authorization Engine takes one or two Envoy RBAC policies as input
|
||||
* and uses CEL library to evaluate the condition field
|
||||
* inside each RBAC policy based on the provided Envoy Attributes.
|
||||
* CEL-based Authorization Engine will generate an authorization decision which
|
||||
* could be ALLOW, DENY or UNKNOWN.
|
||||
*
|
||||
* <p>Use as in:
|
||||
*
|
||||
* <pre>
|
||||
* AuthorizationEngine engine = new AuthorizationEngine(rbacPolicy);
|
||||
* AuthorizationDecision result = engine.evaluate(new EvaluateArgs(call, headers));
|
||||
* </pre>
|
||||
*/
|
||||
public class AuthorizationEngine {
|
||||
/**
|
||||
* RbacEngine is an inner class that holds RBAC action
|
||||
* and a list of conditions in RBAC policy.
|
||||
*/
|
||||
private static class RbacEngine {
|
||||
@SuppressWarnings("UnusedVariable")
|
||||
private final Action action;
|
||||
private final ImmutableMap<String, Expr> conditions;
|
||||
|
||||
public RbacEngine(Action action, ImmutableMap<String, Expr> conditions) {
|
||||
this.action = checkNotNull(action);
|
||||
this.conditions = checkNotNull(conditions);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger log = Logger.getLogger(AuthorizationEngine.class.getName());
|
||||
private final RbacEngine allowEngine;
|
||||
private final RbacEngine denyEngine;
|
||||
|
||||
/**
|
||||
* Creates a CEL-based Authorization Engine from one Envoy RBAC policy.
|
||||
* @param rbacPolicy input Envoy RBAC policy.
|
||||
*/
|
||||
public AuthorizationEngine(RBAC rbacPolicy) {
|
||||
Map<String, Expr> conditions = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Policy> policy: rbacPolicy.getPolicies().entrySet()) {
|
||||
conditions.put(policy.getKey(), policy.getValue().getCondition());
|
||||
}
|
||||
allowEngine = (rbacPolicy.getAction() == Action.ALLOW)
|
||||
? new RbacEngine(Action.ALLOW, ImmutableMap.copyOf(conditions)) : null;
|
||||
denyEngine = (rbacPolicy.getAction() == Action.DENY)
|
||||
? new RbacEngine(Action.DENY, ImmutableMap.copyOf(conditions)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CEL-based Authorization Engine from two Envoy RBAC policies.
|
||||
* When it takes two RBAC policies,
|
||||
* the order has to be a DENY policy followed by an ALLOW policy.
|
||||
* @param denyPolicy input Envoy RBAC policy with DENY action.
|
||||
* @param allowPolicy input Envoy RBAC policy with ALLOW action.
|
||||
* @throws IllegalArgumentException if the user inputs an invalid RBAC list.
|
||||
*/
|
||||
public AuthorizationEngine(RBAC denyPolicy, RBAC allowPolicy) throws IllegalArgumentException {
|
||||
checkArgument(
|
||||
denyPolicy.getAction() == Action.DENY && allowPolicy.getAction() == Action.ALLOW,
|
||||
"Invalid RBAC list, "
|
||||
+ "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. ");
|
||||
Map<String, Expr> denyConditions = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Policy> policy: denyPolicy.getPolicies().entrySet()) {
|
||||
denyConditions.put(policy.getKey(), policy.getValue().getCondition());
|
||||
}
|
||||
denyEngine = new RbacEngine(Action.DENY, ImmutableMap.copyOf(denyConditions));
|
||||
Map<String, Expr> allowConditions = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Policy> policy: allowPolicy.getPolicies().entrySet()) {
|
||||
allowConditions.put(policy.getKey(), policy.getValue().getCondition());
|
||||
}
|
||||
allowEngine = new RbacEngine(Action.ALLOW, ImmutableMap.copyOf(allowConditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* The evaluate function performs the core authorization mechanism
|
||||
* of CEL-based Authorization Engine.
|
||||
* It determines whether a gRPC call is allowed, denied, or unable to be decided.
|
||||
* @param args evaluate argument that is used to evaluate the RBAC conditions.
|
||||
* @return an AuthorizationDecision generated by CEL-based Authorization Engine.
|
||||
*/
|
||||
public AuthorizationDecision evaluate(EvaluateArgs args) {
|
||||
List<String> unknownPolicyNames = new ArrayList<>();
|
||||
// Set up activation used in CEL library's eval function.
|
||||
Activation activation = Activation.copyOf(args.generateEnvoyAttributes());
|
||||
// Iterate through denyEngine's map.
|
||||
// If there is match, immediately return DENY.
|
||||
// If there are unknown conditions, return UNKNOWN.
|
||||
// If all non-match, then iterate through allowEngine.
|
||||
if (denyEngine != null) {
|
||||
AuthorizationDecision authzDecision = evaluateEngine(denyEngine.conditions.entrySet(),
|
||||
AuthorizationDecision.Output.DENY, unknownPolicyNames, activation);
|
||||
if (authzDecision != null) {
|
||||
return authzDecision;
|
||||
}
|
||||
if (unknownPolicyNames.size() > 0) {
|
||||
return new AuthorizationDecision(
|
||||
AuthorizationDecision.Output.UNKNOWN, unknownPolicyNames);
|
||||
}
|
||||
}
|
||||
// Once we enter allowEngine, if there is a match, immediately return ALLOW.
|
||||
// In the end of iteration, if there are unknown conditions, return UNKNOWN.
|
||||
// If all non-match, return DENY.
|
||||
if (allowEngine != null) {
|
||||
AuthorizationDecision authzDecision = evaluateEngine(allowEngine.conditions.entrySet(),
|
||||
AuthorizationDecision.Output.ALLOW, unknownPolicyNames, activation);
|
||||
if (authzDecision != null) {
|
||||
return authzDecision;
|
||||
}
|
||||
if (unknownPolicyNames.size() > 0) {
|
||||
return new AuthorizationDecision(
|
||||
AuthorizationDecision.Output.UNKNOWN, unknownPolicyNames);
|
||||
}
|
||||
}
|
||||
// Return ALLOW if it only has a denyEngine and it’s unmatched.
|
||||
if (this.allowEngine == null && this.denyEngine != null) {
|
||||
return new AuthorizationDecision(
|
||||
AuthorizationDecision.Output.ALLOW, new ArrayList<String>());
|
||||
}
|
||||
// Return DENY if none of denyEngine and allowEngine matched,
|
||||
// or the single allowEngine is unmatched when there is only one allowEngine.
|
||||
return new AuthorizationDecision(AuthorizationDecision.Output.DENY, new ArrayList<String>());
|
||||
}
|
||||
|
||||
/** Evaluate a single RbacEngine. */
|
||||
protected AuthorizationDecision evaluateEngine(Set<Map.Entry<String, Expr>> entrySet,
|
||||
AuthorizationDecision.Output decision, List<String> unknownPolicyNames,
|
||||
Activation activation) {
|
||||
for (Map.Entry<String, Expr> condition : entrySet) {
|
||||
try {
|
||||
if (matches(condition.getValue(), activation)) {
|
||||
return new AuthorizationDecision(decision,
|
||||
new ArrayList<String>(Arrays.asList(new String[] {condition.getKey()})));
|
||||
}
|
||||
} catch (InterpreterException e) {
|
||||
unknownPolicyNames.add(condition.getKey());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Evaluate if a condition matches the given Enovy Attributes using CEL library. */
|
||||
protected boolean matches(Expr condition, Activation activation) throws InterpreterException {
|
||||
// Set up interpretable used in CEL library's eval function.
|
||||
List<Descriptor> descriptors = new ArrayList<>();
|
||||
RuntimeTypeProvider messageProvider = DescriptorMessageProvider.dynamicMessages(descriptors);
|
||||
Dispatcher dispatcher = DefaultDispatcher.create();
|
||||
Interpreter interpreter = new DefaultInterpreter(messageProvider, dispatcher);
|
||||
Interpretable interpretable = interpreter.createInterpretable(condition);
|
||||
// Parse the generated result object to a boolean variable.
|
||||
try {
|
||||
Object result = interpretable.eval(activation);
|
||||
if (result instanceof Boolean) {
|
||||
return Boolean.valueOf(result.toString());
|
||||
}
|
||||
// Throw an InterpreterException if there are missing Envoy Attributes.
|
||||
if (result instanceof IncompleteData) {
|
||||
throw new InterpreterException.Builder("Envoy Attributes gotten are incomplete.").build();
|
||||
}
|
||||
} catch (InterpreterException e) {
|
||||
// If any InterpreterExceptions are catched, throw it and log the error.
|
||||
log.log(Level.WARNING, e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
|
||||
/** The EvaluateArgs class holds evaluate arguments used in CEL-based Authorization Engine. */
|
||||
public class EvaluateArgs {
|
||||
private Metadata headers;
|
||||
private ServerCall<?, ?> call;
|
||||
|
||||
/**
|
||||
* Creates a new EvaluateArgs using the input {@code headers} for resolving headers
|
||||
* and {@code call} for resolving gRPC call.
|
||||
*/
|
||||
public EvaluateArgs(Metadata headers, ServerCall<?, ?> call) {
|
||||
this.headers = headers;
|
||||
this.call = call;
|
||||
}
|
||||
|
||||
/** Extract the request.url_path field. */
|
||||
protected String getRequestUrlPath() {
|
||||
String requestUrlPath = this.call.getMethodDescriptor().getFullMethodName();
|
||||
return requestUrlPath;
|
||||
}
|
||||
|
||||
/** Extract the request.host field. */
|
||||
protected String getRequestHost() {
|
||||
String requestHost = this.call.getAuthority();
|
||||
return requestHost;
|
||||
}
|
||||
|
||||
/** Extract the request.method field. */
|
||||
protected String getRequestMethod() {
|
||||
// TODO(@zhenlian): confirm extraction for request.method.
|
||||
String requestMethod = this.call.getMethodDescriptor().getServiceName();
|
||||
return requestMethod;
|
||||
}
|
||||
|
||||
/** Extract the request.headers field. */
|
||||
protected Metadata getRequestHeaders() {
|
||||
// TODO(@zhenlian): convert request.headers from Metadata to a String Map.
|
||||
Metadata requestHeaders = this.headers;
|
||||
return requestHeaders;
|
||||
}
|
||||
|
||||
/** Extract the source.address field. */
|
||||
protected String getSourceAddress() {
|
||||
String sourceAddress =
|
||||
this.call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString();
|
||||
return sourceAddress;
|
||||
}
|
||||
|
||||
/** Extract the source.port field. */
|
||||
protected int getSourcePort() {
|
||||
// TODO(@zhenlian): fill out extraction for source.port.
|
||||
int sourcePort = 0;
|
||||
return sourcePort;
|
||||
}
|
||||
|
||||
/** Extract the destination.address field. */
|
||||
protected String getDestinationAddress() {
|
||||
String destinationAddress =
|
||||
this.call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR).toString();
|
||||
return destinationAddress;
|
||||
}
|
||||
|
||||
/** Extract the destination.port field. */
|
||||
protected int getDestinationPort() {
|
||||
// TODO(@zhenlian): fill out extraction for destination.port.
|
||||
int destinationPort = 0;
|
||||
return destinationPort;
|
||||
}
|
||||
|
||||
/** Extract the connection.uri_san_peer_certificate field. */
|
||||
protected String getConnectionUriSanPeerCertificate() {
|
||||
// TODO(@zhenlian): fill out extraction for connection.uri_san_peer_certificate.
|
||||
String connectionUriSanPeerCertificate = "placeholder";
|
||||
return connectionUriSanPeerCertificate;
|
||||
}
|
||||
|
||||
/** Extract the source.principal field. */
|
||||
protected String getSourcePrincipal() {
|
||||
// TODO(@zhenlian): fill out extraction for source.principal.
|
||||
String sourcePrincipal = "placeholder";
|
||||
return sourcePrincipal;
|
||||
}
|
||||
|
||||
/** Extract Envoy Attributes from EvaluateArgs. */
|
||||
public ImmutableMap<String, Object> generateEnvoyAttributes() {
|
||||
ImmutableMap<String, Object> attributes = ImmutableMap.<String, Object>builder()
|
||||
.put("request.url_path", this.getRequestUrlPath())
|
||||
.put("request.host", this.getRequestHost())
|
||||
.put("request.method", this.getRequestMethod())
|
||||
.put("request.headers", this.getRequestHeaders())
|
||||
.put("source.address", this.getSourceAddress())
|
||||
.put("source.port", this.getSourcePort())
|
||||
.put("destination.address", this.getDestinationAddress())
|
||||
.put("destination.port", this.getDestinationPort())
|
||||
.put("connection.uri_san_peer_certificate",
|
||||
this.getConnectionUriSanPeerCertificate())
|
||||
.put("source.principal", this.getSourcePrincipal())
|
||||
.build();
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
|
||||
import com.google.api.expr.v1alpha1.Expr;
|
||||
import com.google.api.expr.v1alpha1.Expr.Ident;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.Policy;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Activation;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.InterpreterException;
|
||||
import java.lang.StringBuilder;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/** Unit tests for evaluate function of CEL Evaluation Engine. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class AuthzEngineEvaluationTest {
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private EvaluateArgs args;
|
||||
|
||||
@Mock
|
||||
private Activation activation;
|
||||
|
||||
@Mock
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private AuthorizationEngine engine;
|
||||
private AuthorizationEngine spyEngine;
|
||||
private AuthorizationDecision evaluateResult;
|
||||
|
||||
// Mock RBAC engine with ALLOW action.
|
||||
private RBAC rbacAllow;
|
||||
// Mock RBAC engine with DENY action.
|
||||
private RBAC rbacDeny;
|
||||
|
||||
// Mock policies that will be used to construct RBAC Engine.
|
||||
private Policy policy1;
|
||||
private Policy policy2;
|
||||
private Policy policy3;
|
||||
private Policy policy4;
|
||||
private Policy policy5;
|
||||
private Policy policy6;
|
||||
|
||||
// Mock conditions that will be used to construct RBAC poilcies.
|
||||
private Expr condition1;
|
||||
private Expr condition2;
|
||||
private Expr condition3;
|
||||
private Expr condition4;
|
||||
private Expr condition5;
|
||||
private Expr condition6;
|
||||
|
||||
@Before
|
||||
public void buildRbac() {
|
||||
// Set up RBAC conditions.
|
||||
condition1 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 1").build())
|
||||
.build();
|
||||
condition2 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 2").build())
|
||||
.build();
|
||||
condition3 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 3").build())
|
||||
.build();
|
||||
condition4 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 4").build())
|
||||
.build();
|
||||
condition5 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 5").build())
|
||||
.build();
|
||||
condition6 = Expr.newBuilder()
|
||||
.setIdentExpr(Ident.newBuilder().setName("Condition 6").build())
|
||||
.build();
|
||||
// Set up RBAC policies.
|
||||
policy1 = Policy.newBuilder().setCondition(condition1).build();
|
||||
policy2 = Policy.newBuilder().setCondition(condition2).build();
|
||||
policy3 = Policy.newBuilder().setCondition(condition3).build();
|
||||
policy4 = Policy.newBuilder().setCondition(condition4).build();
|
||||
policy5 = Policy.newBuilder().setCondition(condition5).build();
|
||||
policy6 = Policy.newBuilder().setCondition(condition6).build();
|
||||
// Set up RBACs.
|
||||
rbacAllow = RBAC.newBuilder()
|
||||
.setAction(Action.ALLOW)
|
||||
.putPolicies("Policy 1", policy1)
|
||||
.putPolicies("Policy 2", policy2)
|
||||
.putPolicies("Policy 3", policy3)
|
||||
.build();
|
||||
rbacDeny = RBAC.newBuilder()
|
||||
.setAction(Action.DENY)
|
||||
.putPolicies("Policy 4", policy4)
|
||||
.putPolicies("Policy 5", policy5)
|
||||
.putPolicies("Policy 6", policy6)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Build an ALLOW engine from Policy 1, 2, 3. */
|
||||
@Before
|
||||
public void setupEngineSingleRbacAllow() {
|
||||
buildRbac();
|
||||
engine = new AuthorizationEngine(rbacAllow);
|
||||
spyEngine = Mockito.spy(engine);
|
||||
doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes();
|
||||
}
|
||||
|
||||
/** Build a DENY engine from Policy 4, 5, 6. */
|
||||
@Before
|
||||
public void setupEngineSingleRbacDeny() {
|
||||
buildRbac();
|
||||
engine = new AuthorizationEngine(rbacDeny);
|
||||
spyEngine = Mockito.spy(engine);
|
||||
doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes();
|
||||
}
|
||||
|
||||
/** Build a pair of engines with a DENY engine followed by an ALLOW engine. */
|
||||
@Before
|
||||
public void setupEngineRbacPair() {
|
||||
buildRbac();
|
||||
engine = new AuthorizationEngine(rbacDeny, rbacAllow);
|
||||
spyEngine = Mockito.spy(engine);
|
||||
doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the ALLOW engine.
|
||||
* The evaluation result of all the CEL expressions is set to true,
|
||||
* so the gRPC authorization returns ALLOW.
|
||||
*/
|
||||
@Test
|
||||
public void testAllowEngineWithAllMatchedPolicies() throws InterpreterException {
|
||||
setupEngineSingleRbacAllow();
|
||||
// Policy 1 - matched; Policy 2 - matched; Policy 3 - matched
|
||||
doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the ALLOW engine.
|
||||
* The evaluation result of all the CEL expressions is set to false,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testAllowEngineWithAllUnmatchedPolicies() throws InterpreterException {
|
||||
setupEngineSingleRbacAllow();
|
||||
// Policy 1 - unmatched; Policy 2 - unmatched; Policy 3 - unmatched
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 0);
|
||||
assertEquals(evaluateResult.toString(),
|
||||
new StringBuilder("Authorization Decision: DENY. \n").toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the ALLOW engine.
|
||||
* The evaluation result of two CEL expressions is set to true,
|
||||
* and the evaluation result of one CEL expression is set to false,
|
||||
* so the gRPC authorization returns ALLOW.
|
||||
*/
|
||||
@Test
|
||||
public void testAllowEngineWithMatchedAndUnmatchedPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacAllow();
|
||||
// Policy 1 - unmatched; Policy 2 - matched; Policy 3 - matched
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the ALLOW engine.
|
||||
* The evaluation result of one CEL expression is set to unknown,
|
||||
* so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testAllowEngineWithUnknownAndUnmatchedPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacAllow();
|
||||
// Policy 1 - unmatched; Policy 2 - unknown; Policy 3 - unknown
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 2);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 2"));
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 3"));
|
||||
assertEquals(evaluateResult.toString(),
|
||||
new StringBuilder("Authorization Decision: UNKNOWN. \n"
|
||||
+ "Policy 2; \n" + "Policy 3; \n").toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the ALLOW engine.
|
||||
* The evaluation result of one CEL expression is set to unknown,
|
||||
* so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testAllowEngineWithMatchedUnmatchedAndUnknownPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacAllow();
|
||||
// Policy 1 - unmatched; Policy 2 - matched; Policy 3 - unknown
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 2"));
|
||||
assertEquals(evaluateResult.toString(),
|
||||
new StringBuilder("Authorization Decision: ALLOW. \n" + "Policy 2; \n").toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine.
|
||||
* The evaluation result of all the CEL expressions is set to true,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testDenyEngineWithAllMatchedPolicies() throws InterpreterException {
|
||||
setupEngineSingleRbacDeny();
|
||||
// Policy 4 - matched; Policy 5 - matched; Policy 6 - matched
|
||||
doReturn(true).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 4"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine.
|
||||
* The evaluation result of all the CEL expressions is set to false,
|
||||
* so the gRPC authorization returns ALLOW.
|
||||
*/
|
||||
@Test
|
||||
public void testDenyEngineWithAllUnmatchedPolicies() throws InterpreterException {
|
||||
setupEngineSingleRbacDeny();
|
||||
// Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine.
|
||||
* The evaluation result of two CEL expressions is set to true,
|
||||
* and the evaluation result of one CEL expression is set to false,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testDenyEngineWithMatchedAndUnmatchedPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacDeny();
|
||||
// Policy 4 - unmatched; Policy 5 - matched; Policy 6 - matched
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 5"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine.
|
||||
* The evaluation result of one CEL expression is set to unknown,
|
||||
* so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testDenyEngineWithUnknownAndUnmatchedPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacDeny();
|
||||
// Policy 4 - unmatched; Policy 5 - unknown; Policy 6 - unknown
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 2);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 5"));
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 6"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine.
|
||||
* The evaluation result of one CEL expression is set to unknown,
|
||||
* so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testDenyEngineWithMatchedUnmatchedAndUnknownPolicies()
|
||||
throws InterpreterException {
|
||||
setupEngineSingleRbacDeny();
|
||||
// Policy 4 - unmatched; Policy 5 - matched; Policy 6 - unknown
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 5"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine and ALLOW engine pair.
|
||||
* The evaluation result of all the CEL expressions is set to true in DENY engine,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testEnginePairWithAllMatchedDenyEngine() throws InterpreterException {
|
||||
setupEngineRbacPair();
|
||||
// Policy 4 - matched; Policy 5 - matched; Policy 6 - matched
|
||||
// Policy 1 - matched; Policy 2 - matched; Policy 3 - matched
|
||||
doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 4"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine and ALLOW engine pair.
|
||||
* The evaluation result of two CEL expressions is set to true,
|
||||
* and the evaluation result of one CEL expression is set to false in DENY engine,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testEnginePairWithPartiallyMatchedDenyEngine()
|
||||
throws InterpreterException {
|
||||
setupEngineRbacPair();
|
||||
// Policy 4 - unmatched; Policy 5 - matched; Policy 6 - unknown
|
||||
// Policy 1 - matched; Policy 2 - matched; Policy 3 - matched
|
||||
doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 1);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 5"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine and ALLOW engine pair.
|
||||
* The DENY engine has unknown policies, so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testEnginePairWithUnknownDenyEngine() throws InterpreterException {
|
||||
setupEngineRbacPair();
|
||||
// Policy 4 - unmatched; Policy 5 - unknown; Policy 6 - unknown
|
||||
// Policy 1 - matched; Policy 2 - matched; Policy 3 - matched
|
||||
doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 2);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 5"));
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 6"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine and ALLOW engine pair.
|
||||
* The evaluation result of all the CEL expressions is set to false in DENY engine,
|
||||
* and the ALLOW engine has unknown policies,
|
||||
* so the gRPC authorization returns UNKNOWN.
|
||||
*/
|
||||
@Test
|
||||
public void testEnginePairWithUnmatchedDenyEngineAndUnknownAllowEngine()
|
||||
throws InterpreterException {
|
||||
setupEngineRbacPair();
|
||||
// Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched
|
||||
// Policy 1 - unmatched; Policy 2 - unknown; Policy 3 - unknown
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doThrow(new InterpreterException.Builder("Unknown result").build())
|
||||
.when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 2);
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 2"));
|
||||
assertTrue(evaluateResult.getPolicyNames().contains("Policy 3"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test on the DENY engine and ALLOW engine pair.
|
||||
* The evaluation result of all the CEL expressions is set to false in both engines,
|
||||
* so the gRPC authorization returns DENY.
|
||||
*/
|
||||
@Test
|
||||
public void testUnmatchedEnginePair() throws InterpreterException {
|
||||
setupEngineRbacPair();
|
||||
// Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched
|
||||
// Policy 1 - unmatched; Policy 2 - unmatched; Policy 3 - unmatched
|
||||
doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition2), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition3), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class));
|
||||
doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class));
|
||||
evaluateResult = spyEngine.evaluate(args);
|
||||
assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY);
|
||||
assertEquals(evaluateResult.getPolicyNames().size(), 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.api.expr.v1alpha1.Expr;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC;
|
||||
import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Activation;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Dispatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Interpretable;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.Interpreter;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.InterpreterException;
|
||||
import io.grpc.xds.internal.rbac.engine.cel.RuntimeTypeProvider;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/** Unit tests for constructor of CEL-based Authorization Engine. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class AuthzEngineTest {
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private Activation activation;
|
||||
|
||||
@Mock
|
||||
private RuntimeTypeProvider messageProvider;
|
||||
|
||||
@Mock
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
@Mock
|
||||
private Interpreter interpreter;
|
||||
|
||||
@Mock
|
||||
private Interpretable interpretable;
|
||||
|
||||
private AuthorizationEngine engine;
|
||||
private RBAC rbacDeny;
|
||||
private RBAC rbacAllow;
|
||||
private Expr expr;
|
||||
private Object result;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
rbacAllow = RBAC.newBuilder()
|
||||
.setAction(Action.ALLOW)
|
||||
.build();
|
||||
rbacDeny = RBAC.newBuilder()
|
||||
.setAction(Action.DENY)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createEngineAllowPolicy() {
|
||||
engine = new AuthorizationEngine(rbacAllow);
|
||||
assertNotNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createEngineDenyPolicy() {
|
||||
engine = new AuthorizationEngine(rbacDeny);
|
||||
assertNotNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createEngineDenyAllowPolicies() {
|
||||
engine = new AuthorizationEngine(rbacDeny, rbacAllow);
|
||||
assertNotNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failToCreateEngineIfRbacPairOfAllowAllow() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Invalid RBAC list, "
|
||||
+ "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. ");
|
||||
engine = new AuthorizationEngine(rbacAllow, rbacAllow);
|
||||
assertNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failToCreateEngineIfRbacPairOfAllowDeny() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Invalid RBAC list, "
|
||||
+ "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. ");
|
||||
engine = new AuthorizationEngine(rbacAllow, rbacDeny);
|
||||
assertNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failToCreateEngineIfRbacPairOfDenyDeny() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Invalid RBAC list, "
|
||||
+ "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. ");
|
||||
engine = new AuthorizationEngine(rbacDeny, rbacDeny);
|
||||
assertNull(engine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCelInterface() throws InterpreterException {
|
||||
engine = new AuthorizationEngine(rbacAllow);
|
||||
when(interpretable.eval(any(Activation.class))).thenReturn(true);
|
||||
expr = Expr.newBuilder().build();
|
||||
result = engine.matches(expr, activation);
|
||||
assertThat(messageProvider).isNotNull();
|
||||
assertThat(dispatcher).isNotNull();
|
||||
assertThat(interpreter).isNotNull();
|
||||
assertThat(activation).isNotNull();
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2020 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.xds.internal.rbac.engine;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.SecurityLevel;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.internal.GrpcAttributes;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import java.net.SocketAddress;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/** Unit tests for evaluate argument. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class EvaluateArgsTest<ReqT,RespT> {
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private ServerCall<ReqT,RespT> call;
|
||||
|
||||
private EvaluateArgs args;
|
||||
private EvaluateArgs spyArgs;
|
||||
|
||||
private Metadata metadata;
|
||||
private ImmutableMap<String, Object> attributesMap;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// Set up metadata.
|
||||
metadata = new Metadata();
|
||||
// Set up spyArgs.
|
||||
args = new EvaluateArgs(metadata, call);
|
||||
spyArgs = Mockito.spy(args);
|
||||
// Set up attributes map.
|
||||
attributesMap = ImmutableMap.<String, Object>builder()
|
||||
.put("request.url_path", "package.service/method")
|
||||
.put("request.host", "fooapi.googleapis.com")
|
||||
.put("request.method", "GET")
|
||||
.put("request.headers", metadata)
|
||||
.put("source.address", "1.2.3.4")
|
||||
.put("source.port", 5050)
|
||||
.put("destination.address", "4.3.2.1")
|
||||
.put("destination.port", 8080)
|
||||
.put("connection.uri_san_peer_certificate", "foo")
|
||||
.put("source.principal", "spiffe")
|
||||
.build();
|
||||
// Set up evaluate args.
|
||||
doReturn("package.service/method").when(spyArgs).getRequestUrlPath();
|
||||
doReturn("fooapi.googleapis.com").when(spyArgs).getRequestHost();
|
||||
doReturn("GET").when(spyArgs).getRequestMethod();
|
||||
doReturn(metadata).when(spyArgs).getRequestHeaders();
|
||||
doReturn("1.2.3.4").when(spyArgs).getSourceAddress();
|
||||
doReturn(5050).when(spyArgs).getSourcePort();
|
||||
doReturn("4.3.2.1").when(spyArgs).getDestinationAddress();
|
||||
doReturn(8080).when(spyArgs).getDestinationPort();
|
||||
doReturn("foo").when(spyArgs).getConnectionUriSanPeerCertificate();
|
||||
doReturn("spiffe").when(spyArgs).getSourcePrincipal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateEnvoyAttributes() {
|
||||
setup();
|
||||
ImmutableMap<String, Object> attributes = spyArgs.generateEnvoyAttributes();
|
||||
assertEquals(attributesMap, attributes);
|
||||
verify(spyArgs, times(1)).getRequestUrlPath();
|
||||
verify(spyArgs, times(1)).getRequestHost();
|
||||
verify(spyArgs, times(1)).getRequestMethod();
|
||||
verify(spyArgs, times(1)).getRequestHeaders();
|
||||
verify(spyArgs, times(1)).getSourceAddress();
|
||||
verify(spyArgs, times(1)).getSourcePort();
|
||||
verify(spyArgs, times(1)).getDestinationAddress();
|
||||
verify(spyArgs, times(1)).getDestinationPort();
|
||||
verify(spyArgs, times(1)).getConnectionUriSanPeerCertificate();
|
||||
verify(spyArgs, times(1)).getSourcePrincipal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateArgsAccessorFunctions() {
|
||||
// Set up args and call.
|
||||
args = new EvaluateArgs(new Metadata(), call);
|
||||
SocketAddress localAddr = new LocalAddress("local_addr");
|
||||
SocketAddress remoteAddr = new LocalAddress("remote_addr");
|
||||
Attributes attrs = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, localAddr)
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr)
|
||||
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
|
||||
.build();
|
||||
when(call.getAttributes()).thenReturn(attrs);
|
||||
when(call.getAuthority()).thenReturn("fooapi.googleapis.com");
|
||||
// Check the behavior of accessor functions.
|
||||
assertEquals(args.getRequestHost(), "fooapi.googleapis.com");
|
||||
assertNotNull(args.getRequestHeaders());
|
||||
assertEquals(args.getSourcePort(), 0);
|
||||
assertEquals(args.getDestinationPort(), 0);
|
||||
assertEquals(args.getSourceAddress(), "local:remote_addr");
|
||||
assertEquals(args.getDestinationAddress(), "local:local_addr");
|
||||
assertEquals(args.getConnectionUriSanPeerCertificate(), "placeholder");
|
||||
assertEquals(args.getSourcePrincipal(), "placeholder");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue