Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance zosmf authentication scheme to support client certificates #2207

Merged
merged 21 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gateway-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ dependencies {
annotationProcessor libraries.lombok

implementation libraries.springFox
testCompile libraries.mockito_core
testCompile libraries.spring_mock_mvc
testCompile libraries.spring_boot_starter_test
testCompile libraries.json_smart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/
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;
Expand All @@ -18,6 +17,8 @@
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.AuthenticationService;
import org.zowe.apiml.gateway.security.service.TokenCreationService;
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;
Expand Down Expand Up @@ -67,7 +68,7 @@ public Providers loginProviders(
*/
@Bean
@Qualifier("x509MFAuthSourceService")
public X509AuthSourceService getX509MFAuthSourceService(@Autowired X509AbstractMapper mapper) {
return new X509AuthSourceService(mapper);
public X509AuthSourceService getX509MFAuthSourceService(X509AbstractMapper mapper, TokenCreationService tokenCreationService, AuthenticationService authenticationService) {
return new X509AuthSourceService(mapper, tokenCreationService, authenticationService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void afterPropertiesSet() {
* @param ltpaToken the LTPA token
* @return the JWT token
*/
public String createJwtToken(String username, String domain, String ltpaToken) {
public String createJwtToken(@NonNull String username, String domain, String ltpaToken) {
long now = System.currentTimeMillis();
long expiration = calculateExpiration(now, username);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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;

import org.springframework.security.core.AuthenticationException;

public class AuthenticationSchemeNotSupportedException extends AuthenticationException {

public AuthenticationSchemeNotSupportedException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,39 @@

import com.netflix.appinfo.InstanceInfo;
import com.netflix.zuul.context.RequestContext;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.gateway.security.login.LoginProvider;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSourceService;
import org.zowe.apiml.gateway.security.service.schema.source.JwtAuthSource;
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
import org.zowe.apiml.auth.Authentication;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
import org.zowe.apiml.util.CookieUtil;
import org.zowe.apiml.util.Cookies;

import java.net.HttpCookie;
import java.util.Date;
import java.util.Optional;

/**
* This bean provide LTPA token into request. It get LTPA from authentication source (JWT token value is set on logon)
* and distribute it as cookie.
*/
@Component
@AllArgsConstructor
@RequiredArgsConstructor
public class ZosmfScheme implements AbstractAuthenticationScheme {

private final AuthSourceService authSourceService;
private final AuthConfigurationProperties authConfigurationProperties;
@Value("${apiml.security.auth.provider}")
private String authProvider;

@Override
public AuthenticationScheme getScheme() {
Expand All @@ -47,6 +52,9 @@ public AuthenticationScheme getScheme() {

@Override
public AuthenticationCommand createCommand(Authentication authentication, AuthSource authSource) {
if (!LoginProvider.ZOSMF.getValue().equals(authProvider)) {
throw new AuthenticationSchemeNotSupportedException("ZOSMF authentication scheme is not supported for this API ML instance.");
}
final AuthSource.Parsed parsedAuthSource = authSourceService.parse(authSource);
final Date expiration = parsedAuthSource == null ? null : parsedAuthSource.getExpiration();
final Long expirationTime = expiration == null ? null : expiration.getTime();
Expand Down Expand Up @@ -96,21 +104,21 @@ public void apply(InstanceInfo instanceInfo) {

Optional<AuthSource> authSourceOptional = authSourceService.getAuthSourceFromRequest();
authSourceOptional.ifPresent(authSource -> {
// client cert needs to be translated to JWT in advance, so we can determine what is the source of it
if (authSource.getType().equals(AuthSource.AuthSourceType.CLIENT_CERT)) {
authSource = new JwtAuthSource(authSourceService.getJWT(authSource));
}
// parse authentication source to detect the source (z/OSMF / Zowe)
AuthSource.Parsed parsedAuthSource = authSourceService.parse(authSource);
switch (parsedAuthSource.getOrigin()) {
case ZOSMF:
// token is generated by z/OSMF, fix set cookies
removeCookie(context, authConfigurationProperties.getCookieProperties().getCookieName());
setCookie(context, ZosmfService.TokenType.JWT.getCookieName(), (String)authSource.getRawSource());
break;
case ZOWE:
// user use Zowe own JWT token, for communication with z/OSMF there should be LTPA token, use it
final String ltpaToken = authSourceService.getLtpaToken(authSource);
setCookie(context, ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
break;
default:
return;

if (AuthSource.Origin.ZOSMF.equals(parsedAuthSource.getOrigin())) {
balhar-jakub marked this conversation as resolved.
Show resolved Hide resolved
// token is generated by z/OSMF, fix set cookies
removeCookie(context, authConfigurationProperties.getCookieProperties().getCookieName());
setCookie(context, ZosmfService.TokenType.JWT.getCookieName(), authSourceService.getJWT(authSource));
} else if (AuthSource.Origin.ZOWE.equals(parsedAuthSource.getOrigin())) {
// user use Zowe own JWT token, for communication with z/OSMF there should be LTPA token, use it
final String ltpaToken = authSourceService.getLtpaToken(authSource);
setCookie(context, ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
}

// remove authentication part
Expand All @@ -124,19 +132,19 @@ public void applyToRequest(HttpRequest request) {

Optional<AuthSource> authSourceOptional = authSourceService.getAuthSourceFromRequest();
authSourceOptional.ifPresent(authSource -> {
// client cert needs to be translated to JWT in advance, so we can determine what is the source of it
if (authSource.getType().equals(AuthSource.AuthSourceType.CLIENT_CERT)) {
authSource = new JwtAuthSource(authSourceService.getJWT(authSource));
}
// parse JWT token to detect the source (z/OSMF / Zowe)
AuthSource.Parsed parsedAuthSource = authSourceService.parse(authSource);
switch (parsedAuthSource.getOrigin()) {
case ZOSMF:
cookies.remove(authConfigurationProperties.getCookieProperties().getCookieName());
createCookie(cookies, ZosmfService.TokenType.JWT.getCookieName(), (String)authSource.getRawSource());
break;
case ZOWE:
final String ltpaToken = authSourceService.getLtpaToken(authSource);
createCookie(cookies, ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
break;
default:
return;

if (AuthSource.Origin.ZOSMF.equals(parsedAuthSource.getOrigin())) {
cookies.remove(authConfigurationProperties.getCookieProperties().getCookieName());
createCookie(cookies, ZosmfService.TokenType.JWT.getCookieName(), authSourceService.getJWT(authSource));
} else if (AuthSource.Origin.ZOWE.equals(parsedAuthSource.getOrigin())) {
final String ltpaToken = authSourceService.getLtpaToken(authSource);
createCookie(cookies, ZosmfService.TokenType.LTPA.getCookieName(), ltpaToken);
}
// remove authentication part
request.removeHeaders(HttpHeaders.AUTHORIZATION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ public interface AuthSourceService {
* @return LTPA token
*/
String getLtpaToken(AuthSource authSource);

String getJWT(AuthSource authSource);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
*/
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;
Expand All @@ -23,11 +20,16 @@
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource.AuthSourceType;
import org.zowe.apiml.gateway.security.service.schema.source.AuthSource.Parsed;

import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;

/**
* 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}.
*/
Expand All @@ -41,7 +43,8 @@ public class DefaultAuthSourceService implements AuthSourceService {

/**
* 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 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) {
Expand All @@ -56,6 +59,7 @@ public DefaultAuthSourceService(@Autowired JwtAuthSourceService jwtAuthSourceSer
* 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.
*/
Expand All @@ -64,14 +68,15 @@ 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();
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
*/
Expand All @@ -83,6 +88,7 @@ public boolean isValid(AuthSource 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
*/
Expand All @@ -94,6 +100,7 @@ public Parsed parse(AuthSource authSource) {

/**
* 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
*/
Expand All @@ -105,6 +112,7 @@ public String getLtpaToken(AuthSource authSource) {

/**
* 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
*/
Expand All @@ -114,6 +122,7 @@ private AuthSourceService getService(AuthSource authSource) {

/**
* 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
*/
Expand All @@ -124,4 +133,10 @@ private AuthSourceService getService(AuthSourceType authSourceType) {
}
return service;
}

@Override
public String getJWT(AuthSource authSource) {
AuthSourceService service = getService(authSource);
return service != null ? service.getJWT(authSource) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,9 @@ public String getLtpaToken(AuthSource authSource) {
}
return null;
}

@Override
public String getJWT(AuthSource authSource) {
return ((JwtAuthSource)authSource).getRawSource();
}
}
Loading