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