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.File;
20  import java.io.FileFilter;
21  import java.io.FileInputStream;
22  import java.io.IOException;
23  import java.io.PrintStream;
24  import java.io.PrintWriter;
25  import java.security.AccessControlException;
26  import java.security.CodeSource;
27  import java.security.Permission;
28  import java.security.PermissionCollection;
29  import java.security.Permissions;
30  import java.security.Policy;
31  import java.security.Principal;
32  import java.security.ProtectionDomain;
33  import java.util.ArrayList;
34  import java.util.Enumeration;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  
42  import org.eclipse.jetty.policy.loader.DefaultPolicyLoader;
43  import org.eclipse.jetty.util.Scanner;
44  import org.eclipse.jetty.util.log.Log;
45  
46  
47  /**
48   * Policy implementation that will load a set of policy files and manage the mapping of permissions and protection domains
49   * 
50   * Features of JettyPolicy are:
51   * 
52   * - 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 
53   * - support for specifying multiple policy files to source permissions from
54   * - support for merging protection domains across multiple policy files for the same codesource
55   * - support for directories of policy files, just specify directory and all *.policy files will be loaded.
56   * 
57   * Possible additions are: 
58   * - jmx reporting
59   * - 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
60   * out to add in again later 
61   * - an xml policy file parser, had originally added this using modello but tore it out since it would have been a
62   * nightmare to get its dependencies through IP validation, could do this with jvm xml parser instead sometime 
63   * - check performance of the synch'd map I am using for the protection domain mapping
64   */
65  public class JettyPolicy extends Policy
66  {
67      private static boolean __DEBUG = false;
68      private static boolean __RELOAD = false;
69  
70      // Policy files that are actively managed by the aggregate policy mechanism
71      private final Set<String> _policies;
72  
73      private final Set<PolicyBlock> _grants = new HashSet<PolicyBlock>();
74  
75      private final Map<Object, PermissionCollection> _cache = new HashMap<Object, PermissionCollection>();
76  
77      private final PolicyContext _context = new PolicyContext();
78  
79      private Boolean _initialized = false;
80  
81      private Scanner _scanner;
82  
83      public JettyPolicy(Set<String> policies, Map<String, String> properties)
84      {
85          try
86          {
87              __RELOAD = Boolean.getBoolean("org.eclipse.jetty.policy.RELOAD");
88              __DEBUG = Boolean.getBoolean("org.eclipse.jetty.policy.DEBUG");
89          }
90          catch (AccessControlException ace)
91          {
92              __RELOAD = false;
93              __DEBUG = false;
94          }
95          
96          _policies = resolvePolicyFiles(policies);
97          _context.setProperties(properties);
98      }
99  
100     @Override
101     public PermissionCollection getPermissions(ProtectionDomain domain)
102     {
103 
104         synchronized (_initialized)
105         {
106             if (!_initialized)
107             {
108                 refresh();
109             }
110         }
111 
112         synchronized (_cache)
113         {
114             if (_cache.containsKey(domain))
115             {
116                 return copyOf(_cache.get(domain));
117             }
118 
119             PermissionCollection perms = new Permissions();
120 
121             for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
122             {
123                 PolicyBlock policyBlock = i.next();
124                 ProtectionDomain grantPD = policyBlock.toProtectionDomain();
125 
126                 if (__DEBUG)
127                 {
128                     debug("----START----");
129                     debug("PDCS: " + policyBlock.getCodeSource());
130                     debug("CS: " + domain.getCodeSource());
131                 }
132 
133                 // 1) if protection domain codesource is null, it is the global permissions (grant {})
134                 // 2) if protection domain codesource implies target codesource and there are no prinicpals
135                 // 2) if protection domain codesource implies target codesource and principals align
136                 if (grantPD.getCodeSource() == null 
137                         || 
138                         grantPD.getCodeSource().implies(domain.getCodeSource()) 
139                         && 
140                         grantPD.getPrincipals() == null 
141                         || 
142                         grantPD.getCodeSource().implies(domain.getCodeSource()) 
143                         && 
144                         validate(grantPD.getPrincipals(),domain.getPrincipals()))
145                 {
146 
147                     for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
148                     {
149                         Permission perm = e.nextElement();
150                         if (__DEBUG)
151                         {
152                             debug("D: " + perm);
153                         }
154                         perms.add(perm);
155                     }
156                 }
157                 if (__DEBUG)
158                 {
159                     debug("----STOP----");
160                 }
161             }
162 
163             _cache.put(domain,perms);
164 
165             return copyOf(perms);
166         }
167     }
168 
169     @Override
170     public PermissionCollection getPermissions(CodeSource codesource)
171     {
172         synchronized (_initialized)
173         {
174             if (!_initialized)
175             {
176                 refresh();
177             }
178         }
179 
180         synchronized (_cache)
181         {
182             if (_cache.containsKey(codesource))
183             {
184                 return copyOf(_cache.get(codesource));
185             }
186 
187             PermissionCollection perms = new Permissions();
188 
189             for (Iterator<PolicyBlock> i = _grants.iterator(); i.hasNext();)
190             {
191                 PolicyBlock policyBlock = i.next();
192                 ProtectionDomain grantPD = policyBlock.toProtectionDomain();
193 
194                 if (grantPD.getCodeSource() == null 
195                         || 
196                         grantPD.getCodeSource().implies(codesource))
197                 {
198                     if (__DEBUG)
199                     {
200                         debug("----START----");
201                         debug("PDCS: " + grantPD.getCodeSource());
202                         debug("CS: " + codesource);
203                     }
204 
205                     for (Enumeration<Permission> e = policyBlock.getPermissions().elements(); e.hasMoreElements();)
206                     {
207                         Permission perm = e.nextElement();
208                         if (__DEBUG)
209                         {
210                             debug("D: " + perm);
211                         }
212                         perms.add(perm);
213                     }
214 
215                     if (__DEBUG)
216                     {
217                         debug("----STOP----");
218                     }
219                 }
220             }
221 
222             _cache.put(codesource,perms);
223 
224             return copyOf(perms);
225         }
226     }
227 
228     @Override
229     public boolean implies(ProtectionDomain domain, Permission permission)
230     {
231         PermissionCollection pc = getPermissions(domain);
232         
233         return (pc == null ? false : pc.implies(permission));
234     }
235     
236 
237     private static boolean validate(Principal[] permCerts, Principal[] classCerts)
238     {
239         if (classCerts == null)
240         {
241             return false;
242         }
243 
244         for (int i = 0; i < permCerts.length; ++i)
245         {
246             boolean found = false;
247             for (int j = 0; j < classCerts.length; ++j)
248             {
249                 if (permCerts[i].equals(classCerts[j]))
250                 {
251                     found = true;
252                     break;
253                 }
254             }
255             // if we didn't find the permCert in the classCerts then we don't match up
256             if (found == false)
257             {
258                 return false;
259             }
260         }
261 
262         return true;
263     }
264 
265     /**
266      * This call performs a refresh of the policy system, first processing the associated 
267      * files and then replacing the policy cache.
268      * 
269      */
270     @Override
271     public synchronized void refresh()
272     {
273 
274         try
275         {
276             // initialize the reloading mechanism if enabled
277             if (__RELOAD && _scanner == null)
278             {
279                 initializeReloading();
280             }
281             
282             if (__DEBUG)
283             {
284                 synchronized (_cache)
285                 {
286                     for (Iterator<Object> i = _cache.keySet().iterator(); i.hasNext();)
287                     {
288                         log(i.next().toString());
289                     }
290                 }
291             }
292 
293             Set<PolicyBlock> clean = new HashSet<PolicyBlock>();
294 
295             for (Iterator<String> i = _policies.iterator(); i.hasNext();)
296             {
297                 File policyFile = new File(i.next());
298 
299                 clean.addAll(DefaultPolicyLoader.load(new FileInputStream(policyFile),_context));
300             }
301 
302             synchronized (_cache)
303             {
304                 _grants.clear();
305                 _grants.addAll(clean);
306                 _cache.clear();
307             }
308             _initialized = true;
309         }
310         catch (Exception e)
311         {
312             e.printStackTrace();
313         }
314     }
315 
316     /**
317      * the scanning mechanism used to detect changes to the policy system and reload
318      * 
319      * @throws Exception
320      */
321     private void initializeReloading() throws Exception
322     {
323         _scanner = new Scanner();
324 
325         List<File> scanDirs = new ArrayList<File>();
326 
327         for (Iterator<String> i = _policies.iterator(); i.hasNext();)
328         {
329             File policyFile = new File(i.next());
330             scanDirs.add(policyFile.getParentFile());
331         }
332 
333         _scanner.addListener(new Scanner.DiscreteListener()
334         {
335 
336             public void fileRemoved(String filename) throws Exception
337             {
338 
339             }
340 
341             /* will trigger when files are changed, not on load time, just when changed */
342             public void fileChanged(String filename) throws Exception
343             {
344                 if (filename.endsWith("policy")) // TODO match up to existing policies to avoid unnecessary reloads
345                 {
346                     log("JettyPolicy: refreshing policy files");
347                     refresh();
348                     log("JettyPolicy: finished refreshing policies");
349                 }
350             }
351 
352             public void fileAdded(String filename) throws Exception
353             {
354 
355             }
356         });
357 
358         _scanner.setScanDirs(scanDirs);
359         _scanner.start();
360         _scanner.setScanInterval(10);
361     }
362 
363     public void dump(PrintStream out)
364     {
365         PrintWriter write = new PrintWriter(out);
366         write.println("JettyPolicy: policy settings dump");
367 
368         synchronized (_cache)
369         {
370             for (Iterator<Object> i = _cache.keySet().iterator(); i.hasNext();)
371             {
372                 Object o = i.next();
373                 write.println(o.toString());
374             }
375         }
376         write.flush();
377     }
378     
379     public PermissionCollection copyOf(final PermissionCollection in)
380     {
381         PermissionCollection out  = new Permissions();
382         synchronized (in)
383         {
384             for (Enumeration<Permission> el = in.elements() ; el.hasMoreElements() ;)
385             {
386                 out.add((Permission)el.nextElement());
387             }
388         }
389         return out;
390     }
391     
392     /**
393      * returns the known policy files that are being tracked by this instance of JettyPolicy
394      * @return set of known policy files
395      */
396     public Set<String> getKnownPolicyFiles()
397     {
398         return _policies;
399     }
400     
401     /**
402      * resolves the initial set of policy files into the actual set of policies, 
403      * scanning directories for .policy files as well.
404      * @param policyInputs
405      * @return
406      */
407     private Set<String> resolvePolicyFiles( Set<String> policyInputs )
408     {
409         Set<String> policyFiles = new HashSet<String>();
410         
411         try
412         {
413         for ( String policyInput : policyInputs )
414         {
415             File check = new File(policyInput);
416             
417             if ( check.isDirectory() )
418             {
419                 File[] foundFiles = check.listFiles(new FileFilter()
420                 {             
421                     public boolean accept(File pathname)
422                     {
423                         if ( pathname.getName().toLowerCase().endsWith("policy") )
424                         {
425                             return true;
426                         }
427                         else
428                         {
429                             return false;
430                         }
431                     }
432                 });
433                 
434                 for( File policyFile : foundFiles )
435                 {
436                     policyFiles.add(policyFile.getCanonicalPath());
437                 }
438             }
439             else
440             {
441                 policyFiles.add(check.getCanonicalPath());
442             }
443         }
444         }
445         catch ( IOException ioe )
446         {
447             throw new IllegalArgumentException( "JettyPolicy: unable to resolve policy files.", ioe );
448         }
449         return policyFiles;
450     }
451     
452     /**
453      * Try and log to normal logging channels and should that not be allowed
454      * debug to system.out
455      * 
456      * @param message
457      */
458     private void debug( String message )
459     {
460         try
461         {
462             Log.debug(message);
463         }
464         catch ( AccessControlException ace )
465         {
466             System.out.println( "[DEBUG] " +  message );
467         }
468     }
469     /**
470      * Try and log to normal logging channels and should that not be allowed
471      * log to system.out
472      * 
473      * @param message
474      */
475     private void log( String message )
476     {
477         log( message, null );
478     }
479     
480     /**
481      * Try and log to normal logging channels and should that not be allowed
482      * log to system.out
483      * 
484      * @param message
485      */
486     private void log( String message, Throwable t )
487     {
488         try
489         {
490             Log.info(message, t);
491         }
492         catch ( AccessControlException ace )
493         {
494             System.out.println( message );
495             t.printStackTrace();
496         }
497     }
498     
499 }