Skip to content

Commit

Permalink
apiml/GH2062/add-x509-auth-source (#2185)
Browse files Browse the repository at this point in the history
* feat(authentication): introduce x509 authentication source

Introduce the object and basic service for a client certificate as source of authentication

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* refactor: use dedicated origin of the authentication source instead of QueryResponse.Source

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* refactor: improve code coverage

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* refactor: resolve licence issue

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: Add implementation of AuthSourceService interface to process client certificate authentication

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: add JUnits

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: return BAD REQUEST (400) when X509 certificate which cannot be used for client authentication is used in authentication scheme

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: fix error in acceptance test (ZosmfSchemeTest)

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: fix Sonar issues

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* feat: define X509 authentication source service as bean in configuration

Signed-off-by: Yelyzaveta Chebanova <[email protected]>

* rerun

Signed-off-by: achmelo <[email protected]>

Co-authored-by: achmelo <[email protected]>
  • Loading branch information
yelyzavetachebanova and achmelo authored Mar 15, 2022
1 parent 576fd72 commit efd53a8
Show file tree
Hide file tree
Showing 21 changed files with 1,470 additions and 403 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import org.zowe.apiml.gateway.security.service.schema.AuthenticationCommand;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService;
import org.zowe.apiml.security.common.error.InvalidCertificateException;
import org.zowe.apiml.security.common.token.TokenExpireException;

import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*;

Expand Down Expand Up @@ -54,6 +56,7 @@ public Object run() {
final RequestContext context = RequestContext.getCurrentContext();

boolean rejected = false;
boolean badRequest = false;
AuthenticationCommand cmd = null;

final String serviceId = (String) context.get(SERVICE_ID_KEY);
Expand All @@ -67,6 +70,9 @@ public Object run() {
}
} catch (TokenExpireException tee) {
cmd = null;
} catch (InvalidCertificateException ice) {
rejected = true;
badRequest = true;
} catch (AuthenticationException ae) {
rejected = true;
} catch (Exception e) {
Expand All @@ -77,7 +83,7 @@ public Object run() {

if (rejected) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(SC_UNAUTHORIZED);
context.setResponseStatusCode(badRequest ? SC_BAD_REQUEST : SC_UNAUTHORIZED);
} else if (cmd != null) {
try {
// Update ZUUL context by authentication schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
*/
package org.zowe.apiml.gateway.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.zowe.apiml.gateway.security.login.Providers;
import org.zowe.apiml.gateway.security.login.x509.X509AbstractMapper;
import org.zowe.apiml.gateway.security.service.schema.source.X509AuthSourceService;
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
import org.zowe.apiml.passticket.PassTicketService;
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
Expand Down Expand Up @@ -57,4 +61,13 @@ public Providers loginProviders(
return new Providers(discoveryClient, authConfigurationProperties, compoundAuthProvider, zosmfService);
}

/**
* Implementation of AuthSourceService interface which uses client certificate as an authentication source.
* This bean performs the mapping between common name from the client certificate and the mainframe user ID.
*/
@Bean
@Qualifier("x509MFAuthSourceService")
public X509AuthSourceService getX509MFAuthSourceService(@Autowired X509AbstractMapper mapper) {
return new X509AuthSourceService(mapper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,32 @@
* Interface defines general source of authentication. Keeps original source of authentication (JWT token, client certificate etc.).
*/
public interface AuthSource extends Serializable {

/**
* Returns the original (unparsed) source of the authentication - JWT token, client certificate etc.
* @return original source of authentication
*/
Object getRawSource();

/**
* Return type of the authentication source
* @return {@link AuthSourceType}
*/
AuthSourceType getType();

/**
* Interface defines general parsed form of the authentication source.
*/
interface Parsed {
String getUserId();
Date getCreation();
Date getExpiration();
Origin getOrigin();
}

/**
* Defines supported origins of the authentication
*/
enum Origin {

// JWT token is generated by Zowe (including ie. LTPA token from z/OSMF)
Expand Down Expand Up @@ -55,4 +72,12 @@ public static Origin valueByIssuer(String issuer) {
throw new TokenNotValidException("Unknown authentication source type : " + issuer);
}
}

/**
* Defines supported type of authentication source - JWT token and client certificate
*/
enum AuthSourceType {
JWT,
CLIENT_CERT
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.gateway.security.service.schema.source;

import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource.AuthSourceType;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource.Parsed;

/**
* Main implementation of AuthSourceService, supports two types of authentication source - JWT token and client certificate.
* <p>
* Service keeps a map of the specific implementations of {@link AuthSourceService} which are responsible to perform operations defined by an interface
* for a particular authentication source. {@link JwtAuthSourceService} is responsible for processing of the authentication source based on JWT token;
* @Qualifier("x509MFAuthSourceService") {@link X509AuthSourceService} is responsible for processing of the authentication source based on client certificate.
* The key for the map is {@link AuthSourceType}.
*/
@Slf4j
@Service
@Primary
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DefaultAuthSourceService implements AuthSourceService {
private final Map<AuthSourceType, AuthSourceService> map = new EnumMap<>(AuthSourceType.class);

/**
* Build the map of the specific implementations of {@link AuthSourceService} for processing of different type of authentications
* @param jwtAuthSourceService {@link JwtAuthSourceService} service which process authentication source of type JWT
* @param x509AuthSourceService {@link X509AuthSourceService} service which process authentication source of type client certificate
*/
public DefaultAuthSourceService(@Autowired JwtAuthSourceService jwtAuthSourceService, @Autowired @Qualifier("x509MFAuthSourceService") X509AuthSourceService x509AuthSourceService) {
map.put(AuthSourceType.JWT, jwtAuthSourceService);
map.put(AuthSourceType.CLIENT_CERT, x509AuthSourceService);
}

/**
* Core method of the interface. Gets source of authentication from request.
* <p>
* In case if more than one source is present in request the precedence is the following:
* 1) JWT token
* 2) Client certificate
* <p>
* @return Optional<AuthSource> which hold original source of authentication (JWT token, client certificate etc.)
* or Optional.empty() when no authentication source found.
*/
@Override
public Optional<AuthSource> getAuthSourceFromRequest() {
AuthSourceService service = getService(AuthSourceType.JWT);
Optional<AuthSource> authSource = service.getAuthSourceFromRequest();
if (!authSource.isPresent()) {
service = getService(AuthSourceType.CLIENT_CERT);
authSource = service.getAuthSourceFromRequest();
}
return authSource;
}

/**
* Delegates the validation of the authentication source to a corresponding service.
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token, client certificate etc.)
* @return true is authentication source is valid, false otherwise
*/
@Override
public boolean isValid(AuthSource authSource) {
AuthSourceService service = getService(authSource);
return service != null && service.isValid(authSource);
}

/**
* Delegates the parsing of the authentication source to a corresponding service.
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token, client certificate etc.)
* @return authentication source in parsed form
*/
@Override
public Parsed parse(AuthSource authSource) {
AuthSourceService service = getService(authSource);
return service != null ? service.parse(authSource) : null;
}

/**
* Delegates the generation of the LTPA token based on the authentication source to a corresponding service.
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token, client certificate etc.)
* @return LPTA token
*/
@Override
public String getLtpaToken(AuthSource authSource) {
AuthSourceService service = getService(authSource);
return service != null ? service.getLtpaToken(authSource) : null;
}

/**
* Choose a service to process authentication source from the map of available services.
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token, client certificate etc.)
* @return implementation of {@link AuthSourceService} or null if service not found
*/
private AuthSourceService getService(AuthSource authSource) {
return authSource != null ? getService(authSource.getType()) : null;
}

/**
* Choose a service to process authentication source of specific type from the map of available services.
* @param authSourceType {@link AuthSourceType} type of the authentication source
* @return implementation of {@link AuthSourceService} or null if service not found
*/
private AuthSourceService getService(AuthSourceType authSourceType) {
final AuthSourceService service = map.get(authSourceType);
if (service == null) {
throw new IllegalArgumentException("Unknown authentication source");
}
return service;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class JwtAuthSource implements AuthSource {
public static final AuthSourceType type = AuthSourceType.JWT;

/**
* JWT token
*/
Expand All @@ -32,6 +34,11 @@ public String getRawSource() {
return source;
}

@Override
public AuthSourceType getType() {
return type;
}

@RequiredArgsConstructor
@Getter
@EqualsAndHashCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,34 @@
import org.zowe.apiml.security.common.token.QueryResponse;

/**
* Main implementation of AuthSourceService, currently support only on type of authentication source - JWT token.
* Implementation of AuthSourceService which supports JWT token as authentication source.
*/
@Slf4j
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AuthSourceServiceImpl implements AuthSourceService {
public class JwtAuthSourceService implements AuthSourceService {
@Autowired
private AuthenticationService authenticationService;

/**
* Core method of the interface. Gets source of authentication (JWT token) from request.
* <p>
* @return Optional<AuthSource> which hold original source of authentication (JWT token)
* or Optional.empty() when no authentication source found.
*/
public Optional<AuthSource> getAuthSourceFromRequest() {
final RequestContext context = RequestContext.getCurrentContext();

Optional<String> jwtToken = authenticationService.getJwtTokenFromRequest(context.getRequest());
return jwtToken.map(JwtAuthSource::new);
}

/**
* Validates authentication source (JWT token) using method of {@link AuthenticationService}
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token)
* @return true if token is valid, false otherwise
*/
public boolean isValid(AuthSource authSource) {
if (authSource instanceof JwtAuthSource) {
String jwtToken = ((JwtAuthSource)authSource).getRawSource();
Expand All @@ -47,6 +58,11 @@ public boolean isValid(AuthSource authSource) {
return false;
}

/**
* Validates authentication source (JWT token) using method of {@link AuthenticationService}
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token)
* @return authentication source in parsed form
*/
public AuthSource.Parsed parse(AuthSource authSource) {
if (authSource instanceof JwtAuthSource) {
String jwtToken = ((JwtAuthSource)authSource).getRawSource();
Expand All @@ -57,6 +73,11 @@ public AuthSource.Parsed parse(AuthSource authSource) {
return null;
}

/**
* Generates LTPA token from current source of authentication (JWT token) using method of {@link AuthenticationService}
* @param authSource {@link AuthSource} object which hold original source of authentication (JWT token)
* @return LTPA token
*/
public String getLtpaToken(AuthSource authSource) {
if (authSource instanceof JwtAuthSource) {
String jwtToken = ((JwtAuthSource)authSource).getRawSource();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.gateway.security.service.schema.source;

import java.security.cert.X509Certificate;
import java.util.Date;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
* Implementation of source of authentication based on client certificate.
*/
@RequiredArgsConstructor
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class X509AuthSource implements AuthSource {
public static final AuthSourceType type = AuthSourceType.CLIENT_CERT;
/**
* X509 client certificate
*/
@EqualsAndHashCode.Include
private final X509Certificate source;

@Override
public X509Certificate getRawSource() {
return source;
}

@Override
public AuthSourceType getType() {
return AuthSourceType.CLIENT_CERT;
}

@RequiredArgsConstructor
@Getter
@EqualsAndHashCode
public static class Parsed implements AuthSource.Parsed, X509Parsed {
private final String userId;
private final Date creation;
private final Date expiration;
private final Origin origin;
private final String publicKey;
private final String distinguishedName;

public String getCommonName() {
return userId;
}
}

public interface X509Parsed {
String getCommonName();
String getPublicKey();
String getDistinguishedName();
}
}
Loading

0 comments on commit efd53a8

Please sign in to comment.