mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
Move FilesEntitlements validation to a separate class (#127703)
Moves FilesEntitlements validation to a separate class. This is the final PR to make EntitlementsInitialization a simpler "orchestrator" of the various steps in the initialization phase.
This commit is contained in:
parent
64568ee59e
commit
f90b01597c
3 changed files with 104 additions and 77 deletions
|
@ -10,11 +10,9 @@
|
||||||
package org.elasticsearch.entitlement.initialization;
|
package org.elasticsearch.entitlement.initialization;
|
||||||
|
|
||||||
import org.elasticsearch.core.Booleans;
|
import org.elasticsearch.core.Booleans;
|
||||||
import org.elasticsearch.core.Strings;
|
|
||||||
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
||||||
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree;
|
|
||||||
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.Policy;
|
import org.elasticsearch.entitlement.runtime.policy.Policy;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
|
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
|
||||||
|
@ -39,7 +37,6 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -248,7 +245,7 @@ public class EntitlementInitialization {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
validateFilesEntitlements(pluginPolicies, pathLookup);
|
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
|
||||||
|
|
||||||
return new PolicyManager(
|
return new PolicyManager(
|
||||||
serverPolicy,
|
serverPolicy,
|
||||||
|
@ -262,74 +259,6 @@ public class EntitlementInitialization {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// package visible for tests
|
|
||||||
static void validateFilesEntitlements(Map<String, Policy> pluginPolicies, PathLookup pathLookup) {
|
|
||||||
Set<Path> readAccessForbidden = new HashSet<>();
|
|
||||||
pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
|
||||||
pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
|
||||||
pathLookup.getBaseDirPaths(LIB).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
|
||||||
Set<Path> writeAccessForbidden = new HashSet<>();
|
|
||||||
pathLookup.getBaseDirPaths(CONFIG).forEach(p -> writeAccessForbidden.add(p.toAbsolutePath().normalize()));
|
|
||||||
for (var pluginPolicy : pluginPolicies.entrySet()) {
|
|
||||||
for (var scope : pluginPolicy.getValue().scopes()) {
|
|
||||||
var filesEntitlement = scope.entitlements()
|
|
||||||
.stream()
|
|
||||||
.filter(x -> x instanceof FilesEntitlement)
|
|
||||||
.map(x -> ((FilesEntitlement) x))
|
|
||||||
.findFirst();
|
|
||||||
if (filesEntitlement.isPresent()) {
|
|
||||||
var fileAccessTree = FileAccessTree.withoutExclusivePaths(filesEntitlement.get(), pathLookup, null);
|
|
||||||
validateReadFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, readAccessForbidden);
|
|
||||||
validateWriteFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, writeAccessForbidden);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IllegalArgumentException buildValidationException(
|
|
||||||
String componentName,
|
|
||||||
String moduleName,
|
|
||||||
Path forbiddenPath,
|
|
||||||
FilesEntitlement.Mode mode
|
|
||||||
) {
|
|
||||||
return new IllegalArgumentException(
|
|
||||||
Strings.format(
|
|
||||||
"policy for module [%s] in [%s] has an invalid file entitlement. Any path under [%s] is forbidden for mode [%s].",
|
|
||||||
moduleName,
|
|
||||||
componentName,
|
|
||||||
forbiddenPath,
|
|
||||||
mode
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void validateReadFilesEntitlements(
|
|
||||||
String componentName,
|
|
||||||
String moduleName,
|
|
||||||
FileAccessTree fileAccessTree,
|
|
||||||
Set<Path> readForbiddenPaths
|
|
||||||
) {
|
|
||||||
|
|
||||||
for (Path forbiddenPath : readForbiddenPaths) {
|
|
||||||
if (fileAccessTree.canRead(forbiddenPath)) {
|
|
||||||
throw buildValidationException(componentName, moduleName, forbiddenPath, READ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void validateWriteFilesEntitlements(
|
|
||||||
String componentName,
|
|
||||||
String moduleName,
|
|
||||||
FileAccessTree fileAccessTree,
|
|
||||||
Set<Path> writeForbiddenPaths
|
|
||||||
) {
|
|
||||||
for (Path forbiddenPath : writeForbiddenPaths) {
|
|
||||||
if (fileAccessTree.canWrite(forbiddenPath)) {
|
|
||||||
throw buildValidationException(componentName, moduleName, forbiddenPath, READ_WRITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
|
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
|
||||||
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
|
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* 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.initialization;
|
||||||
|
|
||||||
|
import org.elasticsearch.core.Strings;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.Policy;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.LIB;
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.MODULES;
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.PLUGINS;
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
|
||||||
|
|
||||||
|
class FilesEntitlementsValidation {
|
||||||
|
|
||||||
|
static void validate(Map<String, Policy> pluginPolicies, PathLookup pathLookup) {
|
||||||
|
Set<Path> readAccessForbidden = new HashSet<>();
|
||||||
|
pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
||||||
|
pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
||||||
|
pathLookup.getBaseDirPaths(LIB).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize()));
|
||||||
|
Set<Path> writeAccessForbidden = new HashSet<>();
|
||||||
|
pathLookup.getBaseDirPaths(CONFIG).forEach(p -> writeAccessForbidden.add(p.toAbsolutePath().normalize()));
|
||||||
|
for (var pluginPolicy : pluginPolicies.entrySet()) {
|
||||||
|
for (var scope : pluginPolicy.getValue().scopes()) {
|
||||||
|
var filesEntitlement = scope.entitlements()
|
||||||
|
.stream()
|
||||||
|
.filter(x -> x instanceof FilesEntitlement)
|
||||||
|
.map(x -> ((FilesEntitlement) x))
|
||||||
|
.findFirst();
|
||||||
|
if (filesEntitlement.isPresent()) {
|
||||||
|
var fileAccessTree = FileAccessTree.withoutExclusivePaths(filesEntitlement.get(), pathLookup, null);
|
||||||
|
validateReadFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, readAccessForbidden);
|
||||||
|
validateWriteFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, writeAccessForbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IllegalArgumentException buildValidationException(
|
||||||
|
String componentName,
|
||||||
|
String moduleName,
|
||||||
|
Path forbiddenPath,
|
||||||
|
FilesEntitlement.Mode mode
|
||||||
|
) {
|
||||||
|
return new IllegalArgumentException(
|
||||||
|
Strings.format(
|
||||||
|
"policy for module [%s] in [%s] has an invalid file entitlement. Any path under [%s] is forbidden for mode [%s].",
|
||||||
|
moduleName,
|
||||||
|
componentName,
|
||||||
|
forbiddenPath,
|
||||||
|
mode
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateReadFilesEntitlements(
|
||||||
|
String componentName,
|
||||||
|
String moduleName,
|
||||||
|
FileAccessTree fileAccessTree,
|
||||||
|
Set<Path> readForbiddenPaths
|
||||||
|
) {
|
||||||
|
|
||||||
|
for (Path forbiddenPath : readForbiddenPaths) {
|
||||||
|
if (fileAccessTree.canRead(forbiddenPath)) {
|
||||||
|
throw buildValidationException(componentName, moduleName, forbiddenPath, READ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateWriteFilesEntitlements(
|
||||||
|
String componentName,
|
||||||
|
String moduleName,
|
||||||
|
FileAccessTree fileAccessTree,
|
||||||
|
Set<Path> writeForbiddenPaths
|
||||||
|
) {
|
||||||
|
for (Path forbiddenPath : writeForbiddenPaths) {
|
||||||
|
if (fileAccessTree.canWrite(forbiddenPath)) {
|
||||||
|
throw buildValidationException(componentName, moduleName, forbiddenPath, READ_WRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ import static org.hamcrest.Matchers.both;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class EntitlementInitializationTests extends ESTestCase {
|
public class FilesEntitlementsValidationTests extends ESTestCase {
|
||||||
|
|
||||||
private static PathLookup TEST_PATH_LOOKUP;
|
private static PathLookup TEST_PATH_LOOKUP;
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ public class EntitlementInitializationTests extends ESTestCase {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP);
|
FilesEntitlementsValidation.validate(Map.of("plugin", policy), TEST_PATH_LOOKUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testValidationFailForRead() {
|
public void testValidationFailForRead() {
|
||||||
|
@ -94,7 +94,7 @@ public class EntitlementInitializationTests extends ESTestCase {
|
||||||
|
|
||||||
var ex = expectThrows(
|
var ex = expectThrows(
|
||||||
IllegalArgumentException.class,
|
IllegalArgumentException.class,
|
||||||
() -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP)
|
() -> FilesEntitlementsValidation.validate(Map.of("plugin", policy), TEST_PATH_LOOKUP)
|
||||||
);
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
ex.getMessage(),
|
ex.getMessage(),
|
||||||
|
@ -119,7 +119,7 @@ public class EntitlementInitializationTests extends ESTestCase {
|
||||||
|
|
||||||
ex = expectThrows(
|
ex = expectThrows(
|
||||||
IllegalArgumentException.class,
|
IllegalArgumentException.class,
|
||||||
() -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin2", policy2), TEST_PATH_LOOKUP)
|
() -> FilesEntitlementsValidation.validate(Map.of("plugin2", policy2), TEST_PATH_LOOKUP)
|
||||||
);
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
ex.getMessage(),
|
ex.getMessage(),
|
||||||
|
@ -145,7 +145,7 @@ public class EntitlementInitializationTests extends ESTestCase {
|
||||||
|
|
||||||
var ex = expectThrows(
|
var ex = expectThrows(
|
||||||
IllegalArgumentException.class,
|
IllegalArgumentException.class,
|
||||||
() -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP)
|
() -> FilesEntitlementsValidation.validate(Map.of("plugin", policy), TEST_PATH_LOOKUP)
|
||||||
);
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
ex.getMessage(),
|
ex.getMessage(),
|
Loading…
Add table
Add a link
Reference in a new issue