Re: READ 1ST: Re: Authorization layer API and low level access checks
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 privilegedCall(Callable c) // Always preserves subject, if any. * Callable privilegedCall(Subject s, Callable c) // Uses provided Subject, from LoginModule, or executing in the context of an authenticated client over a secure connection. * Callable privilegedCall(AuthorizationContext ac, Callable 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 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
Re: READ 1ST: Re: Authorization layer API and low level access checks
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) 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,
READ 1ST: Re: Authorization layer API and low level access checks
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. 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. 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