OTEL data, z/OS data gatherer, Jaeger and TLS

OpenTelemetry is a technology for providing trace data as work flows between systems. It is often called OpenTel or OTel.

As work flows around a network, status(“where the work is”) information is sent to a central location, and tools at this central location can sew the data together and produce visualisations of the flow of data, and where the work was delayed.

The data is normally sent over TCP/IP.

Products/programs could emit their own data in the required format, and sent it over TCP/IP to the server. On z/OS data can be written to In-Memory SMF, and an IBM provided data collector reads the SMF records and sends the data over TCP/IP to the central site.

Typically the data is sent to a visualisation tool called Jaeger, so you can see where the delays in your applications are.

IBM Data gatherer and Jaeger can connect over http, but I could not configure Jaeger to support TLS.
I could get IBM data gatherer to communicate using TLS to OpenTel, which passed the data on the Jaeger.

The TLS implementation of IBM Data Gatherer, feels it was written by someone who does not understand TLS, and only used self signed certificates!

Tools like the OpenTelemetry project can capture the information and make it available to visualisation tools like Jaeger, and data reporting tools like Prometheus.

I’ve documented how to install the IBM Data Gatherer This blog post takes the next step of using IBM Data Gatherer with TLS to secure the traffic from z/OS.

z/OS and Data Gatherer set up for TLS

For TLS you need the certificate, and a private key. These are .pem files (not RACF certificates). To get the TLS working, I copied the files from my Linux machine. You should generate files properly with tools like openssl.

With TLS you normally have a set of CA certificates to validate any certificate received. With Data Gatherer you provide have to provide a file of the certificate(s) of the back end server – and not the Certificate Authority certificates. This makes me think the developer of the code only used self signed certificates.

In the Data Gatherer JCL I had

//   SET $TLSCLKY='/usr/colin/oemput/certs/tempcert.key.pem' 
// SET $TLSCLCR='/usr/colin/oemput/certs/tempcert.pem'
// SET $TLSCERT='/usr/colin/oemput/certs/tempcert.pem'
// SET $OTLENDP='https://10.1.0.2:4317'
//JAVAJVM EXEC PGM=JVMLDM&VERSION,REGION=&REGSIZE,PARM='/+I'
...
//STDENV *
...
IJO="$IJO -Djavax.net.debug=ssl:handshake "

The PARM=’/+I’ displays the parameters passed to the program. For example

JVMJZBL1006I IBM_JAVA_OPTIONS =  -Dgrb.otel.dt.read.flag=0 
-Dgrb.otel.dt.endpoint=https://10.1.0.2:4317 -Dgrb.otel.dt.protocol=grpc
-Dgrb.otel.dt.resource.names=IFASMF.MQOTEL -Dgrb.otel.dt.tls.enabled=true
-Dgrb.otel.dt.mtls.enabled=false
-Dgrb.otel.dt.tls.trusted.certs=/usr/colin/oemput/certs/tempcert.pem
-Dgrb.otel.dt.tls.client.key=/usr/colin/oemput/certs/tempcert.key.pem
-Dgrb.otel.dt.tls.client.cert=/usr/colin/oemput/certs/tempcert.pem
-Dgrb.otel.dt.exporter=false -Dgrb.otel.dt.resource.buffer.size=
-Dgrb.otel.dt.data.dump=false
-Dfile.encoding=IBM-1047 -Dgrb.otel.dt.stats.enabled=true -Djavax.net.debug=ssl:handshake

The -Djavax.net.debug turns on the Java trace of TLS. It shows the TLS handshake and I/O information

Create the Otel container

See One minute – docker to docker gfor more information about configuring multiple docker containers to work with each other.

I created the docker network

docker network create otel-jaeger-network

I created a docker image

docker run --rm  --name otelcollector \
--volume "$(pwd)/o1.yaml":/otel-config.yaml \
-v "/mnt/Colin/ssl/ssl2/tempcert.pem":"/server.pem" \
-v "/mnt/Colin/ssl/ssl2/tempcert.key.pem":"/server.key.pem" \
--env COLLECTOR_OTLP_ENABLED=true \
--publish 4318:4318 \
--publish 4317:4317 \
--network otel-jaeger-network \
--network-alias otelcollector \
otel/opentelemetry-collector --config otel-config.yaml

