Showing with 629 additions and 48 deletions.
  1. +7 −1 ...nts/filters/client-auth/src/main/java/org/openrepose/filters/clientauth/common/Configurables.java
  2. +23 −6 ...uth/src/main/java/org/openrepose/filters/clientauth/openstack/OpenStackAuthenticationHandler.java
  3. +12 −2 .../main/java/org/openrepose/filters/clientauth/openstack/OpenStackAuthenticationHandlerFactory.java
  4. +14 −1 ...s/client-auth/src/main/resources/META-INF/schema/config/openstack-ids-auth/openstack-ids-auth.xsd
  5. +73 −2 ...src/test/java/org/openrepose/filters/clientauth/openstack/OpenStackAuthenticationHandlerTest.java
  6. +12 −0 ...gregator/components/filters/keystone-v2/src/main/resources/META-INF/schema/config/keystone-v2.xsd
  7. +20 −14 ...nents/filters/keystone-v2/src/main/scala/org/openrepose/filters/keystonev2/KeystoneV2Filter.scala
  8. +39 −7 ...s/filters/keystone-v2/src/test/scala/org/openrepose/filters/keystonev2/KeystoneV2FilterTest.scala
  9. +18 −2 ...filters/openstack-identity-v3/src/main/resources/META-INF/schema/config/openstack-identity-v3.xsd
  10. +10 −13 ...ity-v3/src/main/scala/org/openrepose/filters/openstackidentityv3/OpenStackIdentityV3Handler.scala
  11. +9 −0 ...v3/src/test/scala/org/openrepose/filters/openstackidentityv3/OpenStackIdentityV3HandlerTest.scala
  12. +18 −0 ...rc/test/configs/features/filters/clientauthn/removetenant/striptenantprefix/client-auth-n.cfg.xml
  13. +14 −0 ...nfigs/features/filters/identityv3/projectidinuri/stripprojectprefix/openstack-identity-v3.cfg.xml
  14. +19 −0 ...t/src/test/configs/features/filters/keystonev2/removetenant/striptenantprefix/keystone-v2.cfg.xml
  15. +139 −0 ...l-test/src/test/groovy/features/filters/clientauthn/tenantvalidation/StripTenantPrefixTest.groovy
  16. +100 −0 ...l-test/src/test/groovy/features/filters/identityv3/projectidinuri/StripProjectIdPrefixTest.groovy
  17. +102 −0 ...al-test/src/test/groovy/features/filters/keystonev2/tenantvalidation/StripTenantPrefixTest.groovy
