Skip to content

Commit

Permalink
Add PolicyValdidationException for entitlements (#120225)
Browse files Browse the repository at this point in the history
This adds a new exception called PolicyValdidationException. This exception is intended to be used during construction of entitlements that require parsing additional arguments. This allows PolicyParser to give improved error messaging about a specific entitlement that may have failed during the parsing process.
  • Loading branch information
jdconrad authored Jan 17, 2025
1 parent e54bd24 commit 45e80f5
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public NetworkEntitlement(List<String> actionsList) {
for (String actionString : actionsList) {
var action = ACTION_MAP.get(actionString);
if (action == null) {
throw new IllegalArgumentException("unknown network action [" + actionString + "]");
throw new PolicyValidationException("unknown network action [" + actionString + "]");
}
if ((actionsInt & action) == action) {
throw new IllegalArgumentException(Strings.format("network action [%s] specified multiple times", actionString));
throw new PolicyValidationException(Strings.format("network action [%s] specified multiple times", actionString));
}
actionsInt |= action;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.elasticsearch.entitlement.runtime.policy;

import org.elasticsearch.xcontent.XContentLocation;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentParserConfiguration;
import org.elasticsearch.xcontent.yaml.YamlXContent;
Expand Down Expand Up @@ -119,6 +120,7 @@ protected Scope parseScope(String scopeName) throws IOException {
}

protected Entitlement parseEntitlement(String scopeName, String entitlementType) throws IOException {
XContentLocation startLocation = policyParser.getTokenLocation();
Class<?> entitlementClass = EXTERNAL_ENTITLEMENTS.get(entitlementType);

if (entitlementClass == null) {
Expand Down Expand Up @@ -170,7 +172,10 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType)
try {
return (Entitlement) entitlementConstructor.newInstance(parameterValues);
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("internal error");
if (e.getCause() instanceof PolicyValidationException piae) {
throw newPolicyParserException(startLocation, scopeName, entitlementType, piae);
}
throw new IllegalStateException("internal error", e);
}
}

Expand All @@ -191,4 +196,13 @@ protected PolicyParserException newPolicyParserException(String scopeName, Strin
message
);
}

protected PolicyParserException newPolicyParserException(
XContentLocation location,
String scopeName,
String entitlementType,
PolicyValidationException cause
) {
return PolicyParserException.newPolicyParserException(location, policyName, scopeName, entitlementType, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,36 @@ public static PolicyParserException newPolicyParserException(
}
}

public static PolicyParserException newPolicyParserException(
XContentLocation location,
String policyName,
String scopeName,
String entitlementType,
PolicyValidationException cause
) {
assert (scopeName != null);
return new PolicyParserException(
"["
+ location.lineNumber()
+ ":"
+ location.columnNumber()
+ "] policy parsing error for ["
+ policyName
+ "] in scope ["
+ scopeName
+ "] for entitlement type ["
+ entitlementType
+ "]: "
+ cause.getMessage(),
cause
);
}

private PolicyParserException(String message) {
super(message);
}

private PolicyParserException(String message, PolicyValidationException cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.entitlement.runtime.policy;

/**
* This exception is used to track validation errors thrown during the construction
* of entitlements. By using this instead of other exception types the policy
* parser is able to wrap this exception with a line/character number for
* additional useful error information.
*/
class PolicyValidationException extends RuntimeException {

PolicyValidationException(String message) {
super(message);
}

PolicyValidationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ public void testParseNetwork() throws IOException {
assertEquals(expected, parsedPolicy);
}

public void testParseNetworkIllegalAction() throws IOException {
var ex = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream("""
entitlement-module-name:
- network:
actions:
- listen
- doesnotexist
- connect
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", false).parsePolicy());
assertThat(
ex.getMessage(),
equalTo(
"[2:5] policy parsing error for [test-policy.yaml] in scope [entitlement-module-name] for entitlement type [network]: "
+ "unknown network action [doesnotexist]"
)
);
}

public void testParseCreateClassloader() throws IOException {
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
entitlement-module-name:
Expand Down

0 comments on commit 45e80f5

Please sign in to comment.