Skip to content

Commit

Permalink
Merge branch 'main' into license-feature-usage-distinguish-by-family
Browse files Browse the repository at this point in the history
  • Loading branch information
n1v0lg authored Nov 21, 2024
2 parents fa743ee + 35d6af6 commit 740473d
Show file tree
Hide file tree
Showing 41 changed files with 581 additions and 397 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/117153.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 117153
summary: "ESQL: fix the column position in errors"
area: ES|QL
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,41 @@
import com.sun.tools.attach.VirtualMachine;

import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;

public class EntitlementBootstrap {

public record BootstrapArgs(Collection<Tuple<Path, Boolean>> pluginData, Function<Class<?>, String> pluginResolver) {}

private static BootstrapArgs bootstrapArgs;

public static BootstrapArgs bootstrapArgs() {
return bootstrapArgs;
}

/**
* Activates entitlement checking. Once this method returns, calls to forbidden methods
* will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
* Activates entitlement checking. Once this method returns, calls to methods protected by Entitlements from classes without a valid
* policy will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
* @param pluginData a collection of (plugin path, boolean), that holds the paths of all the installed Elasticsearch modules and
* plugins, and whether they are Java modular or not.
* @param pluginResolver a functor to map a Java Class to the plugin it belongs to (the plugin name).
*/
public static void bootstrap() {
public static void bootstrap(Collection<Tuple<Path, Boolean>> pluginData, Function<Class<?>, String> pluginResolver) {
logger.debug("Loading entitlement agent");
if (EntitlementBootstrap.bootstrapArgs != null) {
throw new IllegalStateException("plugin data is already set");
}
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(Objects.requireNonNull(pluginData), Objects.requireNonNull(pluginResolver));
exportInitializationToAgent();
loadAgent(findAgentJar());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,36 @@

package org.elasticsearch.entitlement.initialization;

import org.elasticsearch.core.Tuple;
import org.elasticsearch.core.internal.provider.ProviderLocator;
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.entitlement.instrumentation.CheckerMethod;
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
import org.elasticsearch.entitlement.instrumentation.MethodKey;
import org.elasticsearch.entitlement.instrumentation.Transformer;
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
import org.elasticsearch.entitlement.runtime.policy.Scope;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;

/**
* Called by the agent during {@code agentmain} to configure the entitlement system,
* instantiate and configure an {@link EntitlementChecker},
Expand All @@ -30,6 +47,9 @@
* to begin injecting our instrumentation.
*/
public class EntitlementInitialization {

private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";

private static ElasticsearchEntitlementChecker manager;

// Note: referenced by bridge reflectively
Expand All @@ -39,7 +59,7 @@ public static EntitlementChecker checker() {

// Note: referenced by agent reflectively
public static void initialize(Instrumentation inst) throws Exception {
manager = new ElasticsearchEntitlementChecker();
manager = new ElasticsearchEntitlementChecker(createPolicyManager());

Map<MethodKey, CheckerMethod> methodMap = INSTRUMENTER_FACTORY.lookupMethodsToInstrument(
"org.elasticsearch.entitlement.bridge.EntitlementChecker"
Expand All @@ -61,6 +81,66 @@ private static Class<?> internalNameToClass(String internalName) {
}
}

private static PolicyManager createPolicyManager() throws IOException {
Map<String, Policy> pluginPolicies = createPluginPolicies(EntitlementBootstrap.bootstrapArgs().pluginData());

// TODO: What should the name be?
// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
var serverPolicy = new Policy("server", List.of());
return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver());
}

private static Map<String, Policy> createPluginPolicies(Collection<Tuple<Path, Boolean>> pluginData) throws IOException {
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
for (Tuple<Path, Boolean> entry : pluginData) {
Path pluginRoot = entry.v1();
boolean isModular = entry.v2();

String pluginName = pluginRoot.getFileName().toString();
final Policy policy = loadPluginPolicy(pluginRoot, isModular, pluginName);

pluginPolicies.put(pluginName, policy);
}
return pluginPolicies;
}

private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName) throws IOException {
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);

final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
final Policy policy = parsePolicyIfExists(pluginName, policyFile);

// TODO: should this check actually be part of the parser?
for (Scope scope : policy.scopes) {
if (moduleNames.contains(scope.name) == false) {
throw new IllegalStateException("policy [" + policyFile + "] contains invalid module [" + scope.name + "]");
}
}
return policy;
}

private static Policy parsePolicyIfExists(String pluginName, Path policyFile) throws IOException {
if (Files.exists(policyFile)) {
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName).parsePolicy();
}
return new Policy(pluginName, List.of());
}

private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
if (isModular) {
ModuleFinder moduleFinder = ModuleFinder.of(pluginRoot);
Set<ModuleReference> moduleReferences = moduleFinder.findAll();

return moduleReferences.stream().map(mr -> mr.descriptor().name()).collect(Collectors.toUnmodifiableSet());
}
// When isModular == false we use the same "ALL-UNNAMED" constant as the JDK to indicate (any) unnamed module for this plugin
return Set.of(ALL_UNNAMED);
}

private static String internalName(Class<?> c) {
return c.getName().replace('.', '/');
}

private static final InstrumentationService INSTRUMENTER_FACTORY = new ProviderLocator<>(
"entitlement",
InstrumentationService.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,85 +10,23 @@
package org.elasticsearch.entitlement.runtime.api;

import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;

import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.entitlement.runtime.policy.FlagEntitlementType;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;

/**
* Implementation of the {@link EntitlementChecker} interface, providing additional
* API methods for managing the checks.
* The trampoline module loads this object via SPI.
*/
public class ElasticsearchEntitlementChecker implements EntitlementChecker {
private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class);

private static final Set<Module> systemModules = findSystemModules();

private static Set<Module> findSystemModules() {
var systemModulesDescriptors = ModuleFinder.ofSystem()
.findAll()
.stream()
.map(ModuleReference::descriptor)
.collect(Collectors.toUnmodifiableSet());
private final PolicyManager policyManager;

return ModuleLayer.boot()
.modules()
.stream()
.filter(m -> systemModulesDescriptors.contains(m.getDescriptor()))
.collect(Collectors.toUnmodifiableSet());
public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
this.policyManager = policyManager;
}

@Override
public void check$java_lang_System$exit(Class<?> callerClass, int status) {
var requestingModule = requestingModule(callerClass);
if (isTriviallyAllowed(requestingModule)) {
return;
}

// TODO: this will be checked using policies
if (requestingModule.isNamed() && requestingModule.getName().equals("org.elasticsearch.server")) {
logger.debug("Allowed: caller in {} is entitled to exit the JVM", requestingModule.getName());
return;
}

// Hard-forbidden until we develop the permission granting scheme
throw new NotEntitledException("Missing entitlement for " + requestingModule);
}

private static Module requestingModule(Class<?> callerClass) {
if (callerClass != null) {
Module callerModule = callerClass.getModule();
if (systemModules.contains(callerModule) == false) {
// fast path
return callerModule;
}
}
int framesToSkip = 1 // getCallingClass (this method)
+ 1 // the checkXxx method
+ 1 // the runtime config method
+ 1 // the instrumented method
;
Optional<Module> module = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(
s -> s.skip(framesToSkip)
.map(f -> f.getDeclaringClass().getModule())
.filter(m -> systemModules.contains(m) == false)
.findFirst()
);
return module.orElse(null);
}

private static boolean isTriviallyAllowed(Module requestingModule) {
if (requestingModule == null) {
logger.debug("Trivially allowed: entire call stack is in composed of classes in system modules");
return true;
}
logger.trace("Not trivially allowed");
return false;
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.SYSTEM_EXIT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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;

public enum FlagEntitlementType {
SYSTEM_EXIT;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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.core.Strings;
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;

import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class PolicyManager {
private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class);

protected final Policy serverPolicy;
protected final Map<String, Policy> pluginPolicies;
private final Function<Class<?>, String> pluginResolver;

public static final String ALL_UNNAMED = "ALL-UNNAMED";

private static final Set<Module> systemModules = findSystemModules();

private static Set<Module> findSystemModules() {
var systemModulesDescriptors = ModuleFinder.ofSystem()
.findAll()
.stream()
.map(ModuleReference::descriptor)
.collect(Collectors.toUnmodifiableSet());

return ModuleLayer.boot()
.modules()
.stream()
.filter(m -> systemModulesDescriptors.contains(m.getDescriptor()))
.collect(Collectors.toUnmodifiableSet());
}

public PolicyManager(Policy defaultPolicy, Map<String, Policy> pluginPolicies, Function<Class<?>, String> pluginResolver) {
this.serverPolicy = Objects.requireNonNull(defaultPolicy);
this.pluginPolicies = Collections.unmodifiableMap(Objects.requireNonNull(pluginPolicies));
this.pluginResolver = pluginResolver;
}

public void checkFlagEntitlement(Class<?> callerClass, FlagEntitlementType type) {
var requestingModule = requestingModule(callerClass);
if (isTriviallyAllowed(requestingModule)) {
return;
}

// TODO: real policy check. For now, we only allow our hardcoded System.exit policy for server.
// TODO: this will be checked using policies
if (requestingModule.isNamed()
&& requestingModule.getName().equals("org.elasticsearch.server")
&& type == FlagEntitlementType.SYSTEM_EXIT) {
logger.debug("Allowed: caller [{}] in module [{}] has entitlement [{}]", callerClass, requestingModule.getName(), type);
return;
}

// TODO: plugins policy check using pluginResolver and pluginPolicies
throw new NotEntitledException(
Strings.format("Missing entitlement [%s] for caller [%s] in module [%s]", type, callerClass, requestingModule.getName())
);
}

private static Module requestingModule(Class<?> callerClass) {
if (callerClass != null) {
Module callerModule = callerClass.getModule();
if (systemModules.contains(callerModule) == false) {
// fast path
return callerModule;
}
}
int framesToSkip = 1 // getCallingClass (this method)
+ 1 // the checkXxx method
+ 1 // the runtime config method
+ 1 // the instrumented method
;
Optional<Module> module = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(
s -> s.skip(framesToSkip)
.map(f -> f.getDeclaringClass().getModule())
.filter(m -> systemModules.contains(m) == false)
.findFirst()
);
return module.orElse(null);
}

private static boolean isTriviallyAllowed(Module requestingModule) {
if (requestingModule == null) {
logger.debug("Trivially allowed: entire call stack is in composed of classes in system modules");
return true;
}
logger.trace("Not trivially allowed");
return false;
}

@Override
public String toString() {
return "PolicyManager{" + "serverPolicy=" + serverPolicy + ", pluginPolicies=" + pluginPolicies + '}';
}
}
Loading

0 comments on commit 740473d

Please sign in to comment.