View Javadoc

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