xds: remove cell based rbac engine (#8277)

This commit is contained in:
yifeizhuang 2021-06-25 11:20:11 -07:00 committed by GitHub
parent c1ad5de135
commit 3aa871b7de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 0 additions and 1683 deletions

View File

@ -1,91 +0,0 @@
/*
* 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).append("; \n");
}
return authzStr.toString();
}
}

View File

@ -1,214 +0,0 @@
/*
* 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.getPoliciesMap().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) {
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.getPoliciesMap().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.getPoliciesMap().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.isEmpty()) {
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.isEmpty()) {
return new AuthorizationDecision(
AuthorizationDecision.Output.UNKNOWN, unknownPolicyNames);
}
}
// Return ALLOW if it only has a denyEngine and its 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.parseBoolean(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;
}
}

View File

@ -1,123 +0,0 @@
/*
* 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 final Metadata headers;
private final 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;
}
}

View File

@ -1,51 +0,0 @@
/*
* 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.cel;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Binds names to values.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public abstract class Activation {
/** Resolves the given name to its value. Returns null if resolution fails. */
@Nullable
public abstract Object resolve(String name);
/** Creates an immutable binder using the provided mapping. */
public static Activation copyOf(Map<String, ?> map) {
final ImmutableMap<String, Object> copy = ImmutableMap.copyOf(map);
return new Activation() {
@Nullable
@Override
public Object resolve(String name) {
return copy.get(name);
}
@Override
public String toString() {
return copy.toString();
}
};
}
}

View File

@ -1,41 +0,0 @@
/*
* 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.cel;
import java.lang.String;
import java.util.List;
/**
* Default implementation of {@link Dispatcher}.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public class DefaultDispatcher implements Dispatcher {
/** Creates a new dispatcher with all standard functions. */
public static DefaultDispatcher create() {
return new DefaultDispatcher();
}
@Override
public Object dispatch(Metadata metadata, long exprId,
String functionName, List<String> overloadIds, Object[] args)
throws InterpreterException {
return new Object();
}
}

View File

@ -1,54 +0,0 @@
/*
* 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.cel;
import com.google.api.expr.v1alpha1.Expr;
/**
* Default implementation of {@link Interpreter}.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public class DefaultInterpreter implements Interpreter {
/**
* Creates a new interpreter
* @param typeProvider object which allows to construct and inspect messages.
* @param dispatcher a method dispatcher.
*/
public DefaultInterpreter(RuntimeTypeProvider typeProvider, Dispatcher dispatcher) {}
@Override
public Interpretable createInterpretable(Expr expr)
throws InterpreterException {
return new DefaultInterpretable(expr);
}
private static class DefaultInterpretable implements Interpretable {
/**
* Creates a new interpretable.
* @param expr a Cel expression.
*/
public DefaultInterpretable(Expr expr) {}
@Override
public Object eval(Activation activation) throws InterpreterException {
return new Object();
}
}
}

View File

@ -1,36 +0,0 @@
/*
* 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.cel;
import com.google.protobuf.Descriptors.Descriptor;
/**
* An implementation of {@link RuntimeTypeProvider} which relies on proto descriptors.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public class DescriptorMessageProvider implements RuntimeTypeProvider {
/**
* Creates a new message provider that provides only {@link DynamicMessage DynamicMessages} for
* the specified descriptors.
*/
public static DescriptorMessageProvider dynamicMessages(Iterable<Descriptor> descriptors) {
return new DescriptorMessageProvider();
}
}

View File

@ -1,45 +0,0 @@
/*
* 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.cel;
import java.util.List;
/**
* Determines an appropriate function and invokes it.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface Dispatcher {
/**
* Invokes a function based on given parameters.
*
* @param metadata Metadata used for error reporting.
* @param exprId Expression identifier which can be used together with {@code metadata} to get
* information about the dispatch target for error reporting.
* @param functionName the logical name of the function being invoked.
* @param overloadIds A list of function overload ids. The dispatcher selects the unique overload
* from this list with matching arguments.
* @param args The arguments to pass to the function.
* @return The result of the function call.
* @throws InterpreterException if something goes wrong.
*/
Object dispatch(
Metadata metadata, long exprId, String functionName, List<String> overloadIds, Object[] args)
throws InterpreterException;
}

View File

@ -1,27 +0,0 @@
/*
* 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.cel;
/**
* Interpreter returns an instance of {@code IncompleteData} if
* an object is an instance of {@link PartialMessage}.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface IncompleteData {}

View File

@ -1,29 +0,0 @@
/*
* 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.cel;
/**
* Represent an expression which can be interpreted repeatedly using a given activation.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface Interpretable {
/** Runs interpretation with the given activation which supplies name/value bindings. */
Object eval(Activation activation) throws InterpreterException;
}

View File

@ -1,31 +0,0 @@
/*
* 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.cel;
import com.google.api.expr.v1alpha1.Expr;
/**
* Interface to a CEL interpreter.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface Interpreter {
/** Creates an interpretable for the given expression. */
Interpretable createInterpretable(Expr expr) throws InterpreterException;
}

View File

