From 052e0155aad5ec8000a829690b878feb92fa0c85 Mon Sep 17 00:00:00 2001 From: Farah Juma Date: Thu, 24 Oct 2019 17:47:50 -0400 Subject: [PATCH] [ELY-1894] Add the ability to make use of the IP address of a remote client when making authorization decisions --- .../auth/realm/AggregateSecurityRealm.java | 1 + .../auth/realm/AggregateAttributesTest.java | 1 + .../security/auth/server/SecurityDomain.java | 31 +++- .../auth/server/SecurityIdentity.java | 26 +++ .../server/ServerAuthenticationContext.java | 162 ++++++++++++---- .../security/authz}/AggregateAttributes.java | 8 +- .../security/authz/AuthorizationIdentity.java | 50 +++++ .../wildfly/security/authz/RoleDecoder.java | 26 +++ .../authz/SourceAddressRoleDecoder.java | 90 +++++++++ .../security/authz/RoleDecoderTest.java | 141 ++++++++++++++ .../SourceAddressRuntimeAttributesTest.java | 173 ++++++++++++++++++ 11 files changed, 662 insertions(+), 47 deletions(-) rename auth/{realm/base/src/main/java/org/wildfly/security/auth/realm => server/base/src/main/java/org/wildfly/security/authz}/AggregateAttributes.java (90%) create mode 100644 auth/server/base/src/main/java/org/wildfly/security/authz/SourceAddressRoleDecoder.java create mode 100644 auth/server/base/src/test/java/org/wildfly/security/authz/RoleDecoderTest.java create mode 100644 tests/base/src/test/java/org/wildfly/security/auth/server/SourceAddressRuntimeAttributesTest.java diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java index a3df5b46ec0..347d687c59b 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java @@ -29,6 +29,7 @@ import org.wildfly.security.auth.server.event.RealmAuthenticationEvent; import org.wildfly.security.auth.server.event.RealmAuthorizationEvent; import org.wildfly.security.auth.server.event.RealmEvent; +import org.wildfly.security.authz.AggregateAttributes; import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.AuthorizationIdentity; import org.wildfly.security.credential.Credential; diff --git a/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/AggregateAttributesTest.java b/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/AggregateAttributesTest.java index 695ee81f123..f3822e76a44 100644 --- a/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/AggregateAttributesTest.java +++ b/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/AggregateAttributesTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import org.wildfly.security.authz.AggregateAttributes; import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.Attributes.Entry; import org.wildfly.security.authz.MapAttributes; diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityDomain.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityDomain.java index 62eb80d5640..ab0d9dc7f3f 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityDomain.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityDomain.java @@ -99,6 +99,7 @@ public final class SecurityDomain { private final Predicate trustedSecurityDomain; private final Consumer securityEventListener; private final Function evidenceDecoder; + private final RoleDecoder roleDecoder; SecurityDomain(Builder builder, final LinkedHashMap realmMap) { this.realmMap = realmMap; @@ -112,6 +113,7 @@ public final class SecurityDomain { this.trustedSecurityDomain = builder.trustedSecurityDomain; this.securityEventListener = builder.securityEventListener; this.evidenceDecoder = builder.evidenceDecoder; + this.roleDecoder = builder.roleDecoder; final Map originalRoleMappers = builder.categoryRoleMappers; final Map copiedRoleMappers; if (originalRoleMappers.isEmpty()) { @@ -698,15 +700,19 @@ Roles mapRoles(SecurityIdentity securityIdentity) { // zeroth role mapping, just grab roles from the identity Roles decodedRoles = realmInfo.getRoleDecoder().decodeRoles(identity); + // determine roles based on any runtime attributes associated with the identity + Roles domainDecodedRoles = securityIdentity.getSecurityDomain().getRoleDecoder().decodeRoles(identity); + Roles combinedRoles = decodedRoles.or(domainDecodedRoles); + // apply the first level mapping, which is based on the role mapper associated with a realm. - Roles realmMappedRoles = realmInfo.getRoleMapper().mapRoles(decodedRoles); + Roles realmMappedRoles = realmInfo.getRoleMapper().mapRoles(combinedRoles); // apply the second level mapping, which is based on the role mapper associated with this security domain. Roles domainMappedRoles = roleMapper.mapRoles(realmMappedRoles); if (log.isTraceEnabled()) { - log.tracef("Role mapping: principal [%s] -> decoded roles [%s] -> realm mapped roles [%s] -> domain mapped roles [%s]", - securityIdentity.getPrincipal(), String.join(", ", decodedRoles), String.join(", ", realmMappedRoles), String.join(", ", domainMappedRoles)); + log.tracef("Role mapping: principal [%s] -> decoded roles [%s] -> domain decoded roles [%s] -> realm mapped roles [%s] -> domain mapped roles [%s]", + securityIdentity.getPrincipal(), String.join(", ", decodedRoles), String.join(", ", domainDecodedRoles), String.join(", ", realmMappedRoles), String.join(", ", domainMappedRoles)); } return domainMappedRoles; @@ -788,6 +794,10 @@ Function getEvidenceDecoder() { return evidenceDecoder; } + RoleDecoder getRoleDecoder() { + return roleDecoder; + } + /** * A builder for creating new security domains. */ @@ -807,6 +817,7 @@ public static final class Builder { private Predicate trustedSecurityDomain = domain -> false; private Consumer securityEventListener = e -> {}; private Function evidenceDecoder = evidence -> evidence.getDefaultPrincipal(); + private RoleDecoder roleDecoder = RoleDecoder.EMPTY; Builder() { } @@ -1027,6 +1038,20 @@ public Builder setEvidenceDecoder(EvidenceDecoder evidenceDecoder) { return this; } + /** + * Set the role decoder for this security domain. + * + * @param roleDecoder the role decoder (must not be {@code null}) + * @return this builder + * @since 1.11.0 + */ + public Builder setRoleDecoder(RoleDecoder roleDecoder) { + Assert.checkNotNullParam("roleDecoder", roleDecoder); + assertNotBuilt(); + this.roleDecoder = roleDecoder; + return this; + } + /** * Construct this security domain. * diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityIdentity.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityIdentity.java index 858f667f6d3..5a0857eed68 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityIdentity.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/SecurityIdentity.java @@ -207,6 +207,21 @@ public final class SecurityIdentity implements PermissionVerifier, PermissionMap this.withIdentities = old.withIdentities; } + SecurityIdentity(final SecurityIdentity old, final Attributes runtimeAttributes) { + this.securityDomain = old.securityDomain; + this.principal = old.principal; + this.realmInfo = old.realmInfo; + this.authorizationIdentity = AuthorizationIdentity.basicIdentity(old.authorizationIdentity, runtimeAttributes); + this.defaultRoles = old.defaultRoles; + this.roleMappers = old.roleMappers; + this.creationTime = old.creationTime; + this.verifier = old.verifier; + this.publicCredentials = old.publicCredentials; + this.privateCredentials = old.privateCredentials; + this.withSuppliedIdentities = null; + this.withIdentities = old.withIdentities; + } + SecurityDomain getSecurityDomain() { return securityDomain; } @@ -891,6 +906,17 @@ public SecurityIdentity withPrivateCredentials(final IdentityCredentials credent return credentials == IdentityCredentials.NONE ? this : new SecurityIdentity(this, credentials, true); } + /** + * Create a new security identity which is the same as this one, but which includes the given runtime attributes. + * + * @param runtimeAttributes the runtime attributes (must not be {@code null}) + * @return the new identity + */ + public SecurityIdentity withRuntimeAttributes(final Attributes runtimeAttributes) { + Assert.checkNotNullParam("runtimeAttributes", runtimeAttributes); + return runtimeAttributes == Attributes.EMPTY ? this : new SecurityIdentity(this, runtimeAttributes); + } + /** * Get the private credentials of this identity. The caller must have the {@code getPrivateCredentials} {@link ElytronPermission}. * diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java index f448e95e027..9052b106368 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java @@ -20,8 +20,10 @@ import static org.wildfly.common.Assert.checkNotNullParam; import static org.wildfly.security.auth.server._private.ElytronMessages.log; +import static org.wildfly.security.authz.RoleDecoder.KEY_SOURCE_ADDRESS; import java.io.IOException; +import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.cert.X509Certificate; @@ -74,7 +76,10 @@ import org.wildfly.security.auth.server.event.SecurityAuthenticationFailedEvent; import org.wildfly.security.auth.server.event.SecurityAuthenticationSuccessfulEvent; import org.wildfly.security.auth.server.event.SecurityRealmUnavailableEvent; +import org.wildfly.security.authz.AggregateAttributes; +import org.wildfly.security.authz.Attributes; import org.wildfly.security.authz.AuthorizationIdentity; +import org.wildfly.security.authz.MapAttributes; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.credential.source.CredentialSource; @@ -311,7 +316,7 @@ public final class ServerAuthenticationContext implements AutoCloseable { } ServerAuthenticationContext(final SecurityIdentity capturedIdentity, final MechanismConfigurationSelector mechanismConfigurationSelector) { - stateRef = new AtomicReference<>(new InactiveState(capturedIdentity, mechanismConfigurationSelector, IdentityCredentials.NONE, IdentityCredentials.NONE)); + stateRef = new AtomicReference<>(new InactiveState(capturedIdentity, mechanismConfigurationSelector, IdentityCredentials.NONE, IdentityCredentials.NONE, Attributes.EMPTY)); } /** @@ -793,6 +798,16 @@ public void addPrivateCredential(Credential credential) { stateRef.get().addPrivateCredential(credential); } + /** + * Add runtime attributes to the identity being authenticated. + * + * @param runtimeAttributes the runtime attributes to add (must not be {@code null}) + */ + public void addRuntimeAttributes(Attributes runtimeAttributes) { + Assert.checkNotNullParam("runtimeAttributes", runtimeAttributes); + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + /** * Attempt to import the given security identity as a trusted identity. If this method returns {@code true}, * the context will be in an authorized state, and the new identity can be retrieved. @@ -1040,7 +1055,9 @@ private void handleOne(final Callback[] callbacks, final int idx) throws IOExcep final SocketAddressCallback socketAddressCallback = (SocketAddressCallback) callback; log.tracef("Handling SocketAddressCallback"); if (socketAddressCallback.getKind() == SocketAddressCallback.Kind.PEER) { - // todo: filter by IP address + Attributes runtimeAttributes = new MapAttributes(); + runtimeAttributes.addFirst(KEY_SOURCE_ADDRESS, ((InetSocketAddress) socketAddressCallback.getAddress()).getAddress().getHostAddress()); + addRuntimeAttributes(runtimeAttributes); } handleOne(callbacks, idx + 1); } else if (callback instanceof SecurityIdentityCallback) { @@ -1152,28 +1169,28 @@ private static String mapRealmName(Principal principal, RealmMapper realmMapper, return realmName != null ? realmName : defaultRealmName; } - State assignName(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, Principal originalPrincipal, final Evidence evidence, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) throws RealmUnavailableException { - return assignName(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, originalPrincipal, evidence, privateCredentials, publicCredentials, false); + State assignName(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, Principal originalPrincipal, final Evidence evidence, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) throws RealmUnavailableException { + return assignName(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, originalPrincipal, evidence, privateCredentials, publicCredentials, false, runtimeAttributes); } - State assignName(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, Principal originalPrincipal, final Evidence evidence, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final boolean exclusive) throws RealmUnavailableException { + State assignName(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, Principal originalPrincipal, final Evidence evidence, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final boolean exclusive, final Attributes runtimeAttributes) throws RealmUnavailableException { final SecurityDomain domain = capturedIdentity.getSecurityDomain(); final Principal preRealmPrincipal = rewriteAll(originalPrincipal, mechanismRealmConfiguration.getPreRealmRewriter(), mechanismConfiguration.getPreRealmRewriter(), domain.getPreRealmRewriter()); if (preRealmPrincipal == null) { log.tracef("Unable to rewrite principal [%s] by pre-realm rewritters", originalPrincipal); - return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials); + return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials, runtimeAttributes); } String realmName = mapAll(preRealmPrincipal, mechanismRealmConfiguration.getRealmMapper(), mechanismConfiguration.getRealmMapper(), domain.getRealmMapper(), domain.getDefaultRealmName()); final RealmInfo realmInfo = domain.getRealmInfo(realmName); final Principal postRealmPrincipal = rewriteAll(preRealmPrincipal, mechanismRealmConfiguration.getPostRealmRewriter(), mechanismConfiguration.getPostRealmRewriter(), domain.getPostRealmRewriter()); if (postRealmPrincipal == null) { log.tracef("Unable to rewrite principal [%s] by post-realm rewritters", preRealmPrincipal); - return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials); + return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials, runtimeAttributes); } final Principal finalPrincipal = rewriteAll(postRealmPrincipal, mechanismRealmConfiguration.getFinalRewriter(), mechanismConfiguration.getFinalRewriter(), realmInfo.getPrincipalRewriter()); if (finalPrincipal == null) { log.tracef("Unable to rewrite principal [%s] by final rewritters", postRealmPrincipal); - return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials); + return new InvalidNameState(capturedIdentity, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials, runtimeAttributes); } log.tracef("Principal assigning: [%s], pre-realm rewritten: [%s], realm name: [%s], post-realm rewritten: [%s], realm rewritten: [%s]", @@ -1197,7 +1214,7 @@ State assignName(final SecurityIdentity capturedIdentity, final MechanismConfigu } - return new NameAssignedState(capturedIdentity, realmInfo, realmIdentity, preRealmPrincipal, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials); + return new NameAssignedState(capturedIdentity, realmInfo, realmIdentity, preRealmPrincipal, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials, runtimeAttributes); } abstract static class State { @@ -1297,6 +1314,10 @@ void addPrivateCredential(final Credential credential) { throw log.noAuthenticationInProgress(); } + void addRuntimeAttributes(final Attributes runtimeAttributes) { + throw log.noAuthenticationInProgress(); + } + /** * Indicate whether or not current state is {@link NameAssignedState}. * @@ -1332,23 +1353,25 @@ final class InactiveState extends State { private final MechanismInformation mechanismInformation; private final IdentityCredentials privateCredentials; private final IdentityCredentials publicCredentials; + private final Attributes runtimeAttributes; - public InactiveState(SecurityIdentity capturedIdentity, MechanismConfigurationSelector mechanismConfigurationSelector, IdentityCredentials privateCredentials, IdentityCredentials publicCredentials) { - this(capturedIdentity, mechanismConfigurationSelector, MechanismInformation.DEFAULT, privateCredentials, publicCredentials); + public InactiveState(SecurityIdentity capturedIdentity, MechanismConfigurationSelector mechanismConfigurationSelector, IdentityCredentials privateCredentials, IdentityCredentials publicCredentials, Attributes runtimeAttributes) { + this(capturedIdentity, mechanismConfigurationSelector, MechanismInformation.DEFAULT, privateCredentials, publicCredentials, runtimeAttributes); } public InactiveState(SecurityIdentity capturedIdentity, MechanismConfigurationSelector mechanismConfigurationSelector, - MechanismInformation mechanismInformation, IdentityCredentials privateCredentials, IdentityCredentials publicCredentials) { + MechanismInformation mechanismInformation, IdentityCredentials privateCredentials, IdentityCredentials publicCredentials, Attributes runtimeAttributes) { this.capturedIdentity = capturedIdentity; this.mechanismConfigurationSelector = mechanismConfigurationSelector; this.mechanismInformation = checkNotNullParam("mechanismInformation", mechanismInformation); this.privateCredentials = privateCredentials; this.publicCredentials = publicCredentials; + this.runtimeAttributes = runtimeAttributes; } @Override void setMechanismInformation(MechanismInformation mechanismInformation) { - InactiveState inactiveState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials); + InactiveState inactiveState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials, runtimeAttributes); InitialState nextState = inactiveState.selectMechanismConfiguration(); if (! stateRef.compareAndSet(this, nextState)) { stateRef.get().setMechanismInformation(mechanismInformation); @@ -1427,7 +1450,7 @@ MechanismConfiguration getMechanismConfiguration() { @Override void addPublicCredential(final Credential credential) { - final InactiveState newState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials.withCredential(credential)); + final InactiveState newState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials.withCredential(credential), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } @@ -1435,12 +1458,20 @@ void addPublicCredential(final Credential credential) { @Override void addPrivateCredential(final Credential credential) { - final InactiveState newState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials.withCredential(credential), publicCredentials); + final InactiveState newState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials.withCredential(credential), publicCredentials, runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPrivateCredential(credential); } } + @Override + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final InactiveState newState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials, AggregateAttributes.aggregateOf(this.runtimeAttributes, runtimeAttributes)); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } + private void transition() { InitialState initialState = selectMechanismConfiguration(); stateRef.compareAndSet(this, initialState); @@ -1453,7 +1484,7 @@ private InitialState selectMechanismConfiguration() { mechanismInformation.getMechanismName(), mechanismInformation.getHostName(), mechanismInformation.getProtocol()); } - return new InitialState(capturedIdentity, mechanismConfiguration, mechanismConfigurationSelector, privateCredentials, publicCredentials); + return new InitialState(capturedIdentity, mechanismConfiguration, mechanismConfigurationSelector, privateCredentials, publicCredentials, runtimeAttributes); } } @@ -1469,7 +1500,7 @@ boolean authorize(Principal authorizationId, boolean authorizeRunAs) throws Real // get the identity we are authorizing from final SecurityIdentity sourceIdentity = getSourceIdentity(); - final State state = assignName(sourceIdentity, getMechanismConfiguration(), getMechanismRealmConfiguration(), authorizationId, null, IdentityCredentials.NONE, IdentityCredentials.NONE); + final State state = assignName(sourceIdentity, getMechanismConfiguration(), getMechanismRealmConfiguration(), authorizationId, null, IdentityCredentials.NONE, IdentityCredentials.NONE, Attributes.EMPTY); if (!state.isNameAssigned()) { ElytronMessages.log.tracef("Authorization failed - unable to assign identity name"); return false; @@ -1536,12 +1567,14 @@ abstract class UnassignedState extends ActiveState { final MechanismConfiguration mechanismConfiguration; final IdentityCredentials privateCredentials; final IdentityCredentials publicCredentials; + final Attributes runtimeAttributes; - UnassignedState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) { + UnassignedState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) { this.capturedIdentity = capturedIdentity; this.mechanismConfiguration = mechanismConfiguration; this.privateCredentials = privateCredentials; this.publicCredentials = publicCredentials; + this.runtimeAttributes = runtimeAttributes; } SecurityIdentity getSourceIdentity() { @@ -1608,7 +1641,7 @@ boolean importIdentity(final SecurityIdentity importedIdentity) throws RealmUnav } // Finally, run the identity through the normal name selection process. - final State state = assignName(sourceIdentity, mechanismConfiguration, getMechanismRealmConfiguration(), importedPrincipal, null, privateCredentials, publicCredentials); + final State state = assignName(sourceIdentity, mechanismConfiguration, getMechanismRealmConfiguration(), importedPrincipal, null, privateCredentials, publicCredentials, runtimeAttributes); if (!state.isNameAssigned()) { return false; } @@ -1654,7 +1687,7 @@ boolean verifyEvidence(final Evidence evidence) throws RealmUnavailableException log.tracef("Evidence verification: evidence = %s evidencePrincipal = %s", evidence, evidencePrincipal); final MechanismRealmConfiguration mechanismRealmConfiguration = getMechanismRealmConfiguration(); if (evidencePrincipal != null) { - final State newState = assignName(getSourceIdentity(), mechanismConfiguration, mechanismRealmConfiguration, evidencePrincipal, evidence, privateCredentials, publicCredentials); + final State newState = assignName(getSourceIdentity(), mechanismConfiguration, mechanismRealmConfiguration, evidencePrincipal, evidence, privateCredentials, publicCredentials, runtimeAttributes); if (! newState.verifyEvidence(evidence)) { if (newState.isNameAssigned()) { ((NameAssignedState)newState).realmIdentity.dispose(); @@ -1705,7 +1738,7 @@ boolean verifyEvidence(final Evidence evidence) throws RealmUnavailableException realmIdentity.dispose(); return false; } - final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), realmInfo, realmIdentity, resolvedPrincipal, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials); + final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), realmInfo, realmIdentity, resolvedPrincipal, mechanismConfiguration, mechanismRealmConfiguration, privateCredentials, publicCredentials, runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { realmIdentity.dispose(); return stateRef.get().verifyEvidence(evidence); @@ -1717,7 +1750,7 @@ boolean verifyEvidence(final Evidence evidence) throws RealmUnavailableException void setPrincipal(final Principal principal, final boolean exclusive) throws RealmUnavailableException { Assert.checkNotNullParam("principal", principal); final AtomicReference stateRef = getStateRef(); - final State newState = assignName(capturedIdentity, mechanismConfiguration, getMechanismRealmConfiguration(), principal, null, privateCredentials, publicCredentials, exclusive); + final State newState = assignName(capturedIdentity, mechanismConfiguration, getMechanismRealmConfiguration(), principal, null, privateCredentials, publicCredentials, exclusive, runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { if (newState.isNameAssigned()) { ((NameAssignedState)newState).realmIdentity.dispose(); @@ -1738,14 +1771,18 @@ IdentityCredentials getPrivateCredentials() { IdentityCredentials getPublicCredentials() { return publicCredentials; } + + Attributes getRuntimeAttributes() { + return runtimeAttributes; + } } final class InitialState extends UnassignedState { private final MechanismConfigurationSelector mechanismConfigurationSelector; - InitialState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismConfigurationSelector mechanismConfigurationSelector, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) { - super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials); + InitialState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismConfigurationSelector mechanismConfigurationSelector, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) { + super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials, runtimeAttributes); this.mechanismConfigurationSelector = mechanismConfigurationSelector; } @@ -1761,7 +1798,7 @@ void setMechanismRealmName(final String realmName) { throw log.invalidMechRealmSelection(realmName); } final AtomicReference stateRef = getStateRef(); - if (! stateRef.compareAndSet(this, new RealmAssignedState(capturedIdentity, mechanismConfiguration, configuration, privateCredentials, publicCredentials))) { + if (! stateRef.compareAndSet(this, new RealmAssignedState(capturedIdentity, mechanismConfiguration, configuration, privateCredentials, publicCredentials, runtimeAttributes))) { stateRef.get().setMechanismRealmName(realmName); } } @@ -1780,7 +1817,7 @@ MechanismRealmConfiguration getMechanismRealmConfiguration() { @Override void setMechanismInformation(MechanismInformation mechanismInformation) { - InactiveState inactiveState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials); + InactiveState inactiveState = new InactiveState(capturedIdentity, mechanismConfigurationSelector, mechanismInformation, privateCredentials, publicCredentials, runtimeAttributes); InitialState newState = inactiveState.selectMechanismConfiguration(); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().setMechanismInformation(mechanismInformation); @@ -1788,7 +1825,7 @@ void setMechanismInformation(MechanismInformation mechanismInformation) { } void addPublicCredential(final Credential credential) { - final InitialState newState = new InitialState(getSourceIdentity(), getMechanismConfiguration(), mechanismConfigurationSelector, getPrivateCredentials(), getPublicCredentials().withCredential(credential)); + final InitialState newState = new InitialState(getSourceIdentity(), getMechanismConfiguration(), mechanismConfigurationSelector, getPrivateCredentials(), getPublicCredentials().withCredential(credential), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } @@ -1796,18 +1833,25 @@ void addPublicCredential(final Credential credential) { @Override void addPrivateCredential(final Credential credential) { - final InitialState newState = new InitialState(getSourceIdentity(), getMechanismConfiguration(), mechanismConfigurationSelector, getPrivateCredentials().withCredential(credential), getPublicCredentials()); + final InitialState newState = new InitialState(getSourceIdentity(), getMechanismConfiguration(), mechanismConfigurationSelector, getPrivateCredentials().withCredential(credential), getPublicCredentials(), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } } + + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final InitialState newState = new InitialState(getSourceIdentity(), getMechanismConfiguration(), mechanismConfigurationSelector, getPrivateCredentials(), getPublicCredentials(), AggregateAttributes.aggregateOf(getRuntimeAttributes(), runtimeAttributes)); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } } final class RealmAssignedState extends UnassignedState { final MechanismRealmConfiguration mechanismRealmConfiguration; - RealmAssignedState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) { - super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials); + RealmAssignedState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) { + super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials, runtimeAttributes); this.mechanismRealmConfiguration = mechanismRealmConfiguration; } @@ -1818,7 +1862,7 @@ MechanismRealmConfiguration getMechanismRealmConfiguration() { @Override void addPublicCredential(final Credential credential) { - final RealmAssignedState newState = new RealmAssignedState(getSourceIdentity(), getMechanismConfiguration(), getMechanismRealmConfiguration(), getPrivateCredentials(), getPublicCredentials().withCredential(credential)); + final RealmAssignedState newState = new RealmAssignedState(getSourceIdentity(), getMechanismConfiguration(), getMechanismRealmConfiguration(), getPrivateCredentials(), getPublicCredentials().withCredential(credential), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } @@ -1826,19 +1870,27 @@ void addPublicCredential(final Credential credential) { @Override void addPrivateCredential(final Credential credential) { - final RealmAssignedState newState = new RealmAssignedState(getSourceIdentity(), getMechanismConfiguration(), getMechanismRealmConfiguration(), getPrivateCredentials().withCredential(credential), getPublicCredentials()); + final RealmAssignedState newState = new RealmAssignedState(getSourceIdentity(), getMechanismConfiguration(), getMechanismRealmConfiguration(), getPrivateCredentials().withCredential(credential), getPublicCredentials(), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } } + + @Override + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final RealmAssignedState newState = new RealmAssignedState(getSourceIdentity(), getMechanismConfiguration(), getMechanismRealmConfiguration(), getPrivateCredentials(), getPublicCredentials(), AggregateAttributes.aggregateOf(getRuntimeAttributes(), runtimeAttributes)); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } } final class InvalidNameState extends UnassignedState { final MechanismRealmConfiguration mechanismRealmConfiguration; - InvalidNameState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) { - super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials); + InvalidNameState(final SecurityIdentity capturedIdentity, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) { + super(capturedIdentity, mechanismConfiguration, privateCredentials, publicCredentials, runtimeAttributes); this.mechanismRealmConfiguration = mechanismRealmConfiguration; } @@ -1876,8 +1928,9 @@ final class NameAssignedState extends ActiveState { private final MechanismRealmConfiguration mechanismRealmConfiguration; private final IdentityCredentials privateCredentials; private final IdentityCredentials publicCredentials; + private final Attributes runtimeAttributes; - NameAssignedState(final SecurityIdentity capturedIdentity, final RealmInfo realmInfo, final RealmIdentity realmIdentity, final Principal authenticationPrincipal, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials) { + NameAssignedState(final SecurityIdentity capturedIdentity, final RealmInfo realmInfo, final RealmIdentity realmIdentity, final Principal authenticationPrincipal, final MechanismConfiguration mechanismConfiguration, final MechanismRealmConfiguration mechanismRealmConfiguration, final IdentityCredentials privateCredentials, final IdentityCredentials publicCredentials, final Attributes runtimeAttributes) { this.capturedIdentity = capturedIdentity; this.realmInfo = realmInfo; this.realmIdentity = realmIdentity; @@ -1886,6 +1939,7 @@ final class NameAssignedState extends ActiveState { this.mechanismRealmConfiguration = mechanismRealmConfiguration; this.privateCredentials = privateCredentials; this.publicCredentials = publicCredentials; + this.runtimeAttributes = runtimeAttributes; } @Override @@ -1949,7 +2003,8 @@ AuthorizedAuthenticationState doAuthorization(final boolean requireLoginPermissi final RealmInfo realmInfo = this.realmInfo; final Principal authenticationPrincipal = this.authenticationPrincipal; - final AuthorizationIdentity authorizationIdentity = realmIdentity.getAuthorizationIdentity(); + final AuthorizationIdentity authorizationIdentity = runtimeAttributes == Attributes.EMPTY ? realmIdentity.getAuthorizationIdentity() + : AuthorizationIdentity.basicIdentity(realmIdentity.getAuthorizationIdentity(), runtimeAttributes); final SecurityDomain domain = capturedIdentity.getSecurityDomain(); SecurityIdentity authorizedIdentity = Assert.assertNotNull(domain.transform(new SecurityIdentity(domain, authenticationPrincipal, realmInfo, authorizationIdentity, domain.getCategoryRoleMappers(), IdentityCredentials.NONE, IdentityCredentials.NONE))); @@ -1959,6 +2014,8 @@ AuthorizedAuthenticationState doAuthorization(final boolean requireLoginPermissi if (authorizationIdentity != null) { log.tracef("Authorizing against the following attributes: %s => %s", authorizationIdentity.getAttributes().keySet(), authorizationIdentity.getAttributes().values()); + log.tracef("Authorizing against the following runtime attributes: %s => %s", + authorizationIdentity.getRuntimeAttributes().keySet(), authorizationIdentity.getRuntimeAttributes().values()); } else { log.tracef("Authorizing against the following attributes: Cannot obtain the attributes. Authorization Identity is null."); } @@ -2046,7 +2103,7 @@ boolean isSamePrincipal(Principal principal) { @Override void addPublicCredential(final Credential credential) { - final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), getRealmInfo(), getRealmIdentity(), getAuthenticationPrincipal(), getMechanismConfiguration(), getMechanismRealmConfiguration(), privateCredentials, publicCredentials.withCredential(credential)); + final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), getRealmInfo(), getRealmIdentity(), getAuthenticationPrincipal(), getMechanismConfiguration(), getMechanismRealmConfiguration(), privateCredentials, publicCredentials.withCredential(credential), runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } @@ -2054,12 +2111,20 @@ void addPublicCredential(final Credential credential) { @Override void addPrivateCredential(final Credential credential) { - final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), getRealmInfo(), getRealmIdentity(), getAuthenticationPrincipal(), getMechanismConfiguration(), getMechanismRealmConfiguration(), privateCredentials.withCredential(credential), publicCredentials); + final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), getRealmInfo(), getRealmIdentity(), getAuthenticationPrincipal(), getMechanismConfiguration(), getMechanismRealmConfiguration(), privateCredentials.withCredential(credential), publicCredentials, runtimeAttributes); if (! stateRef.compareAndSet(this, newState)) { stateRef.get().addPublicCredential(credential); } } + @Override + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final NameAssignedState newState = new NameAssignedState(getSourceIdentity(), getRealmInfo(), getRealmIdentity(), getAuthenticationPrincipal(), getMechanismConfiguration(), getMechanismRealmConfiguration(), privateCredentials, publicCredentials, AggregateAttributes.aggregateOf(this.runtimeAttributes, runtimeAttributes)); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } + RealmInfo getRealmInfo() { return realmInfo; } @@ -2237,7 +2302,7 @@ AuthorizedState authorizeRunAs(final Principal authorizationId, final boolean au ElytronMessages.log.trace("RunAs authorization succeed - the same identity"); return this; } - final State state = assignName(authorizedIdentity, getMechanismConfiguration(), getMechanismRealmConfiguration(), authorizationId, null, IdentityCredentials.NONE, IdentityCredentials.NONE); + final State state = assignName(authorizedIdentity, getMechanismConfiguration(), getMechanismRealmConfiguration(), authorizationId, null, IdentityCredentials.NONE, IdentityCredentials.NONE, Attributes.EMPTY); if (!state.isNameAssigned()) { ElytronMessages.log.tracef("RunAs authorization failed - unable to assign identity name"); return null; @@ -2289,6 +2354,15 @@ void addPrivateCredential(final Credential credential) { stateRef.get().addPrivateCredential(credential); } } + + @Override + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final SecurityIdentity sourceIdentity = getSourceIdentity(); + final AuthorizedState newState = new AuthorizedState(sourceIdentity.withRuntimeAttributes(runtimeAttributes), getAuthenticationPrincipal(), getRealmInfo(), getMechanismConfiguration(), getMechanismRealmConfiguration()); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } } final class AuthorizedAuthenticationState extends AuthorizedState { @@ -2372,6 +2446,16 @@ void addPrivateCredential(final Credential credential) { stateRef.get().addPrivateCredential(credential); } } + + @Override + void addRuntimeAttributes(final Attributes runtimeAttributes) { + final SecurityIdentity sourceIdentity = getSourceIdentity(); + final AuthorizedAuthenticationState newState = new AuthorizedAuthenticationState(sourceIdentity.withRuntimeAttributes(runtimeAttributes), getAuthenticationPrincipal(), getRealmInfo(), getRealmIdentity(), getMechanismRealmConfiguration(), getMechanismConfiguration()); + if (! stateRef.compareAndSet(this, newState)) { + stateRef.get().addRuntimeAttributes(runtimeAttributes); + } + } + } static final class CompleteState extends State { diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateAttributes.java b/auth/server/base/src/main/java/org/wildfly/security/authz/AggregateAttributes.java similarity index 90% rename from auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateAttributes.java rename to auth/server/base/src/main/java/org/wildfly/security/authz/AggregateAttributes.java index deeed8474ba..f3229923dca 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateAttributes.java +++ b/auth/server/base/src/main/java/org/wildfly/security/authz/AggregateAttributes.java @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.wildfly.security.auth.realm; +package org.wildfly.security.authz; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import org.wildfly.security.authz.Attributes; -import org.wildfly.security.authz.SimpleAttributesEntry; /** * An implementation of {@link Attributes} aggregating multiple instances. @@ -30,7 +28,7 @@ * * @author Darran Lofthouse */ -class AggregateAttributes implements Attributes { +public class AggregateAttributes implements Attributes { private final Map aggregatedEntries; @@ -47,7 +45,7 @@ private AggregateAttributes(final Attributes[] aggrgatedAttributes) { this.aggregatedEntries = aggregatedEntries; } - static Attributes aggregateOf(Attributes... aggrgatedAttributes) { + public static Attributes aggregateOf(Attributes... aggrgatedAttributes) { return new AggregateAttributes(aggrgatedAttributes) .asReadOnly(); } diff --git a/auth/server/base/src/main/java/org/wildfly/security/authz/AuthorizationIdentity.java b/auth/server/base/src/main/java/org/wildfly/security/authz/AuthorizationIdentity.java index d578782af93..373f0020f4e 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/authz/AuthorizationIdentity.java +++ b/auth/server/base/src/main/java/org/wildfly/security/authz/AuthorizationIdentity.java @@ -38,6 +38,15 @@ default Attributes getAttributes() { return Attributes.EMPTY; } + /** + * Get the runtime attributes which pertain to this identity. By default, an empty attribute collection is returned. + * + * @return the runtime attributes (must not be {@code null}) + */ + default Attributes getRuntimeAttributes() { + return Attributes.EMPTY; + } + /** * The empty authorization identity. */ @@ -73,4 +82,45 @@ public String toString() { }; } + + /** + * Create a basic authorization identity implementation using the given attributes and runtime attributes. + * + * @param attributes the attributes + * @param runtimeAttributes the runtime attributes + * @return the authorization identity + */ + static AuthorizationIdentity basicIdentity(Supplier attributes, Supplier runtimeAttributes, final String string) { + return new AuthorizationIdentity() { + + public Attributes getAttributes() { + return attributes.get(); + } + + public Attributes getRuntimeAttributes() { + return runtimeAttributes.get(); + } + + @Override + public String toString() { + return string; + } + + }; + } + + /** + * Create a basic authorization identity implementation using the given authorization + * identity and runtime attributes. + * + * @param authorizationIdentity the authorization identity + * @param runtimeAttributes the identity runtime attributes + * @return the authorization identity + */ + static AuthorizationIdentity basicIdentity(AuthorizationIdentity authorizationIdentity, Attributes runtimeAttributes) { + Attributes attributes = authorizationIdentity.getAttributes(); + Attributes combinedRuntimeAttributes = AggregateAttributes.aggregateOf(authorizationIdentity.getRuntimeAttributes(), runtimeAttributes); + return basicIdentity(() -> attributes, () -> combinedRuntimeAttributes, "EMPTY"); + } + } diff --git a/auth/server/base/src/main/java/org/wildfly/security/authz/RoleDecoder.java b/auth/server/base/src/main/java/org/wildfly/security/authz/RoleDecoder.java index 32e3982fe33..0c5cf0a69b2 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/authz/RoleDecoder.java +++ b/auth/server/base/src/main/java/org/wildfly/security/authz/RoleDecoder.java @@ -18,6 +18,8 @@ package org.wildfly.security.authz; +import static org.wildfly.common.Assert.checkNotNullParam; + import java.util.HashSet; /** @@ -33,6 +35,12 @@ public interface RoleDecoder { */ String KEY_ROLES = "Roles"; + /** + * A key whose value is the string "Source-Address". This is where the IP address of a remote + * client may be found. + */ + String KEY_SOURCE_ADDRESS = "Source-Address"; + /** * Decode the role set from the given authorization identity. * @@ -63,4 +71,22 @@ static RoleDecoder simple(String attribute) { return entry.isEmpty() ? Roles.NONE : entry instanceof Attributes.SetEntry ? Roles.fromSet((Attributes.SetEntry) entry) : Roles.fromSet(new HashSet<>(entry)); }; } + + /** + * Create an aggregate role decoder. Each role decoder is applied in order and the returned value is + * a union of the roles returned by each decoder. + * + * @param decoders the role decoders to apply (must not be {@code null} or contain {@code null} elements) + * @return the aggregate role decoder (not {@code null}) + */ + static RoleDecoder aggregate(RoleDecoder... decoders) { + checkNotNullParam("decoders", decoders); + return identity -> { + Roles combinedRoles = Roles.NONE; + for (RoleDecoder decoder : decoders) { + combinedRoles = combinedRoles.or(decoder.decodeRoles(identity)); + } + return combinedRoles; + }; + } } diff --git a/auth/server/base/src/main/java/org/wildfly/security/authz/SourceAddressRoleDecoder.java b/auth/server/base/src/main/java/org/wildfly/security/authz/SourceAddressRoleDecoder.java new file mode 100644 index 00000000000..21ca9a0686f --- /dev/null +++ b/auth/server/base/src/main/java/org/wildfly/security/authz/SourceAddressRoleDecoder.java @@ -0,0 +1,90 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2019 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.authz; + +import static org.wildfly.common.Assert.checkNotNullParam; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A decoder to obtain role information using the source IP address runtime attribute from the identity. + * + * @author Farah Juma + */ +public class SourceAddressRoleDecoder implements RoleDecoder { + + private String sourceAddress; + private Pattern sourceAddressPattern; + private Roles roles; + + /** + * Construct a new instance. + * + * @param sourceAddress the source IP address to match (cannot be {@code null}) + * @param roles the roles to associate with the identity if the actual source IP address matches + * the given source IP address + */ + public SourceAddressRoleDecoder(String sourceAddress, Roles roles) { + checkNotNullParam("sourceAddress", sourceAddress); + checkNotNullParam("roles", roles); + this.sourceAddress = sourceAddress; + this.roles = roles; + } + + /** + * Construct a new instance. + * + * @param sourceAddressPattern the source IP address pattern to match (cannot be {@code null}) + * @param roles the roles to associate with the identity if the actual source IP address matches + * the given pattern + */ + public SourceAddressRoleDecoder(Pattern sourceAddressPattern, Roles roles) { + checkNotNullParam("sourceAddressPattern", sourceAddressPattern); + checkNotNullParam("roles", roles); + this.sourceAddressPattern = sourceAddressPattern; + this.roles = roles; + } + + /** + * Decode the role set using the source IP address runtime attribute from the given authorization identity. + * + * @param authorizationIdentity the authorization identity (not {@code null}) + * @return the role set (must not be {@code null}) + */ + public Roles decodeRoles(AuthorizationIdentity authorizationIdentity) { + Attributes runtimeAttributes = authorizationIdentity.getRuntimeAttributes(); + if (runtimeAttributes.containsKey(KEY_SOURCE_ADDRESS)) { + String actualSourceAddress = runtimeAttributes.getFirst(KEY_SOURCE_ADDRESS); + if (actualSourceAddress != null) { + if (sourceAddress != null) { + if (sourceAddress.equals(actualSourceAddress)) { + return roles; + } + } else { + final Matcher matcher = sourceAddressPattern.matcher(actualSourceAddress); + if (matcher.matches()) { + return roles; + } + } + } + } + return Roles.NONE; + } +} diff --git a/auth/server/base/src/test/java/org/wildfly/security/authz/RoleDecoderTest.java b/auth/server/base/src/test/java/org/wildfly/security/authz/RoleDecoderTest.java new file mode 100644 index 00000000000..88a738a0f67 --- /dev/null +++ b/auth/server/base/src/test/java/org/wildfly/security/authz/RoleDecoderTest.java @@ -0,0 +1,141 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2018 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.authz; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.wildfly.security.authz.RoleDecoder.KEY_SOURCE_ADDRESS; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.regex.Pattern; + +import org.junit.Test; + +/** + * Tests for role decoders. + * + * @author Farah Juma + */ +public class RoleDecoderTest { + + @Test + public void testSourceAddressRoleDecoderExactMatch() { + Roles roles = getRoles("admin", "user"); + String sourceAddress = "10.12.14.16"; + SourceAddressRoleDecoder roleDecoder = new SourceAddressRoleDecoder(sourceAddress, roles); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.16.18"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("0:0:0:0:ffff:0:192.0.2.128"))); + + // IPv6 + sourceAddress = "2001:db8:85a3:0:0:8a2e:370:7334"; + roleDecoder = new SourceAddressRoleDecoder(sourceAddress, roles); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("0:0:0:0:ffff:0:192.0.2.128"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14.16"))); + + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity(null))); + } + + @Test + public void testSourceAddressRoleDecoderRegex() { + Roles roles = getRoles("admin", "user"); + Pattern sourceAddressPattern = Pattern.compile("10\\.12\\.14\\.\\d+$"); + String sourceAddress = "10.12.14.16"; + SourceAddressRoleDecoder roleDecoder = new SourceAddressRoleDecoder(sourceAddressPattern, roles); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress))); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14.18"))); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14.1"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14."))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("12.12.16.18"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14.18.20"))); + + // IPv6 + sourceAddressPattern = Pattern.compile("2001\\:db8\\:85a3\\:0\\:0\\:8a2e\\:370\\:\\d+$"); + sourceAddress = "2001:db8:85a3:0:0:8a2e:370:7334"; + roleDecoder = new SourceAddressRoleDecoder(sourceAddressPattern, roles); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress))); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity("2001:db8:85a3:0:0:8a2e:370:7335"))); + assertEquals(roles, roleDecoder.decodeRoles(getAuthorizationIdentity("2001:db8:85a3:0:0:8a2e:370:7000"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("2001:db8:85a3:0:0:8a2e:370:"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("2222:db8:85a3:0:0:8a2e:370:7335"))); + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity("2001:db8:85a3:0:0:8a2e:370:7335:0"))); + + assertEquals(Roles.NONE, roleDecoder.decodeRoles(getAuthorizationIdentity(null))); + } + + @Test + public void testAggregateRoleDecoder() { + Roles roles1 = getRoles("admin", "user"); + String sourceAddress1 = "10.12.14.16"; + SourceAddressRoleDecoder roleDecoder1 = new SourceAddressRoleDecoder(sourceAddress1, roles1); + + Roles roles2 = getRoles("employee"); + String sourceAddress2 = "10.12.14.18"; + SourceAddressRoleDecoder roleDecoder2 = new SourceAddressRoleDecoder(sourceAddress2, roles2); + + Roles roles3 = getRoles("internal"); + Pattern pattern = Pattern.compile("10\\.12\\.14\\.\\d+$"); + SourceAddressRoleDecoder roleDecoder3 = new SourceAddressRoleDecoder(pattern, roles3); + + RoleDecoder aggregateRoleDecoder = RoleDecoder.aggregate(roleDecoder1, roleDecoder2, roleDecoder3); + Roles decodedRoles = aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress1)); + assertTrue(decodedRoles.contains("admin")); + assertTrue(decodedRoles.contains("user")); + assertFalse(decodedRoles.contains("employee")); + assertTrue(decodedRoles.contains("internal")); + + decodedRoles = aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity(sourceAddress2)); + assertFalse(decodedRoles.contains("admin")); + assertFalse(decodedRoles.contains("user")); + assertTrue(decodedRoles.contains("employee")); + assertTrue(decodedRoles.contains("internal")); + + decodedRoles = aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity("10.12.14.20")); + assertFalse(decodedRoles.contains("admin")); + assertFalse(decodedRoles.contains("user")); + assertFalse(decodedRoles.contains("employee")); + assertTrue(decodedRoles.contains("internal")); + + decodedRoles = aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity("10.10.14.20")); + assertFalse(decodedRoles.contains("admin")); + assertFalse(decodedRoles.contains("user")); + assertFalse(decodedRoles.contains("employee")); + assertFalse(decodedRoles.contains("internal")); + + assertEquals(Roles.NONE, aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity("2001:db8:85a3:0:0:8a2e:370:"))); + assertEquals(Roles.NONE, aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity("12.12.16.18"))); + assertEquals(Roles.NONE, aggregateRoleDecoder.decodeRoles(getAuthorizationIdentity(null))); + } + + private Roles getRoles(String... roles) { + return Roles.fromSet(new HashSet<>(Arrays.asList(roles))); + } + + private AuthorizationIdentity getAuthorizationIdentity(String sourceAddress) { + if (sourceAddress == null) { + return AuthorizationIdentity.basicIdentity(Attributes.EMPTY); + } else { + MapAttributes runtimeAttributes = new MapAttributes(); + runtimeAttributes.addFirst(KEY_SOURCE_ADDRESS, sourceAddress); + return AuthorizationIdentity.basicIdentity(AuthorizationIdentity.EMPTY, runtimeAttributes); + } + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/auth/server/SourceAddressRuntimeAttributesTest.java b/tests/base/src/test/java/org/wildfly/security/auth/server/SourceAddressRuntimeAttributesTest.java new file mode 100644 index 00000000000..54de7a01195 --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/auth/server/SourceAddressRuntimeAttributesTest.java @@ -0,0 +1,173 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2019 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.auth.server; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.wildfly.security.authz.RoleDecoder.KEY_SOURCE_ADDRESS; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; + +import org.junit.Test; +import org.wildfly.security.auth.permission.LoginPermission; +import org.wildfly.security.auth.principal.NamePrincipal; +import org.wildfly.security.auth.realm.FileSystemSecurityRealm; +import org.wildfly.security.authz.Attributes; +import org.wildfly.security.authz.MapAttributes; +import org.wildfly.security.authz.RoleDecoder; +import org.wildfly.security.authz.Roles; +import org.wildfly.security.authz.SourceAddressRoleDecoder; +import org.wildfly.security.permission.PermissionVerifier; + +/** + * Tests for role decoding with the source IP address runtime attribute. + * + * @author Farah Juma + */ +public class SourceAddressRuntimeAttributesTest { + + @Test + public void testRoleDecodingWithSourceAddressMatch() throws Exception { + FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm(); + String sourceAddress = "10.12.14.16"; + SourceAddressRoleDecoder roleDecoder = new SourceAddressRoleDecoder(sourceAddress, Roles.of("Admin")); + SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build() + .setPermissionMapper((permissionMappable, roles) -> roles.contains("Admin") ? LoginPermission.getInstance() : PermissionVerifier.NONE) + .setRoleDecoder(roleDecoder) + .build(); + + ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext(); + sac.setAuthenticationName("bob"); + assertFalse(sac.authorize()); // based on the security realm alone, bob does not have "Admin" role + + // make use of the runtime source IP address attribute + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes(sourceAddress)); + sac.setAuthenticationName("bob"); + assertTrue(sac.authorize()); + + // runtime source IP address attribute not specified + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes(null)); + sac.setAuthenticationName("bob"); + assertFalse(sac.authorize()); + + sac = securityDomain.createNewAuthenticationContext(); + sac.setAuthenticationName("alice"); + assertTrue(sac.authorize()); // based on the security realm alone, alice already has "Admin" role + + // make use of the runtime source IP address attribute, make sure alice still has "Admin" role + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes(sourceAddress)); + sac.setAuthenticationName("alice"); + assertTrue(sac.authorize()); + + // make use of the runtime source IP address attribute, make sure alice still has "Admin" role + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes(null)); + sac.setAuthenticationName("alice"); + assertTrue(sac.authorize()); + } + + @Test + public void testRoleDecodingWithSourceAddressMismatch() throws Exception { + FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm(); + String sourceAddress = "10.12.14.16"; + SourceAddressRoleDecoder roleDecoder = new SourceAddressRoleDecoder(sourceAddress, Roles.of("Admin")); + SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build() + .setPermissionMapper((permissionMappable, roles) -> roles.contains("Admin") ? LoginPermission.getInstance() : PermissionVerifier.NONE) + .setRoleDecoder(roleDecoder) + .build(); + + ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext(); + sac.setAuthenticationName("bob"); + assertFalse(sac.authorize()); // based on the security realm alone, bob does not have "Admin" role + + // make use of the runtime source IP address attribute + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes("10.12.16.16")); + sac.setAuthenticationName("bob"); + assertFalse(sac.authorize()); + + sac = securityDomain.createNewAuthenticationContext(); + sac.setAuthenticationName("alice"); + assertTrue(sac.authorize()); // based on the security realm alone, alice already has "Admin" role + + // make use of the runtime source IP address attribute, make sure alice still has "Admin" role + sac = securityDomain.createNewAuthenticationContext(); + sac.addRuntimeAttributes(createRuntimeAttributes("10.12.16.16")); + sac.setAuthenticationName("alice"); + assertTrue(sac.authorize()); + } + + private FileSystemSecurityRealm createSecurityRealm() throws Exception { + FileSystemSecurityRealm realm = new FileSystemSecurityRealm(getRootPath(true)); + addUser(realm, "alice", "Admin"); + addUser(realm, "bob", "Employee"); + return realm; + } + + private Path getRootPath(boolean deleteIfExists) throws Exception { + Path rootPath = Paths.get(getClass().getResource(File.separator).toURI()) + .resolve("filesystem-realm"); + + if (rootPath.toFile().exists() && !deleteIfExists) { + return rootPath; + } + + return Files.walkFileTree(Files.createDirectories(rootPath), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + } + + private void addUser(ModifiableSecurityRealm realm, String userName, String roles) throws RealmUnavailableException { + MapAttributes attributes = new MapAttributes(); + attributes.addAll(RoleDecoder.KEY_ROLES, Collections.singletonList(roles)); + + ModifiableRealmIdentity realmIdentity = realm.getRealmIdentityForUpdate(new NamePrincipal(userName)); + realmIdentity.create(); + realmIdentity.setAttributes(attributes); + realmIdentity.dispose(); + } + + private Attributes createRuntimeAttributes(String actualSourceAddress) { + MapAttributes runtimeAttributes = new MapAttributes(); + if (actualSourceAddress != null) { + runtimeAttributes.addFirst(KEY_SOURCE_ADDRESS, actualSourceAddress); + } + return runtimeAttributes; + } + +}