10
10
package org .elasticsearch .entitlement .runtime .policy ;
11
11
12
12
import org .elasticsearch .core .Strings ;
13
+ import org .elasticsearch .entitlement .runtime .policy .entitlements .Entitlement ;
14
+ import org .elasticsearch .entitlement .runtime .policy .entitlements .FilesEntitlement ;
15
+ import org .elasticsearch .entitlement .runtime .policy .entitlements .WriteSystemPropertiesEntitlement ;
13
16
import org .elasticsearch .logging .LogManager ;
14
17
import org .elasticsearch .logging .Logger ;
15
18
20
23
import java .nio .file .Files ;
21
24
import java .nio .file .Path ;
22
25
import java .nio .file .StandardOpenOption ;
26
+ import java .util .ArrayList ;
23
27
import java .util .Base64 ;
24
28
import java .util .Collection ;
25
29
import java .util .HashMap ;
26
30
import java .util .List ;
27
31
import java .util .Map ;
28
- import java .util .Optional ;
29
32
import java .util .Set ;
33
+ import java .util .function .Function ;
30
34
import java .util .stream .Collectors ;
35
+ import java .util .stream .Stream ;
31
36
32
37
import static java .util .Objects .requireNonNull ;
33
38
import static org .elasticsearch .entitlement .runtime .policy .PolicyManager .ALL_UNNAMED ;
34
39
35
- public class PolicyParserUtils {
40
+ public class PolicyUtils {
36
41
37
- private static final Logger logger = LogManager .getLogger (PolicyParserUtils .class );
42
+ private static final Logger logger = LogManager .getLogger (PolicyUtils .class );
38
43
39
44
public record PluginData (Path pluginPath , boolean isModular , boolean isExternalPlugin ) {
40
45
public PluginData {
@@ -44,8 +49,6 @@ public record PluginData(Path pluginPath, boolean isModular, boolean isExternalP
44
49
45
50
private static final String POLICY_FILE_NAME = "entitlement-policy.yaml" ;
46
51
47
- public static final String POLICY_OVERRIDE_PREFIX = "es.entitlements.policy." ;
48
-
49
52
public static Map <String , Policy > createPluginPolicies (Collection <PluginData > pluginData , Map <String , String > overrides , String version )
50
53
throws IOException {
51
54
Map <String , Policy > pluginPolicies = new HashMap <>(pluginData .size ());
@@ -54,9 +57,15 @@ public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pl
54
57
String pluginName = pluginRoot .getFileName ().toString ();
55
58
final Set <String > moduleNames = getModuleNames (pluginRoot , entry .isModular ());
56
59
57
- var overriddenPolicy = parsePolicyOverrideIfExists (overrides , version , entry .isExternalPlugin (), pluginName , moduleNames );
58
- if (overriddenPolicy .isPresent ()) {
59
- pluginPolicies .put (pluginName , overriddenPolicy .get ());
60
+ var overriddenPolicy = parseEncodedPolicyIfExists (
61
+ overrides .get (pluginName ),
62
+ version ,
63
+ entry .isExternalPlugin (),
64
+ pluginName ,
65
+ moduleNames
66
+ );
67
+ if (overriddenPolicy != null ) {
68
+ pluginPolicies .put (pluginName , overriddenPolicy );
60
69
} else {
61
70
Path policyFile = pluginRoot .resolve (POLICY_FILE_NAME );
62
71
var policy = parsePolicyIfExists (pluginName , policyFile , entry .isExternalPlugin ());
@@ -67,59 +76,54 @@ public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pl
67
76
return pluginPolicies ;
68
77
}
69
78
70
- static Optional < Policy > parsePolicyOverrideIfExists (
71
- Map < String , String > overrides ,
79
+ public static Policy parseEncodedPolicyIfExists (
80
+ String encodedPolicy ,
72
81
String version ,
73
82
boolean externalPlugin ,
74
- String pluginName ,
83
+ String layerName ,
75
84
Set <String > moduleNames
76
85
) {
77
- var policyOverride = overrides .get (pluginName );
78
- if (policyOverride != null ) {
86
+ if (encodedPolicy != null ) {
79
87
try {
80
- var versionedPolicy = decodeOverriddenPluginPolicy ( policyOverride , pluginName , externalPlugin );
81
- validatePolicyScopes (pluginName , versionedPolicy .policy (), moduleNames , "<override >" );
88
+ var versionedPolicy = decodeEncodedPolicy ( encodedPolicy , layerName , externalPlugin );
89
+ validatePolicyScopes (layerName , versionedPolicy .policy (), moduleNames , "<patch >" );
82
90
83
91
// Empty versions defaults to "any"
84
92
if (versionedPolicy .versions ().isEmpty () || versionedPolicy .versions ().contains (version )) {
85
- logger .info ("Using policy override for plugin [{}]" , pluginName );
86
- return Optional . of ( versionedPolicy .policy () );
93
+ logger .info ("Using policy patch for layer [{}]" , layerName );
94
+ return versionedPolicy .policy ();
87
95
} else {
88
96
logger .warn (
89
- "Found a policy override with version mismatch. The override will not be applied. "
90
- + "Plugin [{}]; policy versions [{}]; current version [{}]" ,
91
- pluginName ,
97
+ "Found a policy patch with version mismatch. The patch will not be applied. "
98
+ + "Layer [{}]; policy versions [{}]; current version [{}]" ,
99
+ layerName ,
92
100
String .join ("," , versionedPolicy .versions ()),
93
101
version
94
102
);
95
103
}
96
104
} catch (Exception ex ) {
97
105
logger .warn (
98
- Strings .format (
99
- "Found a policy override with invalid content. The override will not be applied. Plugin [%s]" ,
100
- pluginName
101
- ),
106
+ Strings .format ("Found a policy patch with invalid content. The patch will not be applied. Layer [%s]" , layerName ),
102
107
ex
103
108
);
104
109
}
105
110
}
106
- return Optional . empty () ;
111
+ return null ;
107
112
}
108
113
109
- static VersionedPolicy decodeOverriddenPluginPolicy (String base64String , String pluginName , boolean isExternalPlugin )
110
- throws IOException {
114
+ static VersionedPolicy decodeEncodedPolicy (String base64String , String layerName , boolean isExternalPlugin ) throws IOException {
111
115
byte [] policyDefinition = Base64 .getDecoder ().decode (base64String );
112
- return new PolicyParser (new ByteArrayInputStream (policyDefinition ), pluginName , isExternalPlugin ).parseVersionedPolicy ();
116
+ return new PolicyParser (new ByteArrayInputStream (policyDefinition ), layerName , isExternalPlugin ).parseVersionedPolicy ();
113
117
}
114
118
115
- private static void validatePolicyScopes (String pluginName , Policy policy , Set <String > moduleNames , String policyLocation ) {
119
+ private static void validatePolicyScopes (String layerName , Policy policy , Set <String > moduleNames , String policyLocation ) {
116
120
// TODO: should this check actually be part of the parser?
117
121
for (Scope scope : policy .scopes ()) {
118
122
if (moduleNames .contains (scope .moduleName ()) == false ) {
119
123
throw new IllegalStateException (
120
124
Strings .format (
121
- "Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy path [%s]" ,
122
- pluginName ,
125
+ "Invalid module name in policy: layer [%s] does not have module [%s]; available modules [%s]; policy path [%s]" ,
126
+ layerName ,
123
127
scope .moduleName (),
124
128
String .join (", " , moduleNames ),
125
129
policyLocation
@@ -147,4 +151,51 @@ private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
147
151
return Set .of (ALL_UNNAMED );
148
152
}
149
153
154
+ public static List <Scope > mergeScopes (List <Scope > mainScopes , List <Scope > additionalScopes ) {
155
+ var result = new ArrayList <Scope >();
156
+ var additionalScopesMap = additionalScopes .stream ().collect (Collectors .toMap (Scope ::moduleName , Scope ::entitlements ));
157
+ for (var mainScope : mainScopes ) {
158
+ List <Entitlement > additionalEntitlements = additionalScopesMap .remove (mainScope .moduleName ());
159
+ if (additionalEntitlements == null ) {
160
+ result .add (mainScope );
161
+ } else {
162
+ result .add (new Scope (mainScope .moduleName (), mergeEntitlements (mainScope .entitlements (), additionalEntitlements )));
163
+ }
164
+ }
165
+
166
+ for (var remainingEntry : additionalScopesMap .entrySet ()) {
167
+ result .add (new Scope (remainingEntry .getKey (), remainingEntry .getValue ()));
168
+ }
169
+ return result ;
170
+ }
171
+
172
+ static List <Entitlement > mergeEntitlements (List <Entitlement > a , List <Entitlement > b ) {
173
+ Map <Class <? extends Entitlement >, Entitlement > entitlementMap = a .stream ()
174
+ .collect (Collectors .toMap (Entitlement ::getClass , Function .identity ()));
175
+
176
+ for (var entitlement : b ) {
177
+ entitlementMap .merge (entitlement .getClass (), entitlement , PolicyUtils ::mergeEntitlement );
178
+ }
179
+ return entitlementMap .values ().stream ().toList ();
180
+ }
181
+
182
+ static Entitlement mergeEntitlement (Entitlement entitlement1 , Entitlement entitlement2 ) {
183
+ if (entitlement1 instanceof FilesEntitlement e ) {
184
+ return merge (e , (FilesEntitlement ) entitlement2 );
185
+ }
186
+ if (entitlement1 instanceof WriteSystemPropertiesEntitlement e ) {
187
+ return merge (e , (WriteSystemPropertiesEntitlement ) entitlement2 );
188
+ }
189
+ return entitlement1 ;
190
+ }
191
+
192
+ private static FilesEntitlement merge (FilesEntitlement a , FilesEntitlement b ) {
193
+ return new FilesEntitlement (Stream .concat (a .filesData ().stream (), b .filesData ().stream ()).distinct ().toList ());
194
+ }
195
+
196
+ private static WriteSystemPropertiesEntitlement merge (WriteSystemPropertiesEntitlement a , WriteSystemPropertiesEntitlement b ) {
197
+ return new WriteSystemPropertiesEntitlement (
198
+ Stream .concat (a .properties ().stream (), b .properties ().stream ()).collect (Collectors .toUnmodifiableSet ())
199
+ );
200
+ }
150
201
}
0 commit comments