@ -1,53 +0,0 @@
/*
* 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.cel;
import java.lang.Exception;
import javax.annotation.Nullable;
/**
* An exception produced during interpretation of expressions.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
@SuppressWarnings("serial")
public class InterpreterException extends Exception {
/** Builder for InterpreterException. */
public static class Builder {
@Nullable private String message;
@Nullable private String location;
private int position;
private Throwable cause;
public Builder(String message, Object... args) {}
/** Build function for InterpreterException. */
public InterpreterException build() {
return new InterpreterException(
String.format(
"evaluation error%s: %s",
location != null ? " at " + location + ":" + position : "", message),
cause);
}
}
private InterpreterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,37 +0,0 @@
/*
* 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.cel;
import java.lang.String;
/**
* An interface which provides metadata for syntax nodes.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface Metadata {
/** Returns the location (like a filename) of the interpreted expression. */
String getLocation();
/**
* Returns the character position of the node in the source.
* This is a 0-based character offset.
*/
int getPosition(long exprId);
}

View File

@ -1,27 +0,0 @@
/*
* 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.cel;
/**
* The {@code RuntimeTypeProvider} is a combination of the MessageProvider
* and TypeResolver interfaces.
*
* <p>This is a Java stub for evaluating Common Expression Language (CEL).
* More information about CEL can be found in https://github.com/google/cel-spec.
* Once Java CEL has been open-sourced, this stub will be removed.
*/
public interface RuntimeTypeProvider {}

View File

@ -1,469 +0,0 @@
/*
* 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.assertEquals;
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.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 Map<String, Object> attributes;
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();
AuthorizationEngine 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();
AuthorizationEngine 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();
AuthorizationEngine 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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.ALLOW);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).isEmpty();
assertThat(evaluateResult.toString()).isEqualTo(
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);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.UNKNOWN);
assertThat(evaluateResult.getPolicyNames()).hasSize(2);
assertThat(evaluateResult.getPolicyNames()).contains("Policy 2");
assertThat(evaluateResult.getPolicyNames()).contains("Policy 3");
assertThat(evaluateResult.toString()).isEqualTo(
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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.ALLOW);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(evaluateResult.getPolicyNames()).contains("Policy 2");
assertThat(evaluateResult.toString()).isEqualTo(
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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.ALLOW);
assertThat(evaluateResult.getPolicyNames()).isEmpty();
}
/**
* 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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.UNKNOWN);
assertThat(evaluateResult.getPolicyNames()).hasSize(2);
assertThat(evaluateResult.getPolicyNames()).contains("Policy 5");
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).hasSize(1);
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.UNKNOWN);
assertThat(evaluateResult.getPolicyNames()).hasSize(2);
assertThat(evaluateResult.getPolicyNames()).contains("Policy 5");
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.UNKNOWN);
assertThat(evaluateResult.getPolicyNames()).hasSize(2);
assertThat(evaluateResult.getPolicyNames()).contains("Policy 2");
assertThat(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);
assertThat(evaluateResult.getDecision()).isEqualTo(AuthorizationDecision.Output.DENY);
assertThat(evaluateResult.getPolicyNames()).isEmpty();
}
}

View File

@ -1,141 +0,0 @@
/*
* 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 {
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
@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 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 = Expr.getDefaultInstance();
result = engine.matches(expr, activation);
assertThat(messageProvider).isNotNull();
assertThat(dispatcher).isNotNull();
assertThat(interpreter).isNotNull();
assertThat(activation).isNotNull();
assertThat(result).isNotNull();
}
}

View File

@ -1,133 +0,0 @@
/*
* 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");
}
}

View File

@ -1,81 +0,0 @@
/*
* 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.cel;
import static com.google.common.truth.Truth.assertThat;
import com.google.api.expr.v1alpha1.Expr;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.Descriptors.Descriptor;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for CEL library stub. */
@RunWith(JUnit4.class)
public class CelInterfaceTest {
private RuntimeTypeProvider messageProvider;
private Dispatcher dispatcher;
private Interpreter interpreter;
private Activation activation;
private String activationString;
private Object activationResolution;
private Object result;
private InterpreterException interpreterException;
private InterpreterException.Builder interpreterExceptionBuilder;
@Test
public void testCelInterface() throws Exception {
// Set up interpreter used in Cel library's eval function.
List<Descriptor> descriptors = new ArrayList<>();
messageProvider = DescriptorMessageProvider.dynamicMessages(descriptors);
dispatcher = DefaultDispatcher.create();
interpreter = new DefaultInterpreter(messageProvider, dispatcher);
// Set up activation used in Cel library's eval function.
Map<String, Object> map = new HashMap<>();
map.put("requestUrlPath", new Object());
map.put("requestHost", new Object());
map.put("requestMethod", new Object());
ImmutableMap<String, Object> apiAttributes = ImmutableMap.copyOf(map);
activation = Activation.copyOf(apiAttributes);
activationString = activation.toString();
activationResolution = activation.resolve("");
// Add a fake condition Expr that are being evaluated.
Expr conditions = Expr.newBuilder().build();
result = interpreter.createInterpretable(conditions).eval(activation);
assertThat(messageProvider).isNotNull();
assertThat(dispatcher).isNotNull();
assertThat(interpreter).isNotNull();
assertThat(activation).isNotNull();
assertThat(activationString).isNotNull();
assertThat(activationResolution).isNull();
assertThat(result).isNotNull();
}
@Test
public void testInterpreterException() {
interpreterExceptionBuilder = new InterpreterException.Builder("");
interpreterException = interpreterExceptionBuilder.build();
assertThat(interpreterExceptionBuilder).isNotNull();
assertThat(interpreterException).isNotNull();
}
}