@@ -44,13 +44,14 @@ public class Configurables {
private final boolean requestGroups;
private final EndpointsConfiguration endpointsConfiguration;
private final Set<String> ignoreTenantRoles;
private final Set<String> tenantPrefixes;
private final boolean sendAllTenantIds;
private final boolean sendTenantIdQuality;

public Configurables(boolean delegable, double delegableQuality, String authServiceUri, KeyedRegexExtractor<String> keyedRegexExtractor,
boolean tenanted, long groupCacheTtl, long tokenCacheTtl, long usrCacheTtl, int cacheOffset, boolean requestGroups,
EndpointsConfiguration endpointsConfiguration, List<String> serviceAdminRoles, List<String> ignoreTenantRoles,
boolean sendAllTenantIds, boolean sendTenantIdQuality) {
Set<String> tenantPrefixes, boolean sendAllTenantIds, boolean sendTenantIdQuality) {
HashSet<String> noTenantRoles = new HashSet<>(serviceAdminRoles);
noTenantRoles.addAll(ignoreTenantRoles);

@@ -66,6 +67,7 @@ public Configurables(boolean delegable, double delegableQuality, String authServ
this.requestGroups = requestGroups;
this.endpointsConfiguration = endpointsConfiguration;
this.ignoreTenantRoles = noTenantRoles;
this.tenantPrefixes = tenantPrefixes;
this.sendAllTenantIds = sendAllTenantIds;
this.sendTenantIdQuality = sendTenantIdQuality;
}
@@ -118,6 +120,10 @@ public Set<String> getIgnoreTenantRoles() {
return ignoreTenantRoles;
}

public Set<String> getTenantPrefixes() {
return tenantPrefixes;
}

public boolean sendingAllTenantIds() {
return sendAllTenantIds;
}
@@ -27,7 +27,6 @@
import org.openrepose.common.auth.openstack.AuthenticationService;
import org.openrepose.common.auth.openstack.AuthenticationServiceClient;
import org.openrepose.common.auth.openstack.OpenStackToken;
import org.openrepose.commons.utils.StringUtilities;
import org.openrepose.commons.utils.http.CommonHttpHeader;
import org.openrepose.commons.utils.regex.ExtractorResult;
import org.openrepose.commons.utils.servlet.http.ReadableHttpServletResponse;
@@ -41,6 +40,7 @@
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@@ -56,6 +56,7 @@ public class OpenStackAuthenticationHandler extends AuthenticationHandler {
private final String wwwAuthHeaderContents;
private final AuthenticationService authenticationService;
private final Set<String> ignoreTenantRoles;
private final Set<String> tenantPrefixes;
private boolean delegatingMode;

public OpenStackAuthenticationHandler(
@@ -71,6 +72,7 @@ public OpenStackAuthenticationHandler(
this.authenticationService = serviceClient;
this.wwwAuthHeaderContents = WWW_AUTH_PREFIX + cfg.getAuthServiceUri();
this.ignoreTenantRoles = cfg.getIgnoreTenantRoles();
this.tenantPrefixes = cfg.getTenantPrefixes();
this.delegatingMode = cfg.isDelegable();
}

@@ -94,14 +96,29 @@ private AuthToken validateTenant(AuthenticateResponse resp, String tenantID) {
authToken = new OpenStackToken(resp);
}

if (authToken != null && !hasIgnoreTenantRole(authToken) && !StringUtilities.nullSafeEquals(authToken.getTenantId(), tenantID)) {
// tenant ID from token did not match URI.
if (authToken != null && !hasIgnoreTenantRole(authToken)) {
Set<String> tokenTenantIds = new HashSet<>();

if (authToken.getTenantId() != null) {
tokenTenantIds.add(authToken.getTenantId());
}

if (resp.getUser() != null && resp.getUser().getRoles() != null) {
for (Role role : resp.getUser().getRoles().getRole()) {
if (tenantID.equals(role.getTenantId())) {
//we have the real tenantID
authToken.setMatchingTenantId(tenantID);
if (role.getTenantId() != null) {
tokenTenantIds.add(role.getTenantId());
}
}
}

for (String ttid : tokenTenantIds) {
if (ttid.equals(tenantID)) {
authToken.setMatchingTenantId(ttid);
return authToken;
}
for (String prefix : tenantPrefixes) {
if (ttid.startsWith(prefix) && ttid.substring(prefix.length()).equals(tenantID)) {
authToken.setMatchingTenantId(ttid);
return authToken;
}
}
@@ -33,8 +33,7 @@
import org.openrepose.filters.clientauth.openstack.config.OpenstackAuth;
import org.openrepose.filters.clientauth.openstack.config.ServiceAdminRoles;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

@Deprecated
public final class OpenStackAuthenticationHandlerFactory {
@@ -83,6 +82,7 @@ public static AuthenticationHandler newInstance(ClientAuthConfig config, KeyedRe
endpointsConfiguration,
getServiceAdminRoles(authConfig.getServiceAdminRoles()),
getIgnoreTenantRoles(authConfig.getIgnoreTenantRoles()),
getTenantPrefixes(authConfig.getStripTokenTenantPrefixes()),
authConfig.isSendAllTenantIds(),
authConfig.isSendTenantIdQuality());

@@ -100,4 +100,14 @@ private static List<String> getIgnoreTenantRoles(IgnoreTenantRoles roles) {
return roles.getRole();
}
}

private static Set<String> getTenantPrefixes(String stripTokenTenantPrefixes) {
if (stripTokenTenantPrefixes != null) {
Set<String> prefixSet = new HashSet<>();
prefixSet.addAll(Arrays.asList(stripTokenTenantPrefixes.split("/")));
return prefixSet;
}

return new HashSet<>();
}
}
@@ -20,7 +20,8 @@
-->


<xs:schema xmlns:os-ids-auth="http://docs.openrepose.org/repose/client-auth/os-ids-auth/v1.0" xmlns:html="http://www.w3.org/1999/xhtml"
<xs:schema xmlns:os-ids-auth="http://docs.openrepose.org/repose/client-auth/os-ids-auth/v1.0"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
xmlns:xerces="http://xerces.apache.org"
xmlns:saxon="http://saxon.sf.net/"
@@ -103,6 +104,18 @@
</xs:annotation>
</xs:attribute>

<xs:attribute name="strip-token-tenant-prefixes" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>
<html:p>
A '/' delimited list of prefixes to attempt to strip from the tenant id in the token
response from the identity service. The post-strip tenant id is only used in the tenant
validation check.
</html:p>
</xs:documentation>
</xs:annotation>
</xs:attribute>

<xs:attribute name="send-all-tenant-ids" type="xs:boolean" use="optional" default="false">
<xs:annotation>
<xs:documentation>
@@ -125,6 +125,10 @@ public void beforeAny() {
final ServiceAdminRoles serviceAdminRoles = new ServiceAdminRoles();
serviceAdminRoles.getRole().add("12345");

final Set<String> tenantPrefixes = new HashSet<>();
tenantPrefixes.add("foo:");
tenantPrefixes.add("bar:");

endpointsConfiguration = new EndpointsConfiguration("json", AUTH_USER_CACHE_TTL, new Integer("1000"));
Configurables configurables = new Configurables(
delegable(),
@@ -139,7 +143,9 @@ public void beforeAny() {
requestGroups(),
endpointsConfiguration,
serviceAdminRoles.getRole(),
new ArrayList<String>(), false, false);
new ArrayList<String>(),
tenantPrefixes,
false, false);
handler = new OpenStackAuthenticationHandler(configurables, authService, null, null, null, null, new UriMatcher(whiteListRegexPatterns));


@@ -337,6 +343,69 @@ public void tenantIdFromTokenMatchesAnIdFromRoles() throws Exception {
assertThat(director.getFilterAction(), equalTo(FilterAction.PASS));
}

@Test
public void tenantIdFromTokenMatchesUriWithoutPrefix() throws Exception {
when(request.getRequestURI()).thenReturn("/start/104772/resource");
userForAuthenticateResponse.setRoles(defaultRoleList());
Token token = new Token();
token.setId("tokenId");
token.setExpires(dataTypeFactory.newXMLGregorianCalendar((GregorianCalendar) expires));
TenantForAuthenticateResponse tenant = new TenantForAuthenticateResponse();
tenant.setId("foo:104772");
tenant.setName("tenantName");
token.setTenant(tenant);
authResponse.setToken(token);
authResponse.setUser(userForAuthenticateResponse);
final AuthToken user = new OpenStackToken(authResponse);
when(authService.validateToken(anyString(), anyString(), anyString())).thenReturn(authResponse);

FilterDirector director = handler.handleRequest(request, response);

Set expectedSet = new LinkedHashSet();
expectedSet.add("foo:104772");
assertThat(director.requestHeaderManager().headersToAdd().get(HeaderName.wrap("x-tenant-id")), equalTo(expectedSet));
assertThat(director.getFilterAction(), equalTo(FilterAction.PASS));
}

@Test
public void tenantIdFromRolesMatchesUriWithoutPrefix() throws Exception {
when(request.getRequestURI()).thenReturn("/start/104772/resource");
Role role1 = new Role();
role1.setName("123456");
role1.setId("123456");
role1.setTenantId("123456");
role1.setDescription("Derp description");
Role role2 = new Role();
role2.setName("foo:104772");
role2.setId("foo:104772");
role2.setTenantId("foo:104772");
role2.setDescription("Derp description");
RoleList roleList = new RoleList();
roleList.getRole().add(role1);
roleList.getRole().add(role2);
userForAuthenticateResponse.setRoles(roleList);
//build a token to go along with the auth response
Token token = new Token();
token.setId("tokenId");
token.setExpires(dataTypeFactory.newXMLGregorianCalendar((GregorianCalendar) expires));
TenantForAuthenticateResponse tenant = new TenantForAuthenticateResponse();
tenant.setId("123456");
tenant.setName("tenantName");
token.setTenant(tenant);
authResponse.setToken(token);
authResponse.setUser(userForAuthenticateResponse);

final AuthToken user = new OpenStackToken(authResponse);
when(authService.validateToken(anyString(), anyString(), anyString())).thenReturn(authResponse);

FilterDirector director = handler.handleRequest(request, response);

Set expectedSet = new LinkedHashSet();
expectedSet.add("foo:104772");
assertThat(director.requestHeaderManager().headersToAdd().get(HeaderName.wrap("x-tenant-id")), equalTo(expectedSet));
assertThat(director.getFilterAction(), equalTo(FilterAction.PASS));
}

@Test
public void tenantIDDoesNotMatch() throws Exception {
when(request.getRequestURI()).thenReturn("/start/104772/resource");
@@ -551,7 +620,9 @@ public void standUp() throws Exception {
requestGroups(),
endpointsConfiguration,
serviceAdminRoles.getRole(),
new ArrayList<String>(), true, false);
new ArrayList<String>(),
new HashSet<String>(),
true, false);
handler2 = new OpenStackAuthenticationHandler(configurables, authService, null, null, null, null, new UriMatcher(whiteListRegexPatterns));

}
@@ -171,6 +171,18 @@
</xs:annotation>
</xs:element>
</xs:sequence>

<xs:attribute name="strip-token-tenant-prefixes" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>
<html:p>
A '/' delimited list of prefixes to attempt to strip from the tenant id in the token
response from the identity service. The post-strip tenant id is only used in the tenant
validation check.
</html:p>
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>

<xs:complexType name="RolesList">
@@ -37,7 +37,7 @@ import org.openrepose.commons.utils.servlet.http.MutableHttpServletRequest
import org.openrepose.core.filter.FilterConfigHelper
import org.openrepose.core.services.config.ConfigurationService
import org.openrepose.core.services.datastore.{Datastore, DatastoreService}
import org.openrepose.core.services.serviceclient.akka.{AkkaServiceClientFactory, AkkaServiceClient, AkkaServiceClientException}
import org.openrepose.core.services.serviceclient.akka.{AkkaServiceClient, AkkaServiceClientException, AkkaServiceClientFactory}
import org.openrepose.core.systemmodel.SystemModel
import org.openrepose.filters.keystonev2.KeystoneRequestHandler._
import org.openrepose.filters.keystonev2.config._
@@ -56,14 +56,15 @@ class KeystoneV2Filter @Inject()(configurationService: ConfigurationService,

import KeystoneV2Filter._

private var configurationFile: String = DEFAULT_CONFIG
private var sendTraceHeader = true

private val datastore: Datastore = datastoreService.getDefaultDatastore //Which happens to be the local datastore
// The local datastore
private val datastore: Datastore = datastoreService.getDefaultDatastore

var keystoneV2Config: KeystoneV2Config = _
var akkaServiceClient: AkkaServiceClient = _

private var configurationFile: String = DEFAULT_CONFIG
private var sendTraceHeader = true

override def init(filterConfig: FilterConfig): Unit = {
configurationFile = new FilterConfigHelper(filterConfig).getFilterConfig(DEFAULT_CONFIG)
logger.info(s"Initializing Keystone V2 Filter using config $configurationFile")
@@ -91,13 +92,13 @@ class KeystoneV2Filter @Inject()(configurationService: ConfigurationService,

override def doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, chain: FilterChain): Unit = {
/**
* STATIC REFERENCE TO CONFIG
*/
* STATIC REFERENCE TO CONFIG
*/
val config = keystoneV2Config

/**
* DECLARE COMMON VALUES
*/
* DECLARE COMMON VALUES
*/
lazy val request = MutableHttpServletRequest.wrap(servletRequest.asInstanceOf[HttpServletRequest])
// Not using the mutable wrapper because it doesn't work properly at the moment, and
// we don't need to modify the response from further down the chain
@@ -108,8 +109,8 @@ class KeystoneV2Filter @Inject()(configurationService: ConfigurationService,
Option(config.getIdentityService.getPassword).isEmpty

/**
* BEGIN PROCESSING
*/
* BEGIN PROCESSING
*/
if (!isInitialized) {
logger.error("Keystone v2 filter has not yet initialized")
response.sendError(SC_INTERNAL_SERVER_ERROR)
@@ -207,8 +208,8 @@ class KeystoneV2Filter @Inject()(configurationService: ConfigurationService,
}

/**
* DEFINING FUNCTIONS IN SCOPE
*/
* DEFINING FUNCTIONS IN SCOPE
*/
def tenantFromUri: Try[Option[String]] = {
Try(
Option(config.getTenantHandling.getValidateTenant) flatMap { validateTenantConfig =>
@@ -311,7 +312,12 @@ class KeystoneV2Filter @Inject()(configurationService: ConfigurationService,
tenantFromUri map {
_ flatMap { uriTenant =>
val tokenTenants = validToken.defaultTenantId.toSet ++ validToken.tenantIds
tokenTenants.find(uriTenant.equals)
val prefixes = Option(config.getTenantHandling.getValidateTenant.getStripTokenTenantPrefixes).map(_.split('/')).getOrElse(Array.empty[String])
tokenTenants find { tokenTenant =>
tokenTenant.equals(uriTenant) || prefixes.exists(prefix =>
tokenTenant.startsWith(prefix) && tokenTenant.substring(prefix.length).equals(uriTenant)
)
}
}
}
} else {