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