Skip to content

Conversation

@jdconrad
Copy link
Contributor

@jdconrad jdconrad commented Feb 21, 2025

This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain module. Should two modules attempt to both specify the same path as exclusive an exception is thrown.

ES-10845

@jdconrad jdconrad requested a review from a team as a code owner February 21, 2025 00:33
@elasticsearchmachine elasticsearchmachine added the Team:Core/Infra Meta label for core/infra team label Feb 21, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questions

var paths = fileData.resolvePaths(pathLookup);
paths.forEach(path -> {
var normalized = normalizePath(path);
for (String exclusivePath : exclusivePaths) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we normalize the exclusive path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets normalized as part of the work done in PolicyManager. However, we could pass in a List<Path> here as you suggested in a later comment and just do the same work again to be on the safe side. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked with @rjernst and I will change this to use Paths for validation here and normalize in FileAccessTree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to use Path via a record indicating component and module as well.

* structures to indicate other modules aren't allowed to use these
* files in {@link FileAccessTree}s.
*/
private final List<String> exclusivePaths;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this need to retain which module has access to the exclusive path, so that when that module's entitlements are initialized we can omit that exclusion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list should already be validated, so that we can safely assume any read or read_write path when we constructor the module's entitlements found as an exclusive path is for that specific module already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up adding component name and module name to check which exclusive paths should be added for each FileAccessTree to ensure paths are safely added instead of making tricky assumptions.

if (fd.exclusive()) {
List<Path> paths = fd.resolvePaths(pathLookup).toList();
for (Path path : paths) {
String pathStr = FileAccessTree.normalizePath(path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we retain this as Path to pass into FileAccessTree, so that it can do the normalization internally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to normalize here to be able to validate. I'm certainly open to alternatives if you have a good one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change this based on previous comments.

FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE),
FileData.ofPath(bootstrapArgs.repoDirResolver().apply(""), READ_WRITE),
FileData.ofPath(bootstrapArgs.tempDir(), READ_WRITE, false),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might benefit from an overload of ofPath that assumes false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that, and I think you're right. I'll go ahead and add additional ofPaths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a ton of overloads (which would fall to combinatorial overload if we add the platform parameter discussed separately), I wonder if FileData could have withExclusive(boolean). Then each of the 4 impls we have can recreate themselves with exclusive changed. But the ofXXX methods can still be simple as they are now. IMO this is preferable since setting exclusive to true is rare.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did end up changing this to @rjernst's suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's even better.

prunedReadPaths.add(currentPath);
for (int i = 1; i < paths.size(); ++i) {
String nextPath = paths.get(i);
if (nextPath.equals(currentPath) == false && nextPath.startsWith(currentPath) == false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to check equals because startsWith includes that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:) I will fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was merged as part of #123177 without the .equals

private static boolean checkPath(String path, String[] paths) {
if (paths.length == 0) {
private boolean checkPath(String path, String[] paths) {
if (paths.length == 0 || Arrays.binarySearch(exclusivePaths, path) >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone configure exclusive access to a directory? If so, this check is insufficient I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please double check this logic for me -- I intend to throw in the constructor for FileAccessTree if we catch a read or read_write path that would require exclusive access to a directory for a read or read_write path. In which case, that leaves the exact match for an exclusive path in an existing read or read_write directory or it will already have thrown in the constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gosh. Tricky.

Ok, suppose:

  • X has read access to /a
  • Y has exclusive access to /a/b
  • X attempts to read /a/b/c

I believe this combo would not be caught in the constructor (because it's valid) and we would fail to detect the violation because it's not an exact file name match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I will add this test and move the logic :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this. Thanks for showing me what was incorrect with a simple example.

@jdconrad
Copy link
Contributor Author

@rjernst and @prdoyle Thank you both for the reviews. I will continue working on this code.

@jdconrad jdconrad changed the title [WIP] Add an exclusive parameter for files entitlements Add an exclusive parameter for files entitlements Feb 25, 2025
@jdconrad
Copy link
Contributor Author

@rjernst @prdoyle I have added tests and am ready for another round of reviews.

Copy link
Contributor

@prdoyle prdoyle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mindboggling

+ "] ["
+ currentExclusivePath.moduleName()
+ "] ["
+ currentExclusivePath.path()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Minor: I suppose it could be handy if we override ExclusivePath.toString to have this format.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

ExclusivePath currentExclusivePath = exclusivePaths.get(0);
for (int i = 1; i < exclusivePaths.size(); ++i) {
ExclusivePath nextPath = exclusivePaths.get(i);
if (currentExclusivePath.path().equals(nextPath.path) || isParent(currentExclusivePath.path(), nextPath.path())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is pretty subtle.

Assuming the incoming list of paths has been sorted by PATH_ORDER, that does not ensure that a child is always immediately after its parent; but it does ensure that any list of paths containing one or more parent-child pairs will have at least one of those pairs adjacent, and that's enough!

For example:

  /a
  /a/b
  /a/c

The child /a/c is not next to its parent /a, but for the sake of validation, that's ok, because to have something else (like /a/b) interleave, that must itself be a child (or descendant) of /a, and so the validation will fail on that.

I'm 95% sure this reasoning works and we're not missing a case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be path order and since that moves file separators to be the first character this should work. This works similarly to pruning and I think has the same guarantees here. Thanks for checking it :)

private static boolean checkPath(String path, String[] paths) {
if (paths.length == 0) {
private boolean checkPath(String path, String[] paths) {
if (paths.length == 0 || Arrays.binarySearch(exclusivePaths, path) >= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gosh. Tricky.

Ok, suppose:

  • X has read access to /a
  • Y has exclusive access to /a/b
  • X attempts to read /a/b/c

I believe this combo would not be caught in the constructor (because it's valid) and we would fail to detect the violation because it's not an exact file name match.

this.entitlementsModule = entitlementsModule;
this.pathLookup = requireNonNull(pathLookup);
this.defaultFileAccess = FileAccessTree.of(FilesEntitlement.EMPTY, pathLookup);
this.defaultFileAccess = FileAccessTree.of("", "", FilesEntitlement.EMPTY, pathLookup, List.of());
Copy link
Contributor

@prdoyle prdoyle Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These empty strings give me the willies, but I'm not sure what to replace them with. 🤔

I'm fine with them if everyone else is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched this to use to the existing (unknown)

String componentName,
String moduleName,
List<Entitlement> entitlements,
List<ExclusiveFileEntitlement> exclusiveFileEntitlements
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is an "out parameter"? That probably deserves a javadoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@jdconrad jdconrad added the auto-backport Automatically create backport pull requests when merged label Feb 25, 2025
Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

String modeAsString = (String) file.remove("mode");
String platformAsString = (String) file.remove("platform");
String ignoreUrlAsString = (String) file.remove("ignore_url");
Boolean exclusiveBoolean = (Boolean) file.remove("exclusive");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a better error message in case this is a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@jdconrad jdconrad merged commit 3e3dfa4 into elastic:main Feb 27, 2025
22 checks passed
jdconrad added a commit to jdconrad/elasticsearch that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
@elasticsearchmachine
Copy link
Collaborator

💚 Backport successful

Status Branch Result
8.18
8.x
9.0

jdconrad added a commit to jdconrad/elasticsearch that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
jdconrad added a commit to jdconrad/elasticsearch that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
elasticsearchmachine pushed a commit that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
elasticsearchmachine pushed a commit that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
elasticsearchmachine pushed a commit that referenced this pull request Feb 27, 2025
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
tvernum added a commit to tvernum/elasticsearch that referenced this pull request Jun 20, 2025
With the introduction of entitlements (elastic#120243) and exclusive file
access (elastic#123087) it is no longer safe to watch a whole directory.

In a lot of deployments, the parent directory for SSL config files
will be the main config directory, which also contains exclusive files
such as SAML realm metadata or File realm users. Watching that
directory will cause entitlement warnings because it is not
permissible for core/ssl-config to read files that are exclusively
owned by the security module (or other modules)
tvernum added a commit that referenced this pull request Jun 25, 2025
With the introduction of entitlements (#120243) and exclusive file
access (#123087) it is no longer safe to watch a whole directory.

In a lot of deployments, the parent directory for SSL config files
will be the main config directory, which also contains exclusive files
such as SAML realm metadata or File realm users. Watching that
directory will cause entitlement warnings because it is not
permissible for core/ssl-config to read files that are exclusively
owned by the security module (or other modules)
elasticsearchmachine pushed a commit that referenced this pull request Jun 25, 2025
With the introduction of entitlements (#120243) and exclusive file
access (#123087) it is no longer safe to watch a whole directory.

In a lot of deployments, the parent directory for SSL config files
will be the main config directory, which also contains exclusive files
such as SAML realm metadata or File realm users. Watching that
directory will cause entitlement warnings because it is not
permissible for core/ssl-config to read files that are exclusively
owned by the security module (or other modules)
mridula-s109 pushed a commit to mridula-s109/elasticsearch that referenced this pull request Jun 25, 2025
With the introduction of entitlements (elastic#120243) and exclusive file
access (elastic#123087) it is no longer safe to watch a whole directory.

In a lot of deployments, the parent directory for SSL config files
will be the main config directory, which also contains exclusive files
such as SAML realm metadata or File realm users. Watching that
directory will cause entitlement warnings because it is not
permissible for core/ssl-config to read files that are exclusively
owned by the security module (or other modules)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-backport Automatically create backport pull requests when merged :Core/Infra/Entitlements Entitlements infrastructure >non-issue Team:Core/Infra Meta label for core/infra team v8.18.1 v8.19.0 v9.0.1 v9.1.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants