READ 1ST: Re: Authorization layer API and low level access checks

Peter Firmstone peter.firmstone at zeus.net.au
Sun Jun 27 09:35:18 UTC 2021


Since I need to implement an authorization layer, and move past the 
current uncertainty surrounding authorization and authentication in 
Java, I think I'll start small and completely independent and learn from 
history.

Requirements:

 1. Ability to perform authorization checks on code and Subjects.
 2. Compatible with all current Java LTS releases.
 3. Will need to use Java API's, nothing platform specific.

Uncertainty:

 1. New JAAS API's are unknown, will they be suitable for our
    application? Unclear.
 2. Will hooks be provided in OpenJDK for Guard checks or not? Unclear.
 3. Lack of support for new features with existing API's going forward,
    eg virtual threads.
 4. How to authenticate TLS and Kerberos connections without using
    deprecated API's?
 5. Avoid other API's that may be removed in future due to
    under-utilisation.

What can we do with what we know, how can we do it better?

 1. Create a new Authorization.class with methods that return Callable
    objects that can be submitted to Executors, including virtual
    threads (assuming virtual threads support StackWalker).
 2. Methods:
      * Callable<T> privilegedCall(Callable<T> c) // Always preserves
        subject, if any.
      * Callable<T> privilegedCall(Subject s, Callable<T> c) // Uses
        provided Subject, from LoginModule, or executing in the context
        of an authenticated client over a secure connection.
      * Callable<T> privilegedCall(AuthorizationContext ac, Callable<T>
        c) // Uses provided AuthorizationContext combined with context
        of the privileged caller.
      * AuthorizationContext  getContext(); // Returns an optimized
        context (combines the inherited and calculated contexts, injects
        Principal[]'s from the Subject into each domain. Stored for
        later use when making privilegedCall's eg for TLS connection.

 3. The Authorization.class implementation will enure that the inherited
    context is stored in a ThreadLocal variable which is restored to
    it's original value using a try finally block to ensure the
    inherited context is only present during the wrapped Callable's
    call.   It's possible that privilegedCall's are nested in one
    thread, in which case the ThreadLocal value will be changed after
    each privilegedCall to the outer calls context, until it is set to
    null, by the outermost privilegedCall.
 4. Callable returned can be submitted to an Executor, eg as a
    privileged task.
 5. Create a new AuthorizationContext class

      * Encapsulates the current Subject
      * Contains a snapshot of the ProtectionDomains when the Callable
        was passed to privilegedCall and includes the domain of the
        class that called the Authorization.privileged method as well as
        any domains of Callable implementation parameter classes, this
        is the inherited context.
      * Methods:
          o Subject getSubject() // For secure connections.
          o void checkEach(Consumer<ProtectionDomain> consumer) throws
            AuthorizationException // Consumer::andThen allows for
            debugging information to be printed to error, such as print
            the privileges of a domain, or to record the required
            privileges of each domain, without throwing a
            SecurityException (when recording allowed operations).
            AuthorizationContext implementation determines how to
            execute.  No mutable shared state.

 4. Use Agents to instrument Java Public API until hooks are provided by
    OpenJDK.
      * *ONLY* target LTS releases to minimize API analysis required.
      * Use static analysis to located methods in Java API's that return
        sensitive classes, eg ClassLoader, ProtectionDomain.
      * We could use some hooks here OpenJDK?
 5. How to capture domains and privileged scope?

      * It is not possible to inherit a call stack from a previous
        thread, so either the thread executes only platform code and is
        checked, assuming the platform code can be trusted to not allow
        sensitive object references to escape, or if application code is
        present, then it is unprivileged unless a privilegedCall is
        made.  It will be known by the presence of a ThreadLocal
        inherited context whether a privilegedCall is being executed.
      * The stack context is only captured after a privilegedCall is
        made on a Thread's call stack, with the exception of a call
        stack that contains *only* platform code.
      * If an inherited ThreadLocal AuthorizationContext isn't present,
        when checked, then an unprivileged domain will represent the
        entire stack, with the following exception:
          o Unless all code on the thread stack is platform code, in
            this case, capture all domains on the call stack since the
            thread started.   This is a code only check, as no Subject
            will be present.
          o For bootloader system code construct ProtectionDomain, with
            CodeSource URL[] containing a jrt:/$MODULE , if Module
            isNamed(), retrieved from Class.getModule().
          o Authorization agents will be considered platform code.
      * At the time Authorization.getContext() is called, the inherited
        context will be combined with the stack walk from the current
        thread and the all domains will include the Subject's principals.

 6. GuardFactory and GuardFactorySpi, Authorization agents will need to
    call GuardFactory, to obtain Guard instances, the Guard
    implementation can call:
      * Authorization::getContext()checkEach(domain -> doSomething(domain));

-- 
Regards,
  
Peter Firmstone

On 26/06/2021 10:05 pm, Peter Firmstone wrote:
>
>
> On 26/06/2021 3:41 pm, Peter Firmstone wrote:
>>
>> Apologies for multiple earlier emails, please ignore and read this 
>> instead.
>>
>> This proposal is about stripping out and simplifying as much of the 
>> dilapidated and complex SecurityManager infrastructure as possible, 
>> while retaining the ability for developers to implement a better high 
>> scaling and performant Authorization layer, without prohibitively 
>> preventing it.
>>
>> Summary of Proposed Changes:
>>
>>  1. GuardFactory & GuardFactorySpi to provide hooks for authorization
>>     checks without SecurityManager or Policy. (Note GuardFactory
>>     should never return null and instead return a no-op Guard that
>>     hotspot can optimize out.
>>  2. Existing Permission implementations to be obtained using
>>     GuardFactorySpi implementations, until their removal.  Note that
>>     when SecurityManager is stubbed out and Permission
>>     implementations are deprecated for removal, these should no
>>     longer be provided by default, but instead need to be enabled by
>>     entries in the java.security config file, in preparation for
>>     their removal.
>>  3. JDK code to no longer call Permission implementations directly,
>>     instances obtained using GuardFactory, when enabled in the
>>     java.security configuration file.
>>  4. Threads (system and virtual) updated to use a singleton
>>     *unprivileged* AccessControlContext, instead of inherited
>>     AccessControlContext, this is more appropriate for Executors, the
>>     original inherited context was designed before Executors were
>>     introduced.
>>  5. Deprecation for removal of all Permission implementations from
>>     the JDK platform.   The existing implementations of Permission
>>     introduce unnecessary complexity; they lack sufficient
>>     flexibility resulting in a proliferation of Permission grants
>>     required in policy files and some make blocking network calls.
>>  6. Introduce a system property to change AccessController's default
>>     behaviour, disable the stack walk by default, but allow it to be
>>     re-enabled with a system property, replace the stack walk array
>>     result of ProtectionDomains with an *unprivileged*
>>     AccessControlContext, the SubjectDomainCombiner can replace it
>>     with a, AccessControlContext containing a single element array,
>>     containing one ProtectionDomain with Principals.
>>  7. AccessController::doPrivileged erases the DomainCombiner by
>>     default, deprecate these methods for removal (make private),
>>     retain doPrivilegedWithCombiner methods that preserve the
>>     SubjectDomainCombiner.   Developers should replace their
>>     doPrivileged methods with doPrivilegedWithCombiner.   Create a
>>     new method AccessController::doUnprivileged, clear intent, to
>>     erase the DomainCombiner, and use the *unprivileged*
>>     AccessControlContext.  Update
>>     AccessController.AccHolder.innocuousAcc to refer to an
>>     *unprivileged* context, as per the definition below.
>>  8. Deprecate for removal the CodeSource::implies method.
>>  9. Give unique ProtectionDomain's with a meaninful CodeSource to
>>     Java modules mapped to the boot loader, rather than using a
>>     Shared ProtectionDomain with a null CodeSource.
>> 10. Deprecate for removal AccessController::checkPermission and
>>     AccessControlContext::checkPermission methods.
>>
> AccessController.checkPermission calls AccessControlContext.optimize, 
> which invokes the DomainCombiner, prior to calling 
> AccessControlContext.checkPermission
>
> In my implementation of SecurityManager, I call 
> AccessController.getContext from within a PrivilegedAction, to 
> optimise a newly created AccessControlContext, 
> (AccessController.getContext also calls AcessControlConext.optimize), 
> prior to calling AccessControlContext.checkPermission.
>
> I think it would be simpler however, to create a new method in 
> AccessController to replace checkPermission which also calls optimize.
>
> I think something could be done here with Stream and Consumer to 
> perform the function checking ProtectionDomain's.  Needs a little more 
> thought, but basically we want to be able to check each 
> ProtectionDomain, without being restricted to Permission or Policy 
> implementations.
>
> Eg:
>
> AccessController.stream(AccessControlContext context).forEach(domain 
> -> Check::domain)
>
> Or
>
> AccessController.checkDomains(AccessControlContext context, 
> Consumer<ProtectionDomain>)
>
> This method would have a relevant Guard.check "RUNTIME" 
> "getProtectionDomain" prior to calling AccessControlContext.optimize 
> and the developer would need to make the call from a PrivilegedAction, 
> and remember pass the relevant guard check for it's own 
> AccessControlContext.
>
>> 11. Undeprecate AccessController, AccessControlContext,
>>     DomainCombiner, SubjectDomainCombiner and Subject::doAs methods,
>>     while deprecating for removal methods stated in items above.
>> 12. Deprecate for removal ProtectionDomain::implies,
>>     ProtectionDomain::getPermissions and
>>     ProtectionDomain::staticPermissionsOnly
>> 13. Replace PermissionCollection type argument with Object in
>>     ProtectionDomain constructors, ignore the permissions parameter,
>>     and deprecate existing constructors.   Deprecate
>>     PermissionCollection for removal.
>> 14. Create a new constructor: ProtectionDomain(CodeSource cs,
>>     ClassLoader cl, Principal[] p).
>> 15. Create a new factory method
>>     ProtectionDomain::newInstance(Principal[] p), to allow a weak
>>     cache of ProtectionDomain instances for each Principal[], to be
>>     utilised by SubjectDomainCombiner to avoid unnecessary
>>     duplication of objects.   This is an optimization for
>>     AccessControlContext::equals and ::hashCode methods.   Using a
>>     cache of AccessControlContext, it is possible to avoid rechecking
>>     authorization that has already been checked.  For example, when
>>     using an Executor with many tasks, all with the same
>>     AccessControlContext, you only need to check once and return the
>>     same result for subsequent checks.   This is an optimization I
>>     have used previously to great effect.
>>
> The ProtectionDomain::newInstance is just a nice to have, 
> SubjectDomainCombiner already caches PD's, just seems a better place 
> to cache for the following reasons:
>
>   * Cache can be utilised by other implementations.
>   * Simplification of SubjectDomainCombiner.
>   * Responsibility of ProtectionDomain.
>
> It's not an essential item, however, just seems like an opportunity 
> for a little refactoring.
>
>> To clarify what an *unprivileged* AccessControlContext is:
>>
>>     An instance of AccessControlContext, that contains a single
>>     element array, containing a ProtectionDomain, with a null
>>     ClassLoader, null Principal[] and a *non-null* CodeSource,
>>     containing a null URL.
>>
>>     So as to distinguish between what is traditionally a JDK
>>     bootstrap ProtectionDomain and unprivileged domain after
>>     ProtectionDomain::getPermissions is removed.
>>
>> Stubbing of SecurityManager and Policy, for runtime backward 
>> compatibility. Update ProtectionDomain::implies method, to *not* 
>> consult with the Policy.  Note it's possible to get access to the 
>> ProtectionDomain array contained within AccessControlContext using a 
>> DomainCombiner.
>>
>> This is backward compatible with existing usages of JAAS and least 
>> painful method of transition for all concerned as well as allowing 
>> complete flexibility of implementation.
>>
>> Regards,
>>
>> Peter Firmstone.
>>
>> On 25/06/2021 3:59 pm, Peter Firmstone wrote:
>>> Thanks Dalibor,
>>>
>>> Would targeting Java 18 be practical?
>>>
>>> Also it won't take long to code a prototype, just not sure of the 
>>> process.
>>>
>>> Cheers,
>>>
>>> Peter.
>>>
>>>
>>> On 24/06/2021 9:30 pm, Dalibor Topic wrote:
>>>> On 24.06.2021 04:24, Peter Firmstone wrote:
>>>>> Thanks Andrew,
>>>>>
>>>>> For the simple case, of replacing the SecurityManager stack walk, 
>>>>> one could use reflection.
>>>>>
>>>>> Thank you for also confirming that is not possible (or at least 
>>>>> very unlikely) to add a GuardBuilder to Java 8, the proposal is 
>>>>> for JDK code to use a provider mechanism, to intercept permission 
>>>>> checks, so custom authentication layers can be implemented, this 
>>>>> could be accepted in future versions of Java, but not existing. As 
>>>>> it is said, there is no harm in asking.
>>>>
>>>> Generally speaking, adding any public APIs to a platform release 
>>>> after its specification has been published, is always going to be a 
>>>> very tall order involving the JCP, among other things. It's not 
>>>> really worth it, when other technical solutions, such as 
>>>> multi-release JARs, already exist, that alleviate the necessity.
>>>>
>>>> cheers,
>>>> dalibor topic
>>>>
> -- 
> Regards,
>   
> Peter Firmstone

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/security-dev/attachments/20210627/7d4261fd/attachment.htm>


More information about the security-dev mailing list