Skip to content

Commit

Permalink
Merge remote-tracking branch 'es/main' into block_source_loader_ignor…
Browse files Browse the repository at this point in the history
…e_above
  • Loading branch information
martijnvg committed Oct 21, 2024
2 parents ccf3810 + 671458a commit c04ab86
Show file tree
Hide file tree
Showing 105 changed files with 2,375 additions and 686 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public enum DockerBase {
// Chainguard based wolfi image with latest jdk
// This is usually updated via renovatebot
// spotless:off
WOLFI("docker.elastic.co/wolfi/chainguard-base:latest@sha256:277ebb42c458ef39cb4028f9204f0b3d51d8cd628ea737a65696a1143c3e42fe",
WOLFI("docker.elastic.co/wolfi/chainguard-base:latest@sha256:bf163e1977002301f7b9fd28fe6837a8cb2dd5c83e4cd45fb67fb28d15d5d40f",
"-wolfi",
"apk"
),
Expand Down
6 changes: 1 addition & 5 deletions distribution/tools/entitlement-runtime/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ apply plugin: 'elasticsearch.publish'

dependencies {
compileOnly project(':libs:elasticsearch-core') // For @SuppressForbidden
compileOnly project(":libs:elasticsearch-x-content") // for parsing policy files
compileOnly project(':server') // To access the main server module for special permission checks
compileOnly project(':distribution:tools:entitlement-bridge')

testImplementation project(":test:framework")
}

tasks.named('forbiddenApisMain').configure {
replaceSignatureFiles 'jdk-signatures'
}

tasks.named('forbiddenApisMain').configure {
replaceSignatureFiles 'jdk-signatures'
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

module org.elasticsearch.entitlement.runtime {
requires org.elasticsearch.entitlement.bridge;
requires org.elasticsearch.xcontent;
requires org.elasticsearch.server;

exports org.elasticsearch.entitlement.runtime.api;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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;

/**
* Marker interface to ensure that only {@link Entitlement} are
* part of a {@link Policy}. All entitlement classes should implement
* this.
*/
public interface Entitlement {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation indicates an {@link Entitlement} is available
* to "external" classes such as those used in plugins. Any {@link Entitlement}
* using this annotation is considered parseable as part of a policy file
* for entitlements.
*/
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExternalEntitlement {

/**
* This is the list of parameter names that are
* parseable in {@link PolicyParser#parseEntitlement(String, String)}.
* The number and order of parameter names much match the number and order
* of constructor parameters as this is how the parser will pass in the
* parsed values from a policy file. However, the names themselves do NOT
* have to match the parameter names of the constructor.
*/
String[] parameterNames() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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;

import java.util.List;
import java.util.Objects;

/**
* Describes a file entitlement with a path and actions.
*/
public class FileEntitlement implements Entitlement {

public static final int READ_ACTION = 0x1;
public static final int WRITE_ACTION = 0x2;

private final String path;
private final int actions;

@ExternalEntitlement(parameterNames = { "path", "actions" })
public FileEntitlement(String path, List<String> actionsList) {
this.path = path;
int actionsInt = 0;

for (String actionString : actionsList) {
if ("read".equals(actionString)) {
if ((actionsInt & READ_ACTION) == READ_ACTION) {
throw new IllegalArgumentException("file action [read] specified multiple times");
}
actionsInt |= READ_ACTION;
} else if ("write".equals(actionString)) {
if ((actionsInt & WRITE_ACTION) == WRITE_ACTION) {
throw new IllegalArgumentException("file action [write] specified multiple times");
}
actionsInt |= WRITE_ACTION;
} else {
throw new IllegalArgumentException("unknown file action [" + actionString + "]");
}
}

this.actions = actionsInt;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileEntitlement that = (FileEntitlement) o;
return actions == that.actions && Objects.equals(path, that.path);
}

@Override
public int hashCode() {
return Objects.hash(path, actions);
}

@Override
public String toString() {
return "FileEntitlement{" + "path='" + path + '\'' + ", actions=" + actions + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* A holder for scoped entitlements.
*/
public class Policy {

public final String name;
public final List<Scope> scopes;

public Policy(String name, List<Scope> scopes) {
this.name = Objects.requireNonNull(name);
this.scopes = Collections.unmodifiableList(Objects.requireNonNull(scopes));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Policy policy = (Policy) o;
return Objects.equals(name, policy.name) && Objects.equals(scopes, policy.scopes);
}

@Override
public int hashCode() {
return Objects.hash(name, scopes);
}

@Override
public String toString() {
return "Policy{" + "name='" + name + '\'' + ", scopes=" + scopes + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* 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;

import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentParserConfiguration;
import org.elasticsearch.xcontent.yaml.YamlXContent;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException;

/**
* A parser to parse policy files for entitlements.
*/
public class PolicyParser {

protected static final ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements");

protected static final String entitlementPackageName = Entitlement.class.getPackage().getName();

protected final XContentParser policyParser;
protected final String policyName;

public PolicyParser(InputStream inputStream, String policyName) throws IOException {
this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream));
this.policyName = policyName;
}

public Policy parsePolicy() {
try {
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
throw newPolicyParserException("expected object <scope name>");
}
List<Scope> scopes = new ArrayList<>();
while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) {
throw newPolicyParserException("expected object <scope name>");
}
String scopeName = policyParser.currentName();
Scope scope = parseScope(scopeName);
scopes.add(scope);
}
return new Policy(policyName, scopes);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}

protected Scope parseScope(String scopeName) throws IOException {
try {
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
throw newPolicyParserException(scopeName, "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]");
}
if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME
|| policyParser.currentName().equals(ENTITLEMENTS_PARSEFIELD.getPreferredName()) == false) {
throw newPolicyParserException(scopeName, "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]");
}
if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) {
throw newPolicyParserException(scopeName, "expected array of <entitlement type>");
}
List<Entitlement> entitlements = new ArrayList<>();
while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) {
if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) {
throw newPolicyParserException(scopeName, "expected object <entitlement type>");
}
if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) {
throw newPolicyParserException(scopeName, "expected object <entitlement type>");
}
String entitlementType = policyParser.currentName();
Entitlement entitlement = parseEntitlement(scopeName, entitlementType);
entitlements.add(entitlement);
if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
throw newPolicyParserException(scopeName, "expected closing object");
}
}
if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
throw newPolicyParserException(scopeName, "expected closing object");
}
return new Scope(scopeName, entitlements);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}

