package org.eclipse.jetty.policy; //======================================================================== //Copyright (c) Webtide LLC //------------------------------------------------------------------------ //All rights reserved. This program and the accompanying materials //are made available under the terms of the Eclipse Public License v1.0 //and Apache License v2.0 which accompanies this distribution. // //The Eclipse Public License is available at //http://www.eclipse.org/legal/epl-v10.html // //The Apache License v2.0 is available at //http://www.opensource.org/licenses/apache2.0.php // //You may elect to redistribute this code under either of these licenses. //======================================================================== import java.io.PrintStream; import java.io.PrintWriter; import java.security.AccessControlException; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; import java.security.Policy; import java.security.Principal; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.security.CertificateValidator; /** * Policy implementation that will load a set of policy files and manage the mapping of permissions and protection domains * * Features of JettyPolicy are: * * - we are able to follow the startup mechanic that jetty uses with jetty-start using OPTIONS=policy,default to be able to startup a security manager and policy implementation without have to rely on the existing JVM cli options * - support for specifying multiple policy files to source permissions from * - support for merging protection domains across multiple policy files for the same codesource * - support for directories of policy files, just specify directory and all *.policy files will be loaded. * Possible additions are: * - scan policy directory for new policy files being added * - jmx reporting * - proxying of system security policy where we can proxy access to the system policy should the jvm have been started with one, I had support for this but ripped it * out to add in again later * - an xml policy file parser, had originally added this using modello but tore it out since it would have been a * nightmare to get its dependencies through IP validation, could do this with jvm xml parser instead sometime * - check performance of the synch'd map I am using for the protection domain mapping */ public class JettyPolicy extends Policy { private static final Logger LOG = Log.getLogger(JettyPolicy.class); private static boolean __DEBUG = false; private static boolean __RELOAD = false; private boolean _STARTED = false; private String _policyDirectory; private final Set<PolicyBlock> _grants = new HashSet<PolicyBlock>(); /* * TODO: make into a proper cache */ private final Map<Object, PermissionCollection> _cache = new ConcurrentHashMap<Object, PermissionCollection>(); private final static PolicyContext _context = new PolicyContext(); private CertificateValidator _validator = null; private PolicyMonitor _policyMonitor = new PolicyMonitor() { @Override public void onPolicyChange(PolicyBlock grant) { boolean setGrant = true; if ( _validator != null ) { if (grant.getCertificates() != null) { for ( Certificate cert : grant.getCertificates() ) { try { _validator.validate(_context.getKeystore(), cert); } catch ( CertificateException ce ) { setGrant = false; } } } } if ( setGrant ) { _grants.add( grant ); _cache.clear(); } } }; public JettyPolicy(String policyDirectory, Map<String, String> properties) { try { __RELOAD = Boolean.getBoolean("org.eclipse.jetty.policy.RELOAD"); __DEBUG = Boolean.getBoolean("org.eclipse.jetty.policy.DEBUG"); } catch (AccessControlException ace) { __RELOAD = false; __DEBUG = false; } _policyDirectory = policyDirectory; _context.setProperties(properties); try { _policyMonitor.setPolicyDirectory(_policyDirectory); //_policyMonitor.setReload( __RELOAD ); } catch ( Exception e) { throw new PolicyException(e); } } @Override public void refresh() { if ( !_STARTED ) { initialize(); } } /** * required for the jetty policy to start function, initializes the * policy monitor and blocks for a full cycle of policy grant updates */ public void initialize() { if ( _STARTED ) { return; } try { _policyMonitor.start(); _policyMonitor.waitForScan(); } catch (Exception e) { e.printStackTrace(); throw new PolicyException(e); } _STARTED = true; } @Override public PermissionCollection getPermissions(ProtectionDomain domain) { if (!_STARTED) { throw new PolicyException("JettyPolicy must be started."); } synchronized (_cache) { if (_cache.containsKey(domain)) { return copyOf(_cache.get(domain)); } PermissionCollection perms = new Permissions(); for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();) { PolicyBlock policyBlock = i.next(); ProtectionDomain grantPD = policyBlock.toProtectionDomain(); if (__DEBUG) { debug("----START----"); debug("PDCS: " + policyBlock.getCodeSource()); debug("CS: " + domain.getCodeSource()); } // 1) if protection domain codesource is null, it is the global permissions (grant {}) // 2) if protection domain codesource implies target codesource and there are no prinicpals if (grantPD.getCodeSource() == null || grantPD.getCodeSource().implies(domain.getCodeSource()) && grantPD.getPrincipals() == null || grantPD.getCodeSource().implies(domain.getCodeSource()) && validate(grantPD.getPrincipals(),domain.getPrincipals())) { for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();) { Permission perm = e.nextElement(); if (__DEBUG) { debug("D: " + perm); } perms.add(perm); } } if (__DEBUG) { debug("----STOP----"); } } _cache.put(domain,perms); return copyOf(perms); } } @Override public PermissionCollection getPermissions(CodeSource codesource) { if (!_STARTED) { throw new PolicyException("JettyPolicy must be started."); } synchronized (_cache) { if (_cache.containsKey(codesource)) { return copyOf(_cache.get(codesource)); } PermissionCollection perms = new Permissions(); for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();) { PolicyBlock policyBlock = i.next(); ProtectionDomain grantPD = policyBlock.toProtectionDomain(); if (grantPD.getCodeSource() == null || grantPD.getCodeSource().implies(codesource)) { if (__DEBUG) { debug("----START----"); debug("PDCS: " + grantPD.getCodeSource()); debug("CS: " + codesource); } for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();) { Permission perm = e.nextElement(); if (__DEBUG) { debug("D: " + perm); } perms.add(perm); } if (__DEBUG) { debug("----STOP----"); } } } _cache.put(codesource,perms); return copyOf(perms); } } @Override public boolean implies(ProtectionDomain domain, Permission permission) { if (!_STARTED) { throw new PolicyException("JettyPolicy must be started."); } PermissionCollection pc = getPermissions(domain); return (pc == null ? false : pc.implies(permission)); } private static boolean validate(Principal[] permCerts, Principal[] classCerts) { if (classCerts == null) { return false; } for (int i = 0; i < permCerts.length; ++i) { boolean found = false; for (int j = 0; j < classCerts.length; ++j) { if (permCerts[i].equals(classCerts[j])) { found = true; break; } } // if we didn't find the permCert in the classCerts then we don't match up if (found == false) { return false; } } return true; } /** * returns the policy context which contains the map of properties that * can be referenced in policy files and the keystore for validation * * @return the policy context */ public static PolicyContext getContext() { return _context; } /** * Try and log to normal logging channels and should that not be allowed * debug to system.out * * @param message */ private void debug( String message ) { try { LOG.info(message); } catch ( AccessControlException ace ) { System.out.println( "[DEBUG] " + message ); } catch ( NoClassDefFoundError ace ) { System.out.println( "[DEBUG] " + message ); //ace.printStackTrace(); } } /** * Try and log to normal logging channels and should that not be allowed * log to system.out * * @param message */ private void log( String message ) { log( message, null ); } /** * Try and log to normal logging channels and should that not be allowed * log to system.out * * @param message */ private void log( String message, Throwable t ) { try { LOG.info(message, t); } catch ( AccessControlException ace ) { System.out.println( message ); t.printStackTrace(); } catch ( NoClassDefFoundError ace ) { System.out.println( message ); t.printStackTrace(); } } public void dump(PrintStream out) { PrintWriter write = new PrintWriter(out); write.println("JettyPolicy: policy settings dump"); synchronized (_cache) { for (Iterator<Object> i = _cache.keySet().iterator(); i.hasNext();) { Object o = i.next(); write.println(o.toString()); } } write.flush(); } private PermissionCollection copyOf(final PermissionCollection in) { PermissionCollection out = new Permissions(); synchronized (in) { for (Enumeration<Permission> el = in.elements() ; el.hasMoreElements() ;) { out.add((Permission)el.nextElement()); } } return out; } public CertificateValidator getCertificateValidator() { return _validator; } public void setCertificateValidator(CertificateValidator validator) { if (_STARTED) { throw new PolicyException("JettyPolicy already started, unable to set validator on running policy"); } _validator = validator; } }