1010package org .elasticsearch .entitlement .runtime .policy .entitlements ;
1111
1212import org .elasticsearch .entitlement .runtime .policy .ExternalEntitlement ;
13+ import org .elasticsearch .entitlement .runtime .policy .PathLookup ;
1314import org .elasticsearch .entitlement .runtime .policy .PolicyValidationException ;
1415
16+ import java .nio .file .Path ;
1517import java .util .ArrayList ;
18+ import java .util .Arrays ;
1619import java .util .HashMap ;
1720import java .util .List ;
1821import java .util .Map ;
22+ import java .util .Objects ;
23+ import java .util .stream .Stream ;
1924
2025/**
2126 * Describes a file entitlement with a path and mode.
@@ -29,8 +34,104 @@ public enum Mode {
2934 READ_WRITE
3035 }
3136
32- public record FileData (String path , Mode mode ) {
37+ public enum BaseDir {
38+ CONFIG ,
39+ DATA
40+ }
41+
42+ public sealed interface FileData {
43+
44+ final class AbsolutePathFileData implements FileData {
45+ private final Path path ;
46+ private final Mode mode ;
47+
48+ private AbsolutePathFileData (Path path , Mode mode ) {
49+ this .path = path ;
50+ this .mode = mode ;
51+ }
52+
53+ @ Override
54+ public Stream <Path > resolvePaths (PathLookup pathLookup ) {
55+ return Stream .of (path );
56+ }
57+
58+ @ Override
59+ public Mode mode () {
60+ return mode ;
61+ }
62+
63+ @ Override
64+ public boolean equals (Object obj ) {
65+ if (obj == this ) return true ;
66+ if (obj == null || obj .getClass () != this .getClass ()) return false ;
67+ var that = (AbsolutePathFileData ) obj ;
68+ return Objects .equals (this .path , that .path ) && Objects .equals (this .mode , that .mode );
69+ }
70+
71+ @ Override
72+ public int hashCode () {
73+ return Objects .hash (path , mode );
74+ }
75+ }
76+
77+ final class RelativePathFileData implements FileData {
78+ private final Path relativePath ;
79+ private final BaseDir baseDir ;
80+ private final Mode mode ;
81+
82+ private RelativePathFileData (Path relativePath , BaseDir baseDir , Mode mode ) {
83+ this .relativePath = relativePath ;
84+ this .baseDir = baseDir ;
85+ this .mode = mode ;
86+ }
87+
88+ @ Override
89+ public Stream <Path > resolvePaths (PathLookup pathLookup ) {
90+ Objects .requireNonNull (pathLookup );
91+ switch (baseDir ) {
92+ case CONFIG :
93+ return Stream .of (pathLookup .configDir ().resolve (relativePath ));
94+ case DATA :
95+ return Arrays .stream (pathLookup .dataDirs ()).map (d -> d .resolve (relativePath ));
96+ default :
97+ throw new IllegalArgumentException ();
98+ }
99+ }
100+
101+ @ Override
102+ public Mode mode () {
103+ return mode ;
104+ }
33105
106+ @ Override
107+ public boolean equals (Object obj ) {
108+ if (obj == this ) return true ;
109+ if (obj == null || obj .getClass () != this .getClass ()) return false ;
110+ var that = (RelativePathFileData ) obj ;
111+ return Objects .equals (this .mode , that .mode )
112+ && Objects .equals (this .relativePath , that .relativePath )
113+ && Objects .equals (this .baseDir , that .baseDir );
114+ }
115+
116+ @ Override
117+ public int hashCode () {
118+ return Objects .hash (relativePath , baseDir , mode );
119+ }
120+ }
121+
122+ static FileData ofPath (Path path , Mode mode ) {
123+ assert path .isAbsolute ();
124+ return new AbsolutePathFileData (path , mode );
125+ }
126+
127+ static FileData ofRelativePath (Path relativePath , BaseDir baseDir , Mode mode ) {
128+ assert relativePath .isAbsolute () == false ;
129+ return new RelativePathFileData (relativePath , baseDir , mode );
130+ }
131+
132+ Stream <Path > resolvePaths (PathLookup pathLookup );
133+
134+ Mode mode ();
34135 }
35136
36137 private static Mode parseMode (String mode ) {
@@ -43,6 +144,15 @@ private static Mode parseMode(String mode) {
43144 }
44145 }
45146
147+ private static BaseDir parseBaseDir (String baseDir ) {
148+ if (baseDir .equals ("config" )) {
149+ return BaseDir .CONFIG ;
150+ } else if (baseDir .equals ("data" )) {
151+ return BaseDir .DATA ;
152+ }
153+ throw new PolicyValidationException ("invalid relative directory: " + baseDir + ", valid values: [config, data]" );
154+ }
155+
46156 @ ExternalEntitlement (parameterNames = { "paths" }, esModulesOnly = false )
47157 @ SuppressWarnings ("unchecked" )
48158 public static FilesEntitlement build (List <Object > paths ) {
@@ -52,18 +162,41 @@ public static FilesEntitlement build(List<Object> paths) {
52162 List <FileData > filesData = new ArrayList <>();
53163 for (Object object : paths ) {
54164 Map <String , String > file = new HashMap <>((Map <String , String >) object );
55- String path = file .remove ("path" );
56- if (path == null ) {
57- throw new PolicyValidationException ("files entitlement must contain path for every listed file" );
58- }
165+ String pathAsString = file .remove ("path" );
166+ String relativePathAsString = file .remove ("relative_path" );
167+ String relativeTo = file .remove ("relative_to" );
59168 String mode = file .remove ("mode" );
169+
170+ if (file .isEmpty () == false ) {
171+ throw new PolicyValidationException ("unknown key(s) [" + file + "] in a listed file for files entitlement" );
172+ }
60173 if (mode == null ) {
61- throw new PolicyValidationException ("files entitlement must contain mode for every listed file" );
174+ throw new PolicyValidationException ("files entitlement must contain ' mode' for every listed file" );
62175 }
63- if (file .isEmpty () == false ) {
64- throw new PolicyValidationException ("unknown key(s) " + file + " in a listed file for files entitlement" );
176+ if (pathAsString != null && relativePathAsString != null ) {
177+ throw new PolicyValidationException ("a files entitlement entry cannot contain both 'path' and 'relative_path'" );
178+ }
179+
180+ if (relativePathAsString != null ) {
181+ if (relativeTo == null ) {
182+ throw new PolicyValidationException ("files entitlement with a 'relative_path' must specify 'relative_to'" );
183+ }
184+ final BaseDir baseDir = parseBaseDir (relativeTo );
185+
186+ Path relativePath = Path .of (relativePathAsString );
187+ if (relativePath .isAbsolute ()) {
188+ throw new PolicyValidationException ("'relative_path' [" + relativePathAsString + "] must be relative" );
189+ }
190+ filesData .add (FileData .ofRelativePath (relativePath , baseDir , parseMode (mode )));
191+ } else if (pathAsString != null ) {
192+ Path path = Path .of (pathAsString );
193+ if (path .isAbsolute () == false ) {
194+ throw new PolicyValidationException ("'path' [" + pathAsString + "] must be absolute" );
195+ }
196+ filesData .add (FileData .ofPath (path , parseMode (mode )));
197+ } else {
198+ throw new PolicyValidationException ("files entitlement must contain either 'path' or 'relative_path' for every entry" );
65199 }
66- filesData .add (new FileData (path , parseMode (mode )));
67200 }
68201 return new FilesEntitlement (filesData );
69202 }
0 commit comments