View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.policy;
20  
21  import java.io.PrintStream;
22  import java.io.PrintWriter;
23  import java.security.AccessControlException;
24  import java.security.CodeSource;
25  import java.security.Permission;
26  import java.security.PermissionCollection;
27  import java.security.Permissions;
28  import java.security.Policy;
29  import java.security.Principal;
30  import java.security.ProtectionDomain;
31  import java.security.cert.Certificate;
32  import java.security.cert.CertificateException;
33  import java.util.Enumeration;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.util.security.CertificateValidator;
43  
44  
45  /**
46   * Policy implementation that will load a set of policy files and manage the mapping of permissions and protection domains
47   * 
48   * Features of JettyPolicy are:
49   * 
50   * - 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 
51   * - support for specifying multiple policy files to source permissions from
52   * - support for merging protection domains across multiple policy files for the same codesource
53   * - support for directories of policy files, just specify directory and all *.policy files will be loaded.
54  
55   * Possible additions are: 
56   * - scan policy directory for new policy files being added
57   * - jmx reporting
58   * - 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
59   * out to add in again later 
60   * - an xml policy file parser, had originally added this using modello but tore it out since it would have been a
61   * nightmare to get its dependencies through IP validation, could do this with jvm xml parser instead sometime 
62   * - check performance of the synch'd map I am using for the protection domain mapping
63   */
64  public class JettyPolicy extends Policy
65  {
66      private static final Logger LOG = Log.getLogger(JettyPolicy.class);
67      
68      private static boolean __DEBUG = false;
69      private static boolean __RELOAD = false;
70  
71      private boolean _STARTED = false;
72      
73      private String _policyDirectory;
74      
75      private final Set<PolicyBlock> _grants = new HashSet<PolicyBlock>();
76  
77      /*
78       * TODO: make into a proper cache
79       */
80      private final Map<Object, PermissionCollection> _cache = new ConcurrentHashMap<Object, PermissionCollection>();
81  
82      private final static PolicyContext _context = new PolicyContext();
83  
84      private CertificateValidator _validator = null;
85          
86      private PolicyMonitor _policyMonitor = new PolicyMonitor()
87      {        
88          @Override
89          public void onPolicyChange(PolicyBlock grant)
90          {
91              boolean setGrant = true;     
92              
93              if ( _validator != null )
94              {
95                  if (grant.getCertificates() != null)
96                  {
97                      for ( Certificate cert : grant.getCertificates() )
98                      {
99                          try
100                         {
101                             _validator.validate(_context.getKeystore(), cert);
102                         }
103                         catch ( CertificateException ce )
104                         {
105                             setGrant = false;
106                         }
107                     }                
108                 }
109             }
110                   
111             if ( setGrant )
112             {
113                 _grants.add( grant );
114                 _cache.clear();
115             }
116         }
117     };
118     
119     public JettyPolicy(String policyDirectory, Map<String, String> properties)
120     {
121         try
122         {
123             __RELOAD = Boolean.getBoolean("org.eclipse.jetty.policy.RELOAD");
124             __DEBUG = Boolean.getBoolean("org.eclipse.jetty.policy.DEBUG");
125         }
126         catch (AccessControlException ace)
127         {
128             __RELOAD = false;
129             __DEBUG = false;
130         }
131         
132         _policyDirectory = policyDirectory;
133         _context.setProperties(properties);
134         
135         try
136         {
137             _policyMonitor.setPolicyDirectory(_policyDirectory);
138             //_policyMonitor.setReload( __RELOAD );
139         }
140         catch ( Exception e)
141         {
142             throw new PolicyException(e);
143         }
144     }
145     
146     
147     
148     @Override
149     public void refresh()
150     {        
151         if ( !_STARTED )
152         {
153             initialize();
154         }
155     }
156 
157     /**
158      * required for the jetty policy to start function, initializes the 
159      * policy monitor and blocks for a full cycle of policy grant updates
160      */
161     public void initialize()
162     {
163         if ( _STARTED )
164         {
165             return;         
166         }
167         
168         try
169         {
170             _policyMonitor.start();
171             _policyMonitor.waitForScan();
172         }
173         catch (Exception e)
174         {
175             e.printStackTrace();
176             throw new PolicyException(e);
177         }
178         
179         _STARTED = true;
180     }
181     
182     @Override
183     public PermissionCollection getPermissions(ProtectionDomain domain)
184     {
185 
186         if (!_STARTED)
187         {
188             throw new PolicyException("JettyPolicy must be started.");
189         }
190 
191         synchronized (_cache)
192         {
193             if (_cache.containsKey(domain))
194             {
195                 return copyOf(_cache.get(domain));
196             }
197 
198             PermissionCollection perms = new Permissions();
199 
200             for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
201             {
202                 PolicyBlock policyBlock = i.next();
203                 ProtectionDomain grantPD = policyBlock.toProtectionDomain();
204 
205                 if (__DEBUG)
206                 {
207                     debug("----START----");
208                     debug("PDCS: " + policyBlock.getCodeSource());
209                     debug("CS: " + domain.getCodeSource());
210 
211                 }
212 
213                 // 1) if protection domain codesource is null, it is the global permissions (grant {})
214                 // 2) if protection domain codesource implies target codesource and there are no prinicpals
215                 if (grantPD.getCodeSource() == null 
216                         || 
217                         grantPD.getCodeSource().implies(domain.getCodeSource()) 
218                         && 
219                         grantPD.getPrincipals() == null 
220                         || 
221                         grantPD.getCodeSource().implies(domain.getCodeSource()) 
222                         && 
223                         validate(grantPD.getPrincipals(),domain.getPrincipals()))
224 
225                 {
226 
227                     for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
228                     {
229                         Permission perm = e.nextElement();
230                         if (__DEBUG)
231                         {
232                             debug("D: " + perm);
233                         }
234                         perms.add(perm);
235                     }
236                 }
237                 if (__DEBUG)
238                 {
239                     debug("----STOP----");
240                 }
241             }
242 
243             _cache.put(domain,perms);
244 
245             return copyOf(perms);
246         }
247     }
248 
249     @Override
250     public PermissionCollection getPermissions(CodeSource codesource)
251     {
252         if (!_STARTED)
253         {
254             throw new PolicyException("JettyPolicy must be started.");
255         }
256 
257         synchronized (_cache)
258         {
259             if (_cache.containsKey(codesource))
260             {
261                 return copyOf(_cache.get(codesource));
262             }
263 
264             PermissionCollection perms = new Permissions();
265 
266             for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
267             {
268                 PolicyBlock policyBlock = i.next();
269                 ProtectionDomain grantPD = policyBlock.toProtectionDomain();
270 
271                 if (grantPD.getCodeSource() == null 
272                         || 
273                         grantPD.getCodeSource().implies(codesource))
274                 {
275                     if (__DEBUG)
276                     {
277                         debug("----START----");
278                         debug("PDCS: " + grantPD.getCodeSource());
279                         debug("CS: " + codesource);
280                     }
281 
282                     for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
283                     {
284                         Permission perm = e.nextElement();
285                         if (__DEBUG)
286                         {
287                             debug("D: " + perm);
288                         }
289                         perms.add(perm);
290                     }
291 
292                     if (__DEBUG)
293                     {
294                         debug("----STOP----");
295                     }
296                 }
297             }
298 
299             _cache.put(codesource,perms);
300 
301             return copyOf(perms);
302         }
303     }
304 
305     @Override
306     public boolean implies(ProtectionDomain domain, Permission permission)
307     {
308         if (!_STARTED)
309         {
310             throw new PolicyException("JettyPolicy must be started.");
311         }
312         
313         PermissionCollection pc = getPermissions(domain);
314         
315         return (pc == null ? false : pc.implies(permission));
316     }
317     
318 
319     private static boolean validate(Principal[] permCerts, Principal[] classCerts)
320     {
321         if (classCerts == null)
322         {
323             return false;
324         }
325 
326         for (int i = 0; i < permCerts.length; ++i)
327         {
328             boolean found = false;
329             for (int j = 0; j < classCerts.length; ++j)
330             {
331                 if (permCerts[i].equals(classCerts[j]))
332                 {
333                     found = true;
334                     break;
335                 }
336             }
337             // if we didn't find the permCert in the classCerts then we don't match up
338             if (found == false)
339             {
340                 return false;
341             }
342         }
343 
344         return true;
345     }
346 
347 
348     /**
349      * returns the policy context which contains the map of properties that
350      * can be referenced in policy files and the keystore for validation
351      * 
352      * @return the policy context
353      */
354     public static PolicyContext getContext()
355     {
356         return _context;
357     }
358     
359    
360     
361     /**
362      * Try and log to normal logging channels and should that not be allowed
363      * debug to system.out
364      * 
365      * @param message
366      */
367     private void debug( String message )
368     {
369         try
370         {
371             LOG.info(message);
372         }
373         catch ( AccessControlException ace )
374         {
375             System.out.println( "[DEBUG] " +  message );
376         }
377         catch ( NoClassDefFoundError ace )
378         {
379             System.out.println( "[DEBUG] " + message );
380             //ace.printStackTrace();
381         }
382     }
383     /**
384      * Try and log to normal logging channels and should that not be allowed
385      * log to system.out
386      * 
387      * @param message
388      */
389     private void log( String message )
390     {
391         log( message, null );
392     }
393     
394     /**
395      * Try and log to normal logging channels and should that not be allowed
396      * log to system.out
397      * 
398      * @param message
399      */
400     private void log( String message, Throwable t )
401     {
402         try
403         {
404             LOG.info(message, t);
405         }
406         catch ( AccessControlException ace )
407         {
408             System.out.println( message );
409             t.printStackTrace();
410         }
411         catch ( NoClassDefFoundError ace )
412         {
413             System.out.println( message );
414             t.printStackTrace();
415         }
416     }
417     
418 
419     public void dump(PrintStream out)
420     {
421         PrintWriter write = new PrintWriter(out);
422         write.println("JettyPolicy: policy settings dump");
423 
424         synchronized (_cache)
425         {
426             for (Iterator<Object> i = _cache.keySet().iterator(); i.hasNext();)
427             {
428                 Object o = i.next();
429                 write.println(o.toString());
430             }
431         }
432         write.flush();
433     }
434     
435     private PermissionCollection copyOf(final PermissionCollection in)
436     {
437         PermissionCollection out  = new Permissions();
438         synchronized (in)
439         {
440             for (Enumeration<Permission> el = in.elements() ; el.hasMoreElements() ;)
441             {
442                 out.add((Permission)el.nextElement());
443             }
444         }
445         return out;
446     }
447 
448     public CertificateValidator getCertificateValidator()
449     {
450         return _validator;
451     }
452 
453     public void setCertificateValidator(CertificateValidator validator)
454     {
455         if (_STARTED)
456         {
457             throw new PolicyException("JettyPolicy already started, unable to set validator on running policy");
458         }
459         
460         _validator = validator;
461     }
462 }