1010package org .elasticsearch .entitlement .runtime .policy ;
1111
1212import 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 ;
1316import org .elasticsearch .logging .LogManager ;
1417import org .elasticsearch .logging .Logger ;
1518
2023import java .nio .file .Files ;
2124import java .nio .file .Path ;
2225import java .nio .file .StandardOpenOption ;
26+ import java .util .ArrayList ;
2327import java .util .Base64 ;
2428import java .util .Collection ;
2529import java .util .HashMap ;
2630import java .util .List ;
2731import java .util .Map ;
28- import java .util .Optional ;
2932import java .util .Set ;
33+ import java .util .function .Function ;
3034import java .util .stream .Collectors ;
35+ import java .util .stream .Stream ;
3136
3237import static java .util .Objects .requireNonNull ;
3338import static org .elasticsearch .entitlement .runtime .policy .PolicyManager .ALL_UNNAMED ;
3439
35- public class PolicyParserUtils {
40+ public class PolicyUtils {
3641
37- private static final Logger logger = LogManager .getLogger (PolicyParserUtils .class );
42+ private static final Logger logger = LogManager .getLogger (PolicyUtils .class );
3843
3944 public record PluginData (Path pluginPath , boolean isModular , boolean isExternalPlugin ) {
4045 public PluginData {
@@ -44,8 +49,6 @@ public record PluginData(Path pluginPath, boolean isModular, boolean isExternalP
4449
4550 private static final String POLICY_FILE_NAME = "entitlement-policy.yaml" ;
4651
47- public static final String POLICY_OVERRIDE_PREFIX = "es.entitlements.policy." ;
48-
4952 public static Map <String , Policy > createPluginPolicies (Collection <PluginData > pluginData , Map <String , String > overrides , String version )
5053 throws IOException {
5154 Map <String , Policy > pluginPolicies = new HashMap <>(pluginData .size ());
@@ -54,9 +57,15 @@ public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pl
5457 String pluginName = pluginRoot .getFileName ().toString ();
5558 final Set <String > moduleNames = getModuleNames (pluginRoot , entry .isModular ());
5659
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 );
6069 } else {
6170 Path policyFile = pluginRoot .resolve (POLICY_FILE_NAME );
6271 var policy = parsePolicyIfExists (pluginName , policyFile , entry .isExternalPlugin ());
@@ -67,59 +76,54 @@ public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pl
6776 return pluginPolicies ;
6877 }
6978
70- static Optional < Policy > parsePolicyOverrideIfExists (
71- Map < String , String > overrides ,
79+ public static Policy parseEncodedPolicyIfExists (
80+ String encodedPolicy ,
7281 String version ,
7382 boolean externalPlugin ,
74- String pluginName ,
83+ String layerName ,
7584 Set <String > moduleNames
7685 ) {
77- var policyOverride = overrides .get (pluginName );
78- if (policyOverride != null ) {
86+ if (encodedPolicy != null ) {
7987 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 >" );
8290
8391 // Empty versions defaults to "any"
8492 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 ();
8795 } else {
8896 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 ,
92100 String .join ("," , versionedPolicy .versions ()),
93101 version
94102 );
95103 }
96104 } catch (Exception ex ) {
97105 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 ),
102107 ex
103108 );
104109 }
105110 }
106- return Optional . empty () ;
111+ return null ;
107112 }
108113
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 {
111115 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 ();
113117 }
114118
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 ) {
116120 // TODO: should this check actually be part of the parser?
117121 for (Scope scope : policy .scopes ()) {
118122 if (moduleNames .contains (scope .moduleName ()) == false ) {
119123 throw new IllegalStateException (
120124 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 ,
123127 scope .moduleName (),
124128 String .join (", " , moduleNames ),
125129 policyLocation
@@ -147,4 +151,51 @@ private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
147151 return Set .of (ALL_UNNAMED );
148152 }
149153
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+ }
150201}
0 commit comments