Where the tempcert.key.pem and tempcert.pem where the private key, and certificate, the same ones as I used on z/OS.

The configuration file o1.yaml had


receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
tls:
cert_file: server.pem
key_file: server.key.pem
min_version: "1.3" # Enforces TLS 1.3 as the minimum requirement
max_version: "1.3" # Locks maximum version to TLS 1.3
cipher_suites: []
reload_interval: "1h"
http:
exporters:

otlphttp/jaeger:
endpoint: "http://jaeger2:4318"
tls:
insecure: true

service:
pipelines:
traces:
receivers: [ otlp ]
processors:
exporters: [otlphttp/jaeger ]

The tls: section had the TLS parameters. I specified TLS v1.3. You could specify 1.2; but 1.3 is better.

The exporters: otlphttp/jaeger:endpoint: “http://jaeger2:4318″ says send data to port 4318 in docker container with the name jaeger2.

There is a lot of good information about configuring OTEL here.

Create the Jaeger container

I created the container

docker run --rm  --name jaeger2 \
--env COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
--env COLLECTOR_OTLP_ENABLED=true \
--publish 16686:16686 \
--network otel-jaeger-network \
--network-alias jaeger \
-v "/home/colin/otel/j1.yaml":"/j1.yaml" \
cr.jaegertracing.io/jaegertracing/jaeger:2.19.0

This container has a name of jaeger2, matching the value in the opentel container.

This maps the internal port 16686 to the external port 16686 so I could use my browser on http://localhost:16686.

With this set of configurations I was able to use TLS from z/OS.

Various TLS return codes

When debugging TLS problems I got various return codes. I’m collecting them here, so I can find them next time I have a problem.

I’d be happy to add to any problems and solutions you find, please let me know.

TLS Handshake failure

Alert 40

Wireshark produced

  • Alert Message
    • Level: Fatal (2)
    • Description: Handshake Failure (40)

Looking in the CTRACE I got

No SSL V3 cipher specs enabled for TLS V1.3

See tls-1-3-everything-possibly-needed-know. This has

just five recommended cipher suites:

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_GCM_SHA256
  • TLS_AES_128_CCM_8_SHA256
  • TLS_AES_128_CCM_SHA256

Alert 51

With TLS 1.3, A certificate like

SUBJECTSDN(CN('10.1.1.2') - 
O('NISTEC256') -
OU('SSS')) -
ALTNAME(IP(10.1.1.2))-
NISTECC -
KEYUSAGE( HANDSHAKE ) -
SIZE(256 ) -
SIGNWITH (CERTAUTH LABEL('DOCZOSCA')) -
WITHLABEL('NISTEC256')

Failed. But changing it to SIZE(512) worked. Strange, because size 512 is supposed to be supported.

Debug details

From the CTRACE

 ICSF service failure: CSFPPKS retCode = 0x8, rsnCode = 0x2b00                                            

S0W1 MESSAGE 00000004 10:25:45.006617 SSL_ERROR
Job TCPIP Process 0001003B Thread 00000003 crypto_sign_data
crypto_ec_sign_data() failed: Error 0x03353084

S0W1 MESSAGE 00000004 10:25:45.006883 SSL_ERROR
Job TCPIP Process 0001003B Thread 00000003 construct_tls13_certificate_verify_message
Unable to generate certificate verify message: Error 0x03353084

S0W1 MESSAGE 00000004 10:25:45.007124 SSL_ERROR
Job TCPIP Process 0001003B Thread 00000003 send_tls13_alert
Sent TLS 1.3 alert 51 to ::ffff:10.1.0.2.43416.

in z/OS Unix the command

grep 03353084 /usr/incl/gsk

gave

/usr/include/gskcms.h:#define CMSERR_ICSF_SERVICE_FAILURE         0x03353084          

The ICSF API points to return codes. 2B00 (11008) says

