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

fix: z/OSMF logging improvements #2998

Merged
merged 23 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.zowe.apiml.security.*;
import org.zowe.apiml.security.ApimlPoolingHttpClientConnectionManager;
import org.zowe.apiml.security.HttpsConfig;
import org.zowe.apiml.security.HttpsConfigError;
import org.zowe.apiml.security.HttpsFactory;
import org.zowe.apiml.security.SecurityUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
Expand Down Expand Up @@ -173,10 +178,10 @@ public void init() {

} catch (HttpsConfigError e) {
log.error("Invalid configuration of HTTPs: {}", e.getMessage());
System.exit(1); // NOSONAR
System.exit(1);
} catch (Exception e) {
log.error("Cannot construct configuration of HTTPs: {}", e.getMessage());
System.exit(1); // NOSONAR
System.exit(1);
}
}

Expand Down
10 changes: 5 additions & 5 deletions certificate-analyser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Usage

java -jar certificate-analyser-<version>.jar --help

```
Usage: <main class> [-hl] [-kp[=<keyPasswd>]] [-tp[=<trustPasswd>]]
[-a=<keyAlias>] [-k=<keyStore>] [-kt=<keyStoreType>]
Expand All @@ -28,9 +29,10 @@ Usage: <main class> [-hl] [-kp[=<keyPasswd>]] [-tp[=<trustPasswd>]]
-tt, --truststoretype=<trustStoreType>
Truststore type, default is PKCS12
```

*NOTE*

keypasswd - if you specify this parameter without a value(e.g. java -jar <file.jar> --keypasswd), you will be asked to enter the password
keypasswd - if you specify this parameter without a value(e.g. java -jar <file.jar> --keypasswd), you will be asked to enter the password

trustpasswd - if you specify this parameter without a value(e.g. java -jar <file.jar> --trustpasswd), you will be asked to enter the password
- if this parameter is omitted completely, value from keypasswd will be used
Expand All @@ -39,14 +41,12 @@ truststoretype - if this parameter is omitted completely, value from keystoretyp

### Do local handshake

java -jar -Djavax.net.debug=ssl:handshake:verbose certificate-analyser-<version>.jar --keystore ../../../keystore/localhost/localhost.keystore.p12 --truststore ../../../keystore/localhost/localhost.truststore.p12 --keypasswd password --keyalias localhost --local
java -jar -Djavax.net.debug=ssl:handshake:verbose certificate-analyser-<version>.jar --keystore ../../../keystore/localhost/localhost.keystore.p12 --truststore ../../../keystore/localhost/localhost.truststore.p12 --keypasswd password --keyalias localhost --local

### Keyring

If you are using SAF keyrings, you need to provide an additional parameter in command line `-Djava.protocol.handler.pkgs=com.ibm.crypto.provider`.

### Possible issues:
### Possible issues

