PolicyUtil.java 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * Licensed to Elasticsearch under one or more contributor
  3. * license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright
  5. * ownership. Elasticsearch licenses this file to you under
  6. * the Apache License, Version 2.0 (the "License"); you may
  7. * not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.elasticsearch.bootstrap;
  20. import org.elasticsearch.common.SuppressForbidden;
  21. import org.elasticsearch.common.io.PathUtils;
  22. import org.elasticsearch.core.internal.io.IOUtils;
  23. import org.elasticsearch.plugins.PluginInfo;
  24. import java.io.IOException;
  25. import java.net.URISyntaxException;
  26. import java.net.URL;
  27. import java.nio.file.DirectoryStream;
  28. import java.nio.file.Files;
  29. import java.nio.file.Path;
  30. import java.security.CodeSource;
  31. import java.security.NoSuchAlgorithmException;
  32. import java.security.Permission;
  33. import java.security.PermissionCollection;
  34. import java.security.Policy;
  35. import java.security.ProtectionDomain;
  36. import java.security.URIParameter;
  37. import java.security.cert.Certificate;
  38. import java.util.ArrayList;
  39. import java.util.Collections;
  40. import java.util.HashSet;
  41. import java.util.LinkedHashMap;
  42. import java.util.LinkedHashSet;
  43. import java.util.List;
  44. import java.util.Map;
  45. import java.util.Set;
  46. public class PolicyUtil {
  47. /**
  48. * Return a map from codebase name to codebase url of jar codebases used by ES core.
  49. */
  50. @SuppressForbidden(reason = "find URL path")
  51. public static Map<String, URL> getCodebaseJarMap(Set<URL> urls) {
  52. Map<String, URL> codebases = new LinkedHashMap<>(); // maintain order
  53. for (URL url : urls) {
  54. try {
  55. String fileName = PathUtils.get(url.toURI()).getFileName().toString();
  56. if (fileName.endsWith(".jar") == false) {
  57. // tests :(
  58. continue;
  59. }
  60. codebases.put(fileName, url);
  61. } catch (URISyntaxException e) {
  62. throw new RuntimeException(e);
  63. }
  64. }
  65. return codebases;
  66. }
  67. /**
  68. * Reads and returns the specified {@code policyFile}.
  69. * <p>
  70. * Jar files listed in {@code codebases} location will be provided to the policy file via
  71. * a system property of the short name: e.g. <code>${codebase.joda-convert-1.2.jar}</code>
  72. * would map to full URL.
  73. */
  74. @SuppressForbidden(reason = "accesses fully qualified URLs to configure security")
  75. public static Policy readPolicy(URL policyFile, Map<String, URL> codebases) {
  76. try {
  77. List<String> propertiesSet = new ArrayList<>();
  78. try {
  79. // set codebase properties
  80. for (Map.Entry<String,URL> codebase : codebases.entrySet()) {
  81. String name = codebase.getKey();
  82. URL url = codebase.getValue();
  83. // We attempt to use a versionless identifier for each codebase. This assumes a specific version
  84. // format in the jar filename. While we cannot ensure all jars in all plugins use this format, nonconformity
  85. // only means policy grants would need to include the entire jar filename as they always have before.
  86. String property = "codebase." + name;
  87. String aliasProperty = "codebase." + name.replaceFirst("-\\d+\\.\\d+.*\\.jar", "");
  88. if (aliasProperty.equals(property) == false) {
  89. propertiesSet.add(aliasProperty);
  90. String previous = System.setProperty(aliasProperty, url.toString());
  91. if (previous != null) {
  92. throw new IllegalStateException("codebase property already set: " + aliasProperty + " -> " + previous +
  93. ", cannot set to " + url.toString());
  94. }
  95. }
  96. propertiesSet.add(property);
  97. String previous = System.setProperty(property, url.toString());
  98. if (previous != null) {
  99. throw new IllegalStateException("codebase property already set: " + property + " -> " + previous +
  100. ", cannot set to " + url.toString());
  101. }
  102. }
  103. return Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI()));
  104. } finally {
  105. // clear codebase properties
  106. for (String property : propertiesSet) {
  107. System.clearProperty(property);
  108. }
  109. }
  110. } catch (NoSuchAlgorithmException | URISyntaxException e) {
  111. throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e);
  112. }
  113. }
  114. /**
  115. * Return info about the security policy for a plugin.
  116. */
  117. public static PluginPolicyInfo getPluginPolicyInfo(Path pluginRoot) throws IOException {
  118. Path policyFile = pluginRoot.resolve(PluginInfo.ES_PLUGIN_POLICY);
  119. if (Files.exists(policyFile) == false) {
  120. return null;
  121. }
  122. // first get a list of URLs for the plugins' jars:
  123. // we resolve symlinks so map is keyed on the normalize codebase name
  124. Set<URL> jars = new LinkedHashSet<>(); // order is already lost, but some filesystems have it
  125. try (DirectoryStream<Path> jarStream = Files.newDirectoryStream(pluginRoot, "*.jar")) {
  126. for (Path jar : jarStream) {
  127. URL url = jar.toRealPath().toUri().toURL();
  128. if (jars.add(url) == false) {
  129. throw new IllegalStateException("duplicate module/plugin: " + url);
  130. }
  131. }
  132. }
  133. // parse the plugin's policy file into a set of permissions
  134. Policy policy = readPolicy(policyFile.toUri().toURL(), getCodebaseJarMap(jars));
  135. return new PluginPolicyInfo(jars, policy);
  136. }
  137. /**
  138. * Return permissions for a policy that apply to a jar.
  139. *
  140. * @param url The url of a jar to find permissions for, or {@code null} for global permissions.
  141. */
  142. public static Set<Permission> getPolicyPermissions(URL url, Policy policy, Path tmpDir) throws IOException {
  143. // create a zero byte file for "comparison"
  144. // this is necessary because the default policy impl automatically grants two permissions:
  145. // 1. permission to exitVM (which we ignore)
  146. // 2. read permission to the code itself (e.g. jar file of the code)
  147. Path emptyPolicyFile = Files.createTempFile(tmpDir, "empty", "tmp");
  148. final Policy emptyPolicy;
  149. try {
  150. emptyPolicy = Policy.getInstance("JavaPolicy", new URIParameter(emptyPolicyFile.toUri()));
  151. } catch (NoSuchAlgorithmException e) {
  152. throw new RuntimeException(e);
  153. }
  154. IOUtils.rm(emptyPolicyFile);
  155. final ProtectionDomain protectionDomain;
  156. if (url == null) {
  157. // global, use PolicyUtil since it is part of core ES
  158. protectionDomain = PolicyUtil.class.getProtectionDomain();
  159. } else {
  160. // we may not have the url loaded, so create a fake protection domain
  161. protectionDomain = new ProtectionDomain(new CodeSource(url, (Certificate[]) null), null);
  162. }
  163. PermissionCollection permissions = policy.getPermissions(protectionDomain);
  164. // this method is supported with the specific implementation we use, but just check for safety.
  165. if (permissions == Policy.UNSUPPORTED_EMPTY_COLLECTION) {
  166. throw new UnsupportedOperationException("JavaPolicy implementation does not support retrieving permissions");
  167. }
  168. Set<Permission> actualPermissions = new HashSet<>();
  169. for (Permission permission : Collections.list(permissions.elements())) {
  170. if (emptyPolicy.implies(protectionDomain, permission) == false) {
  171. actualPermissions.add(permission);
  172. }
  173. }
  174. return actualPermissions;
  175. }
  176. }