The public or private key values are not valid (for example, the modulus or an exponent is zero or the exponent is even) or the key could not have created the signature (for example, the modulus value is less than the signature value). In any case, the key cannot be used to verify the signature.

Changing to

Policy agent

...
ServerCertificateLabel NISTECC521
...
RACDCERT ID(START1) GENCERT -                             
SUBJECTSDN(CN('10.1.1.2') -
O('NISTECC256') -
OU('SSS')) -
ALTNAME(IP(10.1.1.2))-
NISTECC -
KEYUSAGE(HANDSHAKE ) -
SIZE(256) -
SIGNWITH (CERTAUTH LABEL('DOCZOSCA')) -
WITHLABEL('NISTECC256')

worked.

I needed to do F CPAGENT,REFRESH to pickup the change. I needed to refresh the policy agent, because I was using TN3270, which uses AT-TLS.

Session just ends with no alert

Looking at the CTRACE output I got

S0W1      MESSAGE   00000004  12:52:55.333904  SSL_ERROR                                  
Job TCPIP Process 0201001E Thread 00000001 crypto_chacha_encrypt_ctx
ICSF service failure: CSFPSKE retCode = 0x8, rsnCode = 0xbfe

S0W1 MESSAGE 00000004 12:52:55.334123 SSL_ERROR
Job TCPIP Process 0201001E Thread 00000001 crypto_chacha_encrypt_ctx
The algorithm or key size is not supported by ICSF FIPS

S0W1 MESSAGE 00000004 12:52:55.334355 SSL_ERROR
Job TCPIP Process 0201001E Thread 00000001 gsk_encrypt_tls13_record
ChaCha20 Encryption failed: Error 0x0335308f

The return code 0xbfe is

The PKCS #11 algorithm, mode, or keysize is not approved for ICSF FIPS 140-2. This reason code can be returned for PKCS #11 clear key requests when ICSF is in a FIPS 140-2 mode or 140-3,HYBRID mode. To see how 8/BFE(3070) can be returned when the ICSF FIPSMODE is 140-3,HYBRID, see ‘Requiring FIPS 140-2 algorithm checking from select z/OS PKCS #11 applications’ in z/OS Cryptographic Services ICSF Writing PKCS #11 Applications.

FIPS was incorrectly specified. For example FIPS-140 with TLS 1.3

TLS 1.3, Java and z/OS

I was failing to get this working with Java 8 – despite it being supported in Java 8.

I eventually found a support statement which says

Enables TLS v1.3 protocol (defined by RFC 8446) in SR6 FP25

however java -version gave me

Java -version gives build 8.0.6.16 – pmz6480sr6fp16-20200902_01(SR6 FP16))

I had Fix Pack 16 and the support is in Fix Pack 25 (from 2020)

Download a more up to date version

The IBM Support page Java SDK Products on z/OS lists the supported version of Java. There is a link to IBM Semeru Runtime Certified Edition for z/OS (Semeru is free production-ready binaries built with the OpenJDK class libraries and the Eclipse OpenJ9 JVM).

When I tried to download V8 SR8, I got a window saying “You cannot download this due to export restrictions”. Next day, I tried again and it worked, the droids working in the background much have found I was a good guy.

Check your java.security file

When I used my own copy of the java.security file it did not work.

I had

security.provider.1=com.ibm.crypto.provider.IBMJCE 
security.provider.2=com.ibm.crypto.plus.provider.IBMJCEPlus 

The latest fix pack Java had

security.provider.1=com.ibm.jsse2.IBMJSSEProvider2 
security.provider.2=com.ibm.crypto.provider.IBMJCE 
security.provider.3=com.ibm.crypto.plus.provider.IBMJCEPlus 

The error messages I got were

SEVERE org.apache.catalina.util.LifecycleBase.handleSubClassException
Failed to initialize component Connector HTTP/1.1-6800

org.apache.catalina.LifecycleException: Protocol handler initialization failed Caused by: java.lang.IllegalArgumentException: None of the protocols specified are supported by the SSL engine : TLSv1.3

As the support for TLSv1.3 was in JSSE, it is clear (with hindsight) that I needed to specific the provider com.ibm.jsse2.IBMJSSEProvider2!