Keystore/truststore is owned by different user - permission error. Temporarily Change read permission to all.


Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,4 @@ public ResponseEntity<String> forwardLogout(
class LoginRequest {
private String username;
private char[] password;

}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

package org.zowe.apiml.gateway.health;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
Expand All @@ -33,7 +32,6 @@ public class GatewayHealthIndicator extends AbstractHealthIndicator {
private final Providers loginProviders;
private String apiCatalogServiceId;

@Autowired
public GatewayHealthIndicator(DiscoveryClient discoveryClient,
Providers providers,
@Value("${apiml.catalog.serviceId:}") String apiCatalogServiceId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.springframework.security.authentication.AuthenticationServiceException;
import org.zowe.apiml.gateway.security.config.CompoundAuthProvider;
import org.zowe.apiml.gateway.security.service.zosmf.ZosmfService;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
import org.zowe.apiml.security.common.config.AuthConfigurationProperties;
import org.zowe.apiml.security.common.error.ServiceNotAccessibleException;

Expand All @@ -27,18 +29,34 @@ public class Providers {
private final CompoundAuthProvider compoundAuthProvider;
private final ZosmfService zosmfService;

@InjectApimlLogger
private ApimlLogger apimlLog = ApimlLogger.empty();

/**
* This method decides whether the Zosmf service is available.
*
* @return Availability of the ZOSMF service in the system.
* @throws AuthenticationServiceException if the z/OSMF service id is not configured
*/
public boolean isZosmfAvailable() {
boolean isZosmfRegisteredAndPropagated = !this.discoveryClient.getInstances(authConfigurationProperties.validatedZosmfServiceId()).isEmpty();
log.debug("zOSMF registered with the Discovery Service and propagated to Gateway: {}", isZosmfRegisteredAndPropagated);
String zosmfServiceId = authConfigurationProperties.validatedZosmfServiceId();
boolean isZosmfRegisteredAndPropagated = !this.discoveryClient.getInstances(zosmfServiceId).isEmpty();
if (!isZosmfRegisteredAndPropagated) {
apimlLog.log("org.zowe.apiml.security.auth.zosmf.serviceId", zosmfServiceId);
}
log.debug("z/OSMF registered with the Discovery Service and propagated to Gateway: {}", isZosmfRegisteredAndPropagated);
return isZosmfRegisteredAndPropagated;
}

/**
* Provide configured z/OSMF service ID from the Gateway auth configuration.
*
* @return service ID of z/OSMF instance
*/
public String getZosmfServiceId() {
return authConfigurationProperties.validatedZosmfServiceId();
}

/**
* Verify that the zOSMF is registered in the Discovery service and that we can actually reach it.
*
Expand All @@ -48,11 +66,12 @@ public boolean isZosmfAvailableAndOnline() {
try {
boolean isAvailable = isZosmfAvailable();
boolean isAccessible = zosmfService.isAccessible();
log.debug("zOSMF is registered and propagated to the DS: {} and is accessible based on the information: {}", isAvailable, isAccessible);

log.debug("z/OSMF is registered and propagated to the DS: {} and is accessible based on the information: {}", isAvailable, isAccessible);

return isAvailable && isAccessible;
} catch (ServiceNotAccessibleException exception) {
log.debug("zOSMF isn't registered to the Gateway yet");
} catch (ServiceNotAccessibleException e) {
log.debug("z/OSMF is not registered to the Gateway yet: {}", e.getMessage());

return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.zowe.apiml.gateway.security.service;

import com.google.common.annotations.VisibleForTesting;
import com.netflix.discovery.CacheRefreshedEvent;
import com.netflix.discovery.EurekaEvent;
import com.netflix.discovery.EurekaEventListener;
Expand Down Expand Up @@ -38,10 +39,7 @@
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.*;

import static org.awaitility.Awaitility.await;

Expand Down Expand Up @@ -77,16 +75,19 @@ public class JwtSecurity {

private final Providers providers;
private final ZosmfListener zosmfListener;
private final String zosmfServiceId;

private final List<String> events = new ArrayList<>();
private final Set<String> events = Collections.synchronizedSet(new HashSet<>());

@Autowired
public JwtSecurity(Providers providers, ApimlDiscoveryClient discoveryClient) {
this.providers = providers;
this.zosmfServiceId = providers.getZosmfServiceId();
this.zosmfListener = new ZosmfListener(discoveryClient);
}

public JwtSecurity(Providers providers, String keyAlias, String keyStore, char[] keyStorePassword, char[] keyPassword, ApimlDiscoveryClient discoveryClient) {
@VisibleForTesting
JwtSecurity(Providers providers, String keyAlias, String keyStore, char[] keyStorePassword, char[] keyPassword, ApimlDiscoveryClient discoveryClient) {
this(providers, discoveryClient);

this.keyStore = keyStore;
Expand Down Expand Up @@ -120,18 +121,18 @@ public void loadAppropriateJwtKeyOrFail() {
loadJwtSecret();
switch (used) {
case ZOSMF:
log.info("zOSMF is used as the JWT producer");
events.add("zOSMF is recognized as authentication provider.");
log.info("z/OSMF instance {} is used as the JWT producer", zosmfServiceId);
addEvent("z/OSMF instance " + zosmfServiceId + " is recognized as authentication provider.");
validateInitializationAgainstZosmf();
break;
case APIML:
log.info("API ML is used as the JWT producer");
events.add("API ML is recognized as authentication provider.");
addEvent("API ML is recognized as authentication provider.");
validateJwtSecret();
break;
case UNKNOWN:
log.info("zOSMF is probably used as the JWT producer but isn't available yet.");
events.add("Wait for zOSMF to come online before deciding who provides JWT tokens.");
log.info("z/OSMF instance {} is probably used as the JWT producer but isn't available yet.", zosmfServiceId);
addEvent("Wait for z/OSMF instance " + zosmfServiceId + " to come online before deciding who provides JWT tokens.");
validateInitializationWhenZosmfIsAvailable();
break;
default:
Expand Down Expand Up @@ -207,12 +208,12 @@ private HttpsConfig currentConfig() {
*/
private void validateInitializationAgainstZosmf() {
if (!providers.zosmfSupportsJwt()) {
events.add("API ML is responsible for token generation.");
log.debug("zOSMF is UP and does not support JWT");
addEvent("API ML is responsible for token generation.");
log.debug("z/OSMF instance {} is UP and does not support JWT", zosmfServiceId);
validateJwtSecret();
} else {
events.add("zOSMF is UP and supports JWT");
log.debug("zOSMF is UP and supports JWT");
addEvent("z/OSMF instance " + zosmfServiceId + " is UP and supports JWT");
log.debug("z/OSMF instance {} is UP and supports JWT", zosmfServiceId);
}
}

Expand Down Expand Up @@ -260,16 +261,18 @@ private void validateInitializationWhenZosmfIsAvailable() {

new Thread(() -> {
try {
events.add("Started waiting for zOSMF to be registered and known by the discovery service");
log.debug("Waiting for zOSMF to be registered and known by the Discovery Service.");
addEvent("Started waiting for z/OSMF instance " + zosmfServiceId + " to be registered and known by the discovery service");
log.debug("Waiting for z/OSMF instance {} to be registered and known by the Discovery Service.", zosmfServiceId);
await()
.atMost(Duration.of(timeout, ChronoUnit.MINUTES))
.with()
.pollInterval(Durations.ONE_MINUTE)
.until(zosmfListener::isZosmfReady);
} catch (ConditionTimeoutException e) {
apimlLog.log("org.zowe.apiml.gateway.jwtProducerConfigError", StringUtils.join(events, "\n"));
apimlLog.log("org.zowe.apiml.security.zosmfInstanceNotFound", "zOSMF");
synchronized (events) {
apimlLog.log("org.zowe.apiml.gateway.jwtProducerConfigError", StringUtils.join(events, "\n"));
}
apimlLog.log("org.zowe.apiml.security.zosmfInstanceNotFound", zosmfServiceId);
System.exit(1);
}
}).start();
Expand All @@ -278,6 +281,7 @@ private void validateInitializationWhenZosmfIsAvailable() {
/**
* Only for unit testing
*/
@VisibleForTesting
ZosmfListener getZosmfListener() {
return zosmfListener;
}
Expand All @@ -298,21 +302,25 @@ public void onEvent(EurekaEvent event) {
return;
}

events.add("Discovery Service Cache was updated.");
log.debug("Trying to reach the zOSMF.");
addEvent("Discovery Service Cache was updated.");
log.debug("Trying to reach the z/OSMF instance " + zosmfServiceId + ".");
if (providers.isZosmfAvailableAndOnline()) {
events.add("zOSMF is avaiable and online.");
log.debug("The zOSMF was reached ");
addEvent("z/OSMF instance " + zosmfServiceId + " is available and online.");
log.debug("The z/OSMF instance {} was reached.", zosmfServiceId);

discoveryClient.unregisterEventListener(this); // only need to see zosmf up once to validate jwt secret
isZosmfReady = true;

try {
validateInitializationAgainstZosmf();
} catch (HttpsConfigError e) {
apimlLog.log("org.zowe.apiml.gateway.jwtProducerConfigError", StringUtils.join(events, "\n"));
synchronized (events) {
apimlLog.log("org.zowe.apiml.gateway.jwtProducerConfigError", StringUtils.join(events, "\n"));
pablocarle marked this conversation as resolved.
Show resolved Hide resolved
}
System.exit(1);
}
} else {
addEvent("z/OSMF instance " + zosmfServiceId + " is not available and online yet.");
}
}
};
Expand All @@ -338,4 +346,8 @@ public enum JwtProducer {
APIML,
UNKNOWN
}

private void addEvent(String event) {
events.add( event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public String getToken(String username, int expirationTime, Set<String> scopes)
}

public boolean isValidForScopes(String jwtToken, String serviceId) {
// FIXME no logs?
if (serviceId != null) {
QueryResponse parsedToken = authenticationService.parseJwtWithSignature(jwtToken);
if (parsedToken != null && parsedToken.getScopes() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.zowe.apiml.message.log.ApimlLogger;
import org.zowe.apiml.product.logging.annotations.InjectApimlLogger;
Expand All @@ -28,6 +29,9 @@
import org.zowe.apiml.security.common.login.LoginRequest;
import org.zowe.apiml.util.EurekaUtils;

import javax.net.ssl.SSLHandshakeException;

import java.net.ConnectException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
Expand Down Expand Up @@ -144,15 +148,38 @@ protected String getURI(String zosmf) {
*/
protected RuntimeException handleExceptionOnCall(String url, RuntimeException re) {
if (re instanceof ResourceAccessException) {
if (re.getCause() instanceof SSLHandshakeException) {
log.error("SSL Misconfiguration, z/OSMF is not accessible. Please verify the following: \n" +
" - CN (Common Name) and z/OSMF hostname have to match.\n" +
" - Certificate is expired\n" +
" - TLS version match\n" +
"Further details and a stack trace will follow", re);
}
apimlLog.log("org.zowe.apiml.security.serviceUnavailable", url, re.getMessage());
return new ServiceNotAccessibleException("Could not get an access to z/OSMF service.");
}

if (re instanceof HttpClientErrorException.Unauthorized) {
log.warn("Request to z/OSMF requires authentication", re.getMessage());
return new BadCredentialsException("Invalid Credentials");
}

if (re instanceof RestClientResponseException) {
RestClientResponseException responseException = (RestClientResponseException) re;
if (log.isTraceEnabled()) {
log.trace("z/OSMF request {} failed with status code {}, server response: {}", url, responseException.getRawStatusCode(), responseException.getResponseBodyAsString());
} else {
log.debug("z/OSMF request {} failed with status code {}", url, responseException.getRawStatusCode());
}
}

if (re.getCause() instanceof ConnectException) {
log.warn("Could not connecto to z/OSMF. Please verify z/OSMF instance is up and running {}", re.getMessage());
return new ServiceNotAccessibleException("Could not connect to z/OSMF service.");
}

if (re instanceof RestClientException) {
log.debug("z/OSMF isn't accessible. {}", re.getMessage());
apimlLog.log("org.zowe.apiml.security.generic", re.getMessage(), url);
return new AuthenticationServiceException("A failure occurred when authenticating.", re);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.zosmf;

import lombok.Data;

@Data
public class ZosmfAuthResponse {

private int returnCode;
private int reasonCode;
private String message;

}
Loading
Loading