Skip to content
Open
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 build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
'pre-flight-check',
'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
Expand Down
1 change: 1 addition & 0 deletions gradle/publish.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ext.javaLibraries = [
'apiml-security-common',
'apiml-tomcat-common',
'certificate-analyser',
'pre-flight-check',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rename this? There are already some pre-flight checks and some in progress. This name is too general.

For example:
zosmf-jwt-check
jwt-check

'common-service-core',
'security-service-client-spring',
'apiml-sample-extension',
Expand Down
331 changes: 331 additions & 0 deletions pre-flight-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# Pre-Flight Check Tool

A Java utility that verifies connectivity to the z/OSMF JWK endpoint **before/after** starting the Zowe API Mediation Layer. This tool helps diagnose configuration issues early such as incorrect hostnames, unreachable ports, missing certificates, or misconfigured z/OSMF by performing a lightweight HTTP(S) call to the z/OSMF JWK endpoint at `/jwt/ibm/api/zOSMFBuilder/jwk`.

## Table of Contents

- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Building](#building)
- [Usage](#usage)
- [CLI Flags Reference](#cli-flags-reference)
- [Certificate Verification Modes](#certificate-verification-modes)
- [Exit Codes](#exit-codes)
- [Response Interpretation](#response-interpretation)
- [Testing Scenarios](#testing-scenarios)
- [1. Quick Test — DISABLED Mode](#1-quick-test--disabled-mode-no-truststore-needed)
- [2. STRICT Mode — Full Verification](#2-strict-mode--full-certificate-and-hostname-verification)
- [3. NONSTRICT Mode — Skip Hostname Check](#3-nonstrict-mode--certificate-chain-verified-hostname-check-skipped)
- [4. HTTP Mode (No SSL)](#4-http-mode-no-ssl)
- [5. Validation Error Tests](#5-validation-error-tests)
- [SAF Keyrings](#saf-keyrings)
- [Troubleshooting](#troubleshooting)

---

## Overview

When Zowe API ML starts, it attempts to reach z/OSMF to obtain public keys for JWT token validation. If z/OSMF is unreachable or misconfigured, the startup fails with errors that can be difficult to diagnose. This pre-flight check tool isolates that connectivity test into a simple, standalone JAR that can be run before Zowe startup.

**What it checks:**

- TCP connectivity to the z/OSMF host and port
- SSL/TLS handshake (when using HTTPS)
- Certificate trust chain validation (STRICT/NONSTRICT modes)
- Hostname verification (STRICT mode)
- HTTP response from the JWK endpoint (`/jwt/ibm/api/zOSMFBuilder/jwk`)

## Prerequisites

- **Java 17 or higher** (Java 17, 21, or any later version)
- Network access to the z/OSMF server
- A truststore containing the z/OSMF server's CA certificate (required for STRICT and NONSTRICT modes)

## Building

From the root of the `api-layer` repository:

```bash
./gradlew :pre-flight-check:build
```

On Windows:

```powershell
.\gradlew :pre-flight-check:build
```

The fat JAR (with all dependencies bundled) will be generated at:

```
pre-flight-check/build/libs/pre-flight-check-<version>.jar
```

For example: `pre-flight-check/build/libs/pre-flight-check-3.5.12-SNAPSHOT.jar`

## Usage

```bash
java -jar pre-flight-check-<version>.jar --zosmf-host <hostname> --zosmf-port <port> [options]
```

**Minimal example (DISABLED mode — quickest way to test):**

```bash
java -jar pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
```

**Full example (STRICT mode with truststore):**

```bash
java -jar pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore /path/to/truststore.p12 \
--truststore-password changeit
```

**Display help:**

```bash
java -jar pre-flight-check-<version>.jar --help
```

## CLI Flags Reference

### Required Flags

| Flag | Description | Example |
|------|-------------|---------|
| `--zosmf-host` | Hostname or IP address of the z/OSMF server | `--zosmf-host myzosmf.example.com` |
| `--zosmf-port` | Port number of the z/OSMF server | `--zosmf-port 11443` |

> **Note:** If `--zosmf-host` or `--zosmf-port` are omitted, picocli will display:
> `Missing required option: '--zosmf-host=<zosmfHost>'`

### Conditionally Required Flags

These flags are required when `--scheme=https` (the default) and `--verify-certificates` is **not** `DISABLED`:

| Flag | Description | Error when missing |
|------|-------------|-------------------|
| `--truststore` | Path to the truststore file containing the z/OSMF CA certificate | `ERROR: --truststore is required when --scheme=https and verification is not DISABLED.` |
| `--truststore-password` | Password for the truststore. If specified without a value, you will be prompted interactively. | `ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.` |

### Optional Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--scheme` | `https` | Protocol to use: `http` or `https` |
| `--verify-certificates` | `STRICT` | Certificate verification mode: `STRICT`, `NONSTRICT`, or `DISABLED` |
| `--truststore-type` | `PKCS12` | Format of the truststore file (e.g., `PKCS12`, `JKS`, `JCERACFKS`) |
| `--keystore` | *(none)* | Path to keystore file (only needed for mutual TLS / client certificate authentication) |
| `--keystore-password` | *(none)* | Password for the keystore. If specified without a value, you will be prompted interactively. |
| `--keystore-type` | `PKCS12` | Format of the keystore file |
| `-h`, `--help` | | Display usage help and exit |

## Certificate Verification Modes

The `--verify-certificates` flag controls how SSL/TLS certificates are validated when connecting over HTTPS. This mirrors the `zowe.verifyCertificates` setting in the Zowe configuration (`zowe.yaml`).

### STRICT (Default)

```bash
--verify-certificates STRICT
```

- **Certificate chain**: Fully validated against the truststore
- **Hostname verification**: The server certificate's CN/SAN must match the `--zosmf-host` value
- **Truststore**: Required
- **Use case**: Production environments — maximum security

### NONSTRICT

```bash
--verify-certificates NONSTRICT
```

- **Certificate chain**: Fully validated against the truststore
- **Hostname verification**: Skipped — the server certificate does not need to match the hostname
- **Truststore**: Required
- **Use case**: Environments where the z/OSMF certificate is issued for a different hostname (e.g., accessing via IP address when the cert has a DNS name)

### DISABLED

```bash
--verify-certificates DISABLED
```

- **Certificate chain**: Not validated — all certificates are trusted
- **Hostname verification**: Skipped
- **Truststore**: Not required
- **Use case**: Development/testing environments, or initial connectivity debugging
- **Warning**: Prints `WARNING: SSL certificate verification is DISABLED. All certificates will be trusted.`

> **Security Note:** `DISABLED` mode should **never** be used in production. It is vulnerable to man-in-the-middle attacks.

## Exit Codes

| Code | Meaning |
|------|---------|
| `0` | **Success** — z/OSMF JWK endpoint is reachable and responding |
| `4` | **Failure** — connection failed, SSL error, endpoint not found, or configuration error |
| `8` | **Help** — help/version was displayed; no check was performed |

## Response Interpretation

The tool interprets HTTP response codes from the z/OSMF JWK endpoint as follows:

| HTTP Code | Result | Message |
|-----------|--------|---------|
| 200-299 | **SUCCESS** | `z/OSMF JWK endpoint is reachable and responding. HTTP <code>` |
| 401 | **SUCCESS** | `z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401` |
| 404 | **FAILURE** | `z/OSMF JWK endpoint not found. HTTP 404` — Consider configuring `jwtAutoConfiguration` to LTPA |
| 4xx (other) | **FAILURE** | `z/OSMF JWK endpoint returned unexpected client error. HTTP <code>` |
| 5xx | **FAILURE** | `z/OSMF JWK endpoint returned server error. HTTP <code>` |

**Note:** A `401 Unauthorized` is treated as **success** because the tool does not send authentication credentials. A 401 confirms the endpoint exists and z/OSMF is processing requests.

### Connection-Level Errors

| Error | Message |
|-------|---------|
| SSL handshake failure | `FAILURE: SSL handshake failed. Verify that the truststore contains the z/OSMF server certificate.` |
| Connection refused | `FAILURE: Cannot connect to <host>:<port>. Verify the host and port are correct and z/OSMF is running.` |
| Connection timeout | `FAILURE: Connection timed out to <host>:<port>.` |

## Testing Scenarios

Below are step-by-step commands for testing all modes. Replace `<version>` with your actual JAR version (e.g., `3.5.12-SNAPSHOT`) and adjust the host/port for your environment.

### 1. Quick Test — DISABLED Mode (No Truststore Needed)

The fastest way to verify basic TCP + HTTP connectivity:

```bash
java -jar pre-flight-check/build/libs/pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--verify-certificates DISABLED
```

**Expected output (success):**

```
WARNING: SSL certificate verification is DISABLED. All certificates will be trusted.
Checking z/OSMF JWK endpoint: https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk
SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
```

### 2. STRICT Mode — Full Certificate and Hostname Verification

Requires a truststore containing the z/OSMF server's CA certificate (see [Creating a Truststore](#creating-a-truststore)):

```bash
java -jar pre-flight-check/build/libs/pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore /path/to/zosmf-truststore.p12 \
--truststore-password password
```

**Expected output (success):**

```
Checking z/OSMF JWK endpoint: https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk
SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
```

**Expected output (SSL failure — wrong truststore):**

```
FAILURE: SSL handshake failed when connecting to https://myzosmf.example.com:11443/jwt/ibm/api/zOSMFBuilder/jwk.
Verify that the truststore contains the z/OSMF server certificate.
Details: PKIX path building failed: ...unable to find valid certification path to requested target
```

### 3. NONSTRICT Mode — Certificate Chain Verified, Hostname Check Skipped

Useful when connecting via IP address but the certificate has a DNS name:

```bash
java -jar pre-flight-check/build/libs/pre-flight-check-<version>.jar \
--zosmf-host 10.0.0.50 \
--zosmf-port 11443 \
--truststore /path/to/zosmf-truststore.p12 \
--truststore-password password \
--verify-certificates NONSTRICT
```

**Expected output (success):**

```
INFO: Hostname verification is disabled (NONSTRICT mode).
Checking z/OSMF JWK endpoint: https://10.0.0.50:11443/jwt/ibm/api/zOSMFBuilder/jwk
SUCCESS: z/OSMF JWK endpoint exists (returned 401 Unauthorized — expected without credentials). HTTP 401
```

### 4. HTTP Mode (No SSL)

For z/OSMF instances running on plain HTTP (uncommon):

```bash
java -jar pre-flight-check/build/libs/pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 80 \
--scheme http
```

### 5. Validation Error Tests

**Missing required flags:**

```bash
# No arguments at all
java -jar pre-flight-check-<version>.jar
# Output: Missing required options: '--zosmf-host=<zosmfHost>', '--zosmf-port=<zosmfPort>'

# Missing truststore in STRICT mode
java -jar pre-flight-check-<version>.jar --zosmf-host myhost --zosmf-port 443
# Output: ERROR: --truststore is required when --scheme=https and verification is not DISABLED.

# Missing truststore password
java -jar pre-flight-check-<version>.jar --zosmf-host myhost --zosmf-port 443 --truststore my.p12
# Output: ERROR: --truststore-password is required when --scheme=https and verification is not DISABLED.
```

**Invalid values:**

```bash
# Invalid scheme
java -jar pre-flight-check-<version>.jar --zosmf-host myhost --zosmf-port 443 --scheme ftp
# Output: ERROR: --scheme must be 'http' or 'https', got: ftp

# Invalid verify mode
java -jar pre-flight-check-<version>.jar --zosmf-host myhost --zosmf-port 443 --verify-certificates INVALID
# Output: ERROR: --verify-certificates must be STRICT, NONSTRICT, or DISABLED, got: INVALID
```

**Unreachable host:**

```bash
java -jar pre-flight-check-<version>.jar --zosmf-host nonexistent.host --zosmf-port 443 --verify-certificates DISABLED
# Output: FAILURE: Cannot connect to nonexistent.host:443.
```

## SAF Keyrings

On z/OS, if you are using SAF keyrings instead of file-based keystores/truststores, provide the keyring path in the `safkeyring://` format and add the JVM protocol handler:

```bash
java -Djava.protocol.handler.pkgs=com.ibm.crypto.provider \
-jar pre-flight-check-<version>.jar \
--zosmf-host myzosmf.example.com \
--zosmf-port 11443 \
--truststore safkeyring://IZUSVR/ZoweKeyring \
--truststore-password password \
--truststore-type JCERACFKS
```
27 changes: 27 additions & 0 deletions pre-flight-check/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
plugins {
id 'java'
}

dependencies {
implementation libs.picocli
annotationProcessor libs.picocli.codegen

testImplementation libs.mockito.core
testImplementation libs.hamcrest
}

compileJava {
options.compilerArgs += ["-Aproject=${project.group}/${project.name}"]
}

jar {
manifest {
attributes(
'Main-Class': 'org.zowe.apiml.PreFlightCheck'
)
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Loading
Loading