From 59d03be2bfa12b4f8eaa2016387dc62a84abb2b5 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 14 Feb 2017 17:00:07 -0200 Subject: [PATCH] [WFLY-7764] - JACC integration to Undertow subsystem --- .../ApplicationSecurityDomainDefinition.java | 87 +++++++++++++++++- .../wildfly/extension/undertow/Constants.java | 1 + .../undertow/UndertowSubsystemParser_4_0.java | 2 +- .../UndertowDeploymentInfoService.java | 4 + .../undertow/logging/UndertowLogger.java | 5 ++ .../jacc/JACCAuthorizationManager.java | 88 +++++++------------ .../undertow/LocalDescriptions.properties | 1 + .../resources/schema/wildfly-undertow_4_0.xsd | 7 ++ .../extension/undertow/undertow-4.0.xml | 2 +- 9 files changed, 136 insertions(+), 61 deletions(-) diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/ApplicationSecurityDomainDefinition.java b/undertow/src/main/java/org/wildfly/extension/undertow/ApplicationSecurityDomainDefinition.java index 34efa66feb9c..9b3a02855030 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/ApplicationSecurityDomainDefinition.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/ApplicationSecurityDomainDefinition.java @@ -32,6 +32,8 @@ import java.io.IOException; import java.io.InputStream; +import java.security.Permission; +import java.security.Policy; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; @@ -50,11 +52,14 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import javax.security.jacc.WebResourcePermission; +import javax.security.jacc.WebRoleRefPermission; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpSession; @@ -96,9 +101,11 @@ import org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler; import org.wildfly.elytron.web.undertow.server.ScopeSessionListener; import org.wildfly.extension.undertow.logging.UndertowLogger; +import org.wildfly.extension.undertow.security.jacc.JACCAuthorizationManager; import org.wildfly.extension.undertow.security.sso.DistributableApplicationSecurityDomainSingleSignOnManagerBuilder; import org.wildfly.security.auth.server.HttpAuthenticationFactory; import org.wildfly.security.auth.server.SecurityDomain; +import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.http.HttpAuthenticationException; import org.wildfly.security.http.HttpScope; import org.wildfly.security.http.HttpScopeNotification; @@ -113,6 +120,7 @@ import org.wildfly.security.http.util.sso.SingleSignOnSessionFactory; import org.wildfly.security.manager.WildFlySecurityManager; +import io.undertow.security.idm.Account; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.BlockingHandler; @@ -121,9 +129,13 @@ import io.undertow.server.session.SessionIdGenerator; import io.undertow.server.session.SessionManager; import io.undertow.servlet.api.AuthMethodConfig; +import io.undertow.servlet.api.AuthorizationManager; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.core.DefaultAuthorizationManager; import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.util.SavedRequest; @@ -142,6 +154,7 @@ public class ApplicationSecurityDomainDefinition extends PersistentResourceDefin .build(); private static final String HTTP_AUTHENITCATION_FACTORY_CAPABILITY = "org.wildfly.security.http-authentication-factory"; + private static final String JACC_POLICY_CAPABILITY = "org.wildfly.security.jacc-policy"; static final SimpleAttributeDefinition HTTP_AUTHENTICATION_FACTORY = new SimpleAttributeDefinitionBuilder(Constants.HTTP_AUTHENITCATION_FACTORY, ModelType.STRING, false) .setMinSize(1) @@ -158,7 +171,13 @@ public class ApplicationSecurityDomainDefinition extends PersistentResourceDefin .setStorageRuntime() .build(); - private static final AttributeDefinition[] ATTRIBUTES = new AttributeDefinition[] { HTTP_AUTHENTICATION_FACTORY, OVERRIDE_DEPLOYMENT_CONFIG }; + static final SimpleAttributeDefinition ENABLE_JACC = new SimpleAttributeDefinitionBuilder(Constants.ENABLE_JACC, ModelType.BOOLEAN, true) + .setDefaultValue(new ModelNode(false)) + .setMinSize(1) + .setRestartAllServices() + .build(); + + private static final AttributeDefinition[] ATTRIBUTES = new AttributeDefinition[] { HTTP_AUTHENTICATION_FACTORY, OVERRIDE_DEPLOYMENT_CONFIG, ENABLE_JACC }; static final ApplicationSecurityDomainDefinition INSTANCE = new ApplicationSecurityDomainDefinition(); @@ -232,12 +251,13 @@ protected void performRuntime(OperationContext context, ModelNode operation, Res String httpServerMechanismFactory = HTTP_AUTHENTICATION_FACTORY.resolveModelAttribute(context, model).asString(); boolean overrideDeploymentConfig = OVERRIDE_DEPLOYMENT_CONFIG.resolveModelAttribute(context, model).asBoolean(); + boolean enableJacc = ENABLE_JACC.resolveModelAttribute(context, model).asBoolean(); String securityDomainName = context.getCurrentAddressValue(); RuntimeCapability runtimeCapability = APPLICATION_SECURITY_DOMAIN_RUNTIME_CAPABILITY.fromBaseCapability(securityDomainName); ServiceName serviceName = runtimeCapability.getCapabilityServiceName(Function.class); - ApplicationSecurityDomainService applicationSecurityDomainService = new ApplicationSecurityDomainService(overrideDeploymentConfig); + ApplicationSecurityDomainService applicationSecurityDomainService = new ApplicationSecurityDomainService(overrideDeploymentConfig, enableJacc); ServiceBuilder> serviceBuilder = target.addService(serviceName, applicationSecurityDomainService) .setInitialMode(Mode.LAZY); @@ -245,6 +265,10 @@ protected void performRuntime(OperationContext context, ModelNode operation, Res serviceBuilder.addDependency(context.getCapabilityServiceName(HTTP_AUTHENITCATION_FACTORY_CAPABILITY, httpServerMechanismFactory, HttpAuthenticationFactory.class), HttpAuthenticationFactory.class, applicationSecurityDomainService.getHttpAuthenticationFactoryInjector()); + if (enableJacc) { + serviceBuilder.addDependency(ServiceBuilder.DependencyType.REQUIRED, context.getCapabilityServiceName(JACC_POLICY_CAPABILITY, Policy.class)); + } + if (resource.hasChild(UndertowExtension.PATH_SSO)) { ModelNode ssoModel = resource.getChild(UndertowExtension.PATH_SSO).getModel(); @@ -322,12 +346,15 @@ private static class ApplicationSecurityDomainService implements Service httpAuthenticationFactoryInjector = new InjectedValue<>(); private final InjectedValue> singleSignOnTransformer = new InjectedValue<>(); + private final InjectedValue jaccPolicyInjector = new InjectedValue<>(); private final Set registrations = new HashSet<>(); + private final boolean enableJacc; private HttpAuthenticationFactory httpAuthenticationFactory; - private ApplicationSecurityDomainService(final boolean overrideDeploymentConfig) { + private ApplicationSecurityDomainService(final boolean overrideDeploymentConfig, boolean enableJacc) { this.overrideDeploymentConfig = overrideDeploymentConfig; + this.enableJacc = enableJacc; } @Override @@ -371,6 +398,12 @@ private Registration applyElytronSecurity(final DeploymentInfo deploymentInfo) { deploymentInfo.addInnerHandlerChainWrapper(this::finalSecurityHandlers); deploymentInfo.setInitialSecurityWrapper(h -> initialSecurityHandler(deploymentInfo, h, scopeSessionListener)); + if (enableJacc) { + deploymentInfo.setAuthorizationManager(new JACCAuthorizationManager()); + } else { + deploymentInfo.setAuthorizationManager(createElytronAuthorizationManager()); + } + RegistrationImpl registration = new RegistrationImpl(deploymentInfo); synchronized(registrations) { registrations.add(registration); @@ -660,6 +693,54 @@ private HttpHandler finalSecurityHandlers(HttpHandler toWrap) { return new BlockingHandler(new ElytronRunAsHandler(toWrap)); } + private AuthorizationManager createElytronAuthorizationManager() { + return new AuthorizationManager() { + @Override + public boolean isUserInRole(String roleName, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { + return DefaultAuthorizationManager.INSTANCE.isUserInRole(roleName, account, servletInfo, request, deployment); + } + + @Override + public boolean canAccessResource(List mappedConstraints, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { + if (DefaultAuthorizationManager.INSTANCE.canAccessResource(mappedConstraints, account, servletInfo, request, deployment)) { + return true; + } + + SecurityDomain securityDomain = httpAuthenticationFactory.getSecurityDomain(); + SecurityIdentity securityIdentity = securityDomain.getCurrentSecurityIdentity(); + + if (securityIdentity == null) { + return false; + } + + List permissions = new ArrayList<>(); + + permissions.add(new WebResourcePermission(getCanonicalURI(request), request.getMethod())); + permissions.addAll(account.getRoles().stream().map((Function) roleName -> new WebRoleRefPermission(getCanonicalURI(request), request.getMethod())).collect(Collectors.toList())); + + for (Permission permission : permissions) { + if (securityIdentity.implies(permission)) { + return true; + } + } + + return false; + } + + @Override + public io.undertow.servlet.api.TransportGuaranteeType transportGuarantee(io.undertow.servlet.api.TransportGuaranteeType currentConnectionGuarantee, io.undertow.servlet.api.TransportGuaranteeType configuredRequiredGuarantee, HttpServletRequest request) { + return DefaultAuthorizationManager.INSTANCE.transportGuarantee(currentConnectionGuarantee, configuredRequiredGuarantee, request); + } + + private String getCanonicalURI(HttpServletRequest request) { + String canonicalURI = request.getRequestURI().substring(request.getContextPath().length()); + if (canonicalURI == null || canonicalURI.equals("/")) + canonicalURI = ""; + return canonicalURI; + } + }; + } + private String[] getDeployments() { synchronized(registrations) { return registrations.stream().map(r -> r.deploymentInfo.getDeploymentName()).collect(Collectors.toList()).toArray(new String[registrations.size()]); diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java b/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java index 2eb14986dabd..cf9233e0acdc 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java @@ -235,5 +235,6 @@ public interface Constants { String OVERRIDE_DEPLOYMENT_CONFIG = "override-deployment-config"; String REFERENCING_DEPLOYMENTS = "referencing-deployments"; String SECURITY_DOMAIN = "security-domain"; + String ENABLE_JACC = "enable-jacc"; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_4_0.java b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_4_0.java index c459aa7ef108..bcabfff3a3c7 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_4_0.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_4_0.java @@ -321,7 +321,7 @@ public class UndertowSubsystemParser_4_0 extends PersistentResourceXMLParser { .addChild( builder(ApplicationSecurityDomainDefinition.INSTANCE.getPathElement()) .setXmlWrapperElement(Constants.APPLICATION_SECURITY_DOMAINS) - .addAttributes(ApplicationSecurityDomainDefinition.HTTP_AUTHENTICATION_FACTORY, ApplicationSecurityDomainDefinition.OVERRIDE_DEPLOYMENT_CONFIG) + .addAttributes(ApplicationSecurityDomainDefinition.HTTP_AUTHENTICATION_FACTORY, ApplicationSecurityDomainDefinition.OVERRIDE_DEPLOYMENT_CONFIG, ApplicationSecurityDomainDefinition.ENABLE_JACC) .addChild(builder(UndertowExtension.PATH_SSO) .addAttribute(SingleSignOnDefinition.Attribute.DOMAIN.getDefinition()) .addAttribute(SingleSignOnDefinition.Attribute.PATH.getDefinition()) diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/deployment/UndertowDeploymentInfoService.java b/undertow/src/main/java/org/wildfly/extension/undertow/deployment/UndertowDeploymentInfoService.java index db4b7e22dca7..8c6bfc1bfea7 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/deployment/UndertowDeploymentInfoService.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/deployment/UndertowDeploymentInfoService.java @@ -982,6 +982,10 @@ private DeploymentInfo createServletConfig() throws StartException { Function securityFunction = this.securityFunction.getOptionalValue(); if (securityFunction != null) { registration = securityFunction.apply(d); + d.addOuterHandlerChainWrapper(JACCContextIdHandler.wrapper(jaccContextId)); + if(mergedMetaData.isUseJBossAuthorization()) { + UndertowLogger.ROOT_LOGGER.configurationOptionIgnoredWhenUsingElytron("use-jboss-authorization"); + } } else { if (securityDomain != null) { d.addThreadSetupAction(new SecurityContextThreadSetupAction(securityDomain, securityDomainContextValue.getValue(), principalVersusRolesMap)); diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/logging/UndertowLogger.java b/undertow/src/main/java/org/wildfly/extension/undertow/logging/UndertowLogger.java index e365641ea815..dd52d8871fdd 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/logging/UndertowLogger.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/logging/UndertowLogger.java @@ -383,4 +383,9 @@ public interface UndertowLogger extends BasicLogger { @Message(id = 93, value = "Credential %s is not a clear text password") IllegalArgumentException credentialNotClearPassword(String alias); + + @Message(id = 94, value = "Configuration option [%s] ignored when using Elytron subsystem") + @LogMessage(level = WARN) + void configurationOptionIgnoredWhenUsingElytron(String option); + } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/security/jacc/JACCAuthorizationManager.java b/undertow/src/main/java/org/wildfly/extension/undertow/security/jacc/JACCAuthorizationManager.java index 64d856035599..d89026e0f20b 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/security/jacc/JACCAuthorizationManager.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/security/jacc/JACCAuthorizationManager.java @@ -22,13 +22,20 @@ package org.wildfly.extension.undertow.security.jacc; +import static java.security.AccessController.doPrivileged; + import java.security.CodeSource; +import java.security.Permission; +import java.security.Policy; import java.security.Principal; +import java.security.PrivilegedAction; import java.security.ProtectionDomain; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import javax.security.jacc.WebResourcePermission; import javax.security.jacc.WebRoleRefPermission; @@ -41,7 +48,7 @@ import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.SingleConstraintMatch; import io.undertow.servlet.api.TransportGuaranteeType; -import org.jboss.security.SimplePrincipal; +import org.wildfly.security.manager.WildFlySecurityManager; /** *

@@ -54,41 +61,19 @@ public class JACCAuthorizationManager implements AuthorizationManager { @Override public boolean isUserInRole(final String roleName, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, final Deployment deployment) { - - // create the WebRoleRefPermission that will be used by JACC to determine if the user has the specified role. - final WebRoleRefPermission permission = new WebRoleRefPermission(servletInfo.getName(), roleName); - - // create a protection domain with the user roles (or account principal if no roles are found) - final Map> principalVersusRolesMap = deployment.getDeploymentInfo().getPrincipalVersusRolesMap(); - final Principal[] principals = this.getPrincipals(account, principalVersusRolesMap); - final CodeSource codeSource = servletInfo.getServletClass().getProtectionDomain().getCodeSource(); - final ProtectionDomain protectionDomain = new ProtectionDomain(codeSource, null, null, principals); - - // call implies in the protection domain using the constructed WebRoleRefPermission. - return protectionDomain.implies(permission); + return hasPermission(account, deployment, servletInfo, new WebRoleRefPermission(servletInfo.getName(), roleName)); } @Override public boolean canAccessResource(List constraints, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, Deployment deployment) { - - // create the WebResourcePermission that will be used by JACC to determine if access to the resource should be granted or not. - final WebResourcePermission permission = new WebResourcePermission(this.getCanonicalURI(request), request.getMethod()); - - // create a protection domain with the user roles (or account principal if no roles are found) - final Map> principalVersusRolesMap = deployment.getDeploymentInfo().getPrincipalVersusRolesMap(); - final Principal[] principals = this.getPrincipals(account, principalVersusRolesMap); - final CodeSource codeSource = servletInfo.getServletClass().getProtectionDomain().getCodeSource(); - final ProtectionDomain protectionDomain = new ProtectionDomain(codeSource, null, null, principals); - - return protectionDomain.implies(permission); + return hasPermission(account, deployment, servletInfo, new WebResourcePermission(request)); } @Override public TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentConnGuarantee, TransportGuaranteeType configuredRequiredGuarantee, final HttpServletRequest request) { - final ProtectionDomain domain = new ProtectionDomain(null, null, null, null); final String[] httpMethod = new String[] {request.getMethod()}; - final String canonicalURI = this.getCanonicalURI(request); + final String canonicalURI = getCanonicalURI(request); switch (currentConnGuarantee) { case NONE: { @@ -96,7 +81,7 @@ public TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentC WebUserDataPermission permission = new WebUserDataPermission(canonicalURI, httpMethod, null); // if permission was implied then the unprotected connection is ok. - if (domain.implies(permission)) { + if (hasPermission(domain, permission)) { return TransportGuaranteeType.NONE; } else { @@ -110,13 +95,13 @@ public TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentC WebUserDataPermission permission = new WebUserDataPermission(canonicalURI, httpMethod, TransportGuaranteeType.CONFIDENTIAL.name()); - if (domain.implies(permission)) { + if (hasPermission(domain, permission)) { return TransportGuaranteeType.CONFIDENTIAL; } else { // try with the INTEGRAL connection guarantee type. permission = new WebUserDataPermission(canonicalURI, httpMethod, TransportGuaranteeType.INTEGRAL.name()); - if (domain.implies(permission)) { + if (hasPermission(domain, permission)) { return TransportGuaranteeType.INTEGRAL; } else { @@ -144,36 +129,27 @@ private String getCanonicalURI(HttpServletRequest request) { return canonicalURI; } - /** - *

- * Merges the roles found in the specified account parameter with the mapped roles in the second parameter and returns - * the resulting set as a {@link Principal} array. - *

- * - * @param account the authenticated user account. - * @param principalVersusRolesMap the principal to roles map as configured in the deployment. - * @return a {@link Principal}[] containing the merged roles. If the specified account is {@code null}, this method - * returns {@code null}. If the account is not null but no roles can be associated with the account principal, then - * the account principal is returned. - */ - private Principal[] getPrincipals(Account account, Map> principalVersusRolesMap) { - - if (account == null) - return null; + private boolean hasPermission(Account account, Deployment deployment, ServletInfo servletInfo, Permission permission) { + CodeSource codeSource = servletInfo.getServletClass().getProtectionDomain().getCodeSource(); + ProtectionDomain domain = new ProtectionDomain(codeSource, null, null, getGrantedRoles(account, deployment)); + return hasPermission(domain, permission); + } - final Set mappedRoles = principalVersusRolesMap.get(account.getPrincipal().getName()); + private boolean hasPermission(ProtectionDomain domain, Permission permission) { + Policy policy = WildFlySecurityManager.isChecking() ? doPrivileged((PrivilegedAction) Policy::getPolicy) : Policy.getPolicy(); + return policy.implies(domain, permission); + } - // create a set that merges the account roles with deployment roles (if any) - final Set roles = new HashSet(); - for (String role : account.getRoles()) - roles.add(new SimplePrincipal(role)); - if (mappedRoles != null) { - for (String role : mappedRoles) - roles.add(new SimplePrincipal(role)); + private Principal[] getGrantedRoles(Account account, Deployment deployment) { + if (account == null) { + return new Principal[] {}; } - if (roles.isEmpty()) - return new Principal[] {account.getPrincipal()}; - return roles.toArray(new Principal[roles.size()]); + Set roles = new HashSet<>(account.getRoles()); + Map> principalVersusRolesMap = deployment.getDeploymentInfo().getPrincipalVersusRolesMap(); + + roles.addAll(principalVersusRolesMap.getOrDefault(account.getPrincipal().getName(), Collections.emptySet())); + + return roles.stream().map((Function) roleName -> (Principal) () -> roleName).toArray(Principal[]::new); } } diff --git a/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties b/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties index ac5dcf3f94d2..5c8456188df9 100644 --- a/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties +++ b/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties @@ -367,6 +367,7 @@ undertow.application-security-domain.add=Add a new application referenced securi undertow.application-security-domain.remove=Remove the application referenced security domain mapping. undertow.application-security-domain.http-authentication-factory=The HTTP Authentication Factory to be used by deployments that reference the mapped security domain. undertow.application-security-domain.override-deployment-config=Should the authentication configuration in the deployment be overridden by the factory. +undertow.application-security-domain.enable-jacc=Enable authorization using JACC undertow.application-security-domain.referencing-deployments=The deployments currently referencing this mapping. undertow.application-security-domain.setting=Settings diff --git a/undertow/src/main/resources/schema/wildfly-undertow_4_0.xsd b/undertow/src/main/resources/schema/wildfly-undertow_4_0.xsd index 94ee0acada67..6adac6139507 100644 --- a/undertow/src/main/resources/schema/wildfly-undertow_4_0.xsd +++ b/undertow/src/main/resources/schema/wildfly-undertow_4_0.xsd @@ -675,6 +675,13 @@ + + + + Enable authorization using JACC. + + + diff --git a/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-4.0.xml b/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-4.0.xml index 094ff8ed42ac..080b771ab303 100644 --- a/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-4.0.xml +++ b/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-4.0.xml @@ -135,7 +135,7 @@ - +