protected Entitlement parseEntitlement(String scopeName, String entitlementType) throws IOException {
Class<?> entitlementClass;
try {
entitlementClass = Class.forName(
entitlementPackageName
+ "."
+ Character.toUpperCase(entitlementType.charAt(0))
+ entitlementType.substring(1)
+ "Entitlement"
);
} catch (ClassNotFoundException cnfe) {
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
}
if (Entitlement.class.isAssignableFrom(entitlementClass) == false) {
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
}
Constructor<?> entitlementConstructor = entitlementClass.getConstructors()[0];
ExternalEntitlement entitlementMetadata = entitlementConstructor.getAnnotation(ExternalEntitlement.class);
if (entitlementMetadata == null) {
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
}

if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
throw newPolicyParserException(scopeName, entitlementType, "expected entitlement parameters");
}
Map<String, Object> parsedValues = policyParser.map();

Class<?>[] parameterTypes = entitlementConstructor.getParameterTypes();
String[] parametersNames = entitlementMetadata.parameterNames();
Object[] parameterValues = new Object[parameterTypes.length];
for (int parameterIndex = 0; parameterIndex < parameterTypes.length; ++parameterIndex) {
String parameterName = parametersNames[parameterIndex];
Object parameterValue = parsedValues.remove(parameterName);
if (parameterValue == null) {
throw newPolicyParserException(scopeName, entitlementType, "missing entitlement parameter [" + parameterName + "]");
}
Class<?> parameterType = parameterTypes[parameterIndex];
if (parameterType.isAssignableFrom(parameterValue.getClass()) == false) {
throw newPolicyParserException(
scopeName,
entitlementType,
"unexpected parameter type [" + parameterType.getSimpleName() + "] for entitlement parameter [" + parameterName + "]"
);
}
parameterValues[parameterIndex] = parameterValue;
}
if (parsedValues.isEmpty() == false) {
throw newPolicyParserException(scopeName, entitlementType, "extraneous entitlement parameter(s) " + parsedValues);
}

try {
return (Entitlement) entitlementConstructor.newInstance(parameterValues);
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("internal error");
}
}

protected PolicyParserException newPolicyParserException(String message) {
return PolicyParserException.newPolicyParserException(policyParser.getTokenLocation(), policyName, message);
}

protected PolicyParserException newPolicyParserException(String scopeName, String message) {
return PolicyParserException.newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, message);
}

protected PolicyParserException newPolicyParserException(String scopeName, String entitlementType, String message) {
return PolicyParserException.newPolicyParserException(
policyParser.getTokenLocation(),
policyName,
scopeName,
entitlementType,
message
);
}
}
Loading

0 comments on commit c04ab86

Please sign in to comment.