October 19, 2022

OpenShift 4.6 Automation and Integration: Enterprise Authentication

Introduction

$ kinit admin

$ ipa -vv user-show admin
...
"result": {
"dn": "uid=admin,cn=users,cn=accounts,dc=mkk,dc=example,dc=com",
...

$ ipa group-find
...
  Group name: admins
...

$ ipa -vv group-show admins
...
"result": {
"dn": "cn=admins,cn=groups,cn=accounts,dc=mkk,dc=example,dc=com ",
...

Configuring the LDAP Identity Provider

$ oc explain OAuth.spec.identityProviders.ldap
...
FIELDS:
...
   bindPassword	<Object>
     bindPassword is an optional reference to a secret by name containing a
     password to bind with during the search phase. The key "bindPassword" is
     used to locate the data. If specified and the secret or expected key is not
     found, the identity provider is not honored. The namespace for this secret
     is openshift-config.

   ca	<Object>
     ca is an optional reference to a config map by name containing the
     PEM-encoded CA bundle. It is used as a trust anchor to validate the TLS
     certificate presented by the remote server. The key "ca.crt" is used to
     locate the data. If specified and the config map or expected key is not
     found, the identity provider is not honored. If the specified ca data is
     not valid, the identity provider is not honored. If empty, the default
     system roots are used. The namespace for this config map is
     openshift-config.
...

Administration -> Cluster Settings -> Configuration -> OAuth

curl http://idm.mkk.example.com/ipa/config/ca.crt

bindDN: "uid=admin,cn=users,cn=accounts,dc=mkk,dc=example,dc=com"

url: "ldaps://idm.mkk.example.com/cn=users,cn=accounts,dc=mkk,dc=example,dc=com?uid"

Troubleshooting

  • Authentication Operator Logs
  • Oauth Pods status
  • oc get pods -n openshift-authentication

Synchronizing LDAP Groups

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/authentication_and_authorization/index#ldap-auto-syncing_ldap-syncing-groups

ldap-sync-config-map.yaml

kind: LDAPSyncConfig
apiVersion: v1
url: ldaps://idm.mkk.example.com/
insecure: false
bindDN: uid=admin,cn=users,cn=accounts,dc=mkk,dc=example,dc=com
bindPassword: redhat123
ca: /tmp/ca.crt
rfc2307:
  groupsQuery:
    baseDN: "cn=groups,cn=accounts,dc=mkk,dc=example,dc=com"
    scope: sub
    derefAliases: never
    pageSize: 0
    filter: "(objectClass=ipausergroup)"
  groupUIDAttribute: dn
  groupNameAttributes: [ cn ]
  groupMembershipAttributes: [ member ]
  usersQuery:
    baseDN: "cn=users,cn=accounts,dc=mkk,dc=example,dc=com"
    scope: sub
    derefAliases: never
    pageSize: 0
  userUIDAttribute: dn
  userNameAttributes: [ uid ]
  tolerateMemberNotFoundErrors: false
  tolerateMemberOutOfScopeErrors: false

Verify configuration, connectivity, username, password, etc

$ oc adm groups sync --sync-config tmp/ldap-sync.yml

Create new namespace to store everything

$ oc new-project ldap-group-sync

Modify LDAPSyncConfig and save to /tmp/ldap-group-sync.yaml

...
bindPassword:
  file: "/etc/secrets/bindPassword"
ca: /etc/config/ca.crt
...

$ oc create secret generic ldap-secret --from-literal bindPassword=redhat123 -n ldap-group-sync

$ oc create configmap ldap-config --from-file ldap-group-sync.yaml=/tmp/ldap-group-sync.yaml,ca.crt=/tmp/ca.crt -n ldap-group-sync

kind: ServiceAccount
apiVersion: v1
metadata:
  name: ldap-group-sync-sa
  namespace: ldap-group-sync
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: ldap-group-sync-cr
rules:
  - apiGroups:
      - ''
      - user.openshift.io
    resources:
      - groups
    verbs:
      - get
      - list
      - create
      - update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ldap-group-sync-crb
subjects:
  - kind: ServiceAccount
    name: ldap-group-sync-sa
    namespace: ldap-group-sync
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ldap-group-sync-cr   
---
kind: CronJob
apiVersion: batch/v1beta1
metadata:
  name: ldap-group-sync-cj
  namespace: ldap-group-sync
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        spec:
          containers:
            - name: ldap-group-sync
              image: "registry.redhat.io/openshift4/ose-cli:latest"
              command:
                - "/bin/bash"
                - "-c"
                - "oc adm groups sync --sync-config=/etc/config/ldap-group-sync.yaml --confirm"
              volumeMounts:
                - mountPath: "/etc/config"
                  name: "ldap-sync-volume"
                - mountPath: "/etc/secrets"
                  name: "ldap-bind-password"
          volumes:
            - name: "ldap-sync-volume"
              configMap:
                name: "ldap-config"
            - name: "ldap-bind-password"
              secret:
                secretName: "ldap-secret"
          restartPolicy: "Never"
          terminationGracePeriodSeconds: 30
          activeDeadlineSeconds: 500
          dnsPolicy: "ClusterFirst"
          serviceAccountName: "ldap-group-sync-sa"

$ oc logs pod/ldap-group-sync-...

$ oc get groups

$ oc adm policy add-cluster-role-to-group cluster-admin admins

October 18, 2022

OpenShift 4.6 Automation and Integration: Jenkins

Introduction

A Jenkinsfile is a text file using Groovy syntax, which is very similar to Java and JavaScript.

https://www.jenkins.io/doc/book/pipeline/syntax/

https://www.jenkins.io/doc/book/pipeline/syntax/#scripted-pipeline

There are Two Possible Styles for Writing a Jenkinsfile:

Declarative Pipeline

Pipelines that start with a pipeline directive and define declarative scripts using a special-purpose domain-specific language (DSL) that is a subset of Groovy.

pipeline {
    /* insert Declarative Pipeline here */
}

Scripted Pipeline

Pipelines that start with a node directive and define imperative scripts using the full Groovy programming language.

node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

Declarative Pipeline Example

pipeline {
    agent any
    triggers {
        cron('H */4 * * 1-5')
    } 
    stages {
        stage('Example Build') {
            agent { docker 'maven:3.8.1-adoptopenjdk-11' } 
            steps {
                echo 'Hello, Maven'
                sh 'mvn --version'
            }
        }
        stage('Example Test') {
            agent { docker 'openjdk:8-jre' } 
            steps {
                echo 'Hello, JDK'
                sh 'java -version'
            }
        }
    }
}

The Two Most Common Project Types are:

Pipeline

Runs a pipeline taking as input a single branch from a version control system repository.

Multibranch pipeline

Automatically creates new projects when new branches are detected in a version control system repository. All these projects share the same pipeline definition that must be flexible enough to avoid conflicts between builds in different branches.

Jenkins agent images

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/images/index#images-other-jenkins-agent

Jenkins images are available through the Red Hat Registry:

$ docker pull registry.redhat.io/openshift4/ose-jenkins:<v4.5.0>

$ docker pull registry.redhat.io/openshift4/jenkins-agent-nodejs-10-rhel7:<v4.5.0>

$ docker pull registry.redhat.io/openshift4/jenkins-agent-nodejs-12-rhel7:<v4.5.0>

$ docker pull registry.redhat.io/openshift4/ose-jenkins-agent-maven:<v4.5.0>

$ docker pull registry.redhat.io/openshift4/ose-jenkins-agent-base:<v4.5.0>

Installng Jenkins on OCP

$ oc get templates -A | grep jenkins
openshift   jenkins-ephemeral                               Jenkins service, without persistent storage....
openshift   jenkins-ephemeral-monitored                     Jenkins service, without persistent storage. ...
openshift   jenkins-persistent                              Jenkins service, with persistent storage....
openshift   jenkins-persistent-monitored                    Jenkins service, with persistent storage. ...

$ oc describe -n openshift template jenkins-persistent

$ oc new-project gitops-deploy

$ oc new-app --template jenkins-persistent -p JENKINS_IMAGE_STREAM_TAG=jenkins:v4.8

$ oc adm policy add-cluster-role-to-user self-provisioner -z jenkins -n gitops-deploy

October 17, 2022

OpenShift 4.6 Automation and Integration: Operator

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/operators/index#olm-what-operators-are

$ oc get packagemanifests

$ oc describe packagemanifests file-integrity-operator

$ oc get csv -A

$ oc get subs -A

$ oc describe deployment.apps/file-integrity-operator

$ oc get crd | grep -i fileintegrity

$ oc describe crd fileintegrities.fileintegrity.openshift.io

$ oc get all -n openshift-file-integrity

$ oc logs deployment.apps/file-integrity-operator

Updating an Operator from the OLM Using the CLI

$ oc apply -f file-integrity-operator-subscription.yaml

Deleting Operators

$ oc delete sub $lt;subscription-name>
$ oc delete csv $lt;currentCSV>

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/logging/index#cluster-logging-deploy-cli_cluster-logging-deploying

openshift-file-integrity

apiVersion: v1
kind: Namespace
metadata:
  labels:
    openshift.io/cluster-monitoring: "true"
  name: openshift-file-integrity

apiVersion: operators.coreos.com/v1
kind: OperatorGroup
  metadata:
    name: file-integrity-operator
    namespace: openshift-file-integrity
spec:
  targetNamespaces:
    - openshift-file-integrity
    
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: file-integrity-operator-sub
  namespace: openshift-file-integrity
spec:
  channel: "4.6"
  name: file-integrity-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace    

Cluster Operator

$ oc get clusteroperator

AVAILABLE
The cluster operator is working correctly.

PROGRESSING
The Cluster Version Operator is making changes to this operator.

DEGRADED
The cluster operator has detected a problem and it may not be working correctly.

Cluster Version Operator

$ oc get clusterversion -o jsonpath='{.status.desired.image}' version

Desired release image

$ oc get clusterversion version -o jsonpath='{.status.desired.image}'

$ relimg=$(oc get clusterversion version -o jsonpath='{.status.desired.image}')
$ oc adm release extract --from=$relimg --to=/tmp
$ ll /tmp/*samples*clusteroperator.yaml
-rw-r-----. 1 magnuskkarlsson magnuskkarlsson 778 Apr 21 15:24 /tmp/0000_50_cluster-samples-operator_07-clusteroperator.yaml

OpenShift 4.6 Automation and Integration: Getting Resources Information, Scripts, Rollout, Job, CronJob, Ansible

Getting Resource Information

$ oc get nodes -o wide

$ oc get nodes -o name

$ oc api-resources

$ oc explain route.spec

$ oc get -n openshift-authentication deployment oauth-openshift -o json

$ oc get -n openshift-authentication deployment oauth-openshift -o jsonpath='{.status.availableReplicas}'

$ oc get -n openshift-authentication deployment oauth-openshift -o jsonpath='{.status.conditions[*].type}'

$ oc get -n openshift-authentication deployment oauth-openshift -o jsonpath='{.spec.template.spec.containers[0].name}'

$ oc get -n openshift-authentication deployment oauth-openshift -o jsonpath='{.status.conditions[?(@.type=="Available")].status}'

$ oc get -n openshift-monitoring route -o jsonpath='{.items[*].spec.host}'

$ oc get pods -A -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,IMAGE:.spec.containers[*].name
$ cat not_ready_pods.jsonpath
{range .items[*]}
  {.metadata.name}
  {range .status.conditions[?(@.status=="False")]}
    {.type}{"="}{.status} {.message}
  {end}
{end}

$ oc get nodes -o jsonpath-file=/tmp/not_ready_pods.jsonpath

Labels

$ oc get nodes --show-labels

$ oc get -n openshift-authentication deployment oauth-openshift --show-labels

$ oc get nodes -l node-role.kubernetes.io/worker= -o name

Creating Scripts for Automation

$ oc wait -h
...
Examples:
  # Wait for the pod "busybox1" to contain the status condition of type "Ready"
  oc wait --for=condition=Ready pod/busybox1
  
  # The default value of status condition is true; you can set it to false
  oc wait --for=condition=Ready=false pod/busybox1
  
  # Wait for the pod "busybox1" to contain the status phase to be "Running".
  oc wait --for=jsonpath='{.status.phase}'=Running pod/busybox1
  
  # Wait for the pod "busybox1" to be deleted, with a timeout of 60s, after having issued the "delete" command
  oc delete pod/busybox1
  oc wait --for=delete pod/busybox1 --timeout=60s
...

$ oc rollout status -h
...
Examples:
  # Watch the status of the latest rollout
  oc rollout status dc/nginx
...
$ cat add-user.sh

#!/bin/bash
username=$1
password=$2

echo "$username:$password"

secretname=$(oc get oauth cluster -o jsonpath='{.spec.identityProviders[?(@.name=="htpasswd")].htpasswd.fileData.name}')

secretfile=$(oc extract secret/$secretname -n openshift-config --confirm)

cut -d : -f 1 $secretfile

htpasswd -B -b $secretfile $username $password 

cat $secretfile

oldpods=$(oc get pods -n openshift-authentication -o name)

oc set data secret/$secretname -n openshift-config --from-file=$secretfile

oc wait co/authentication --for condition=Progressing --timeout=90s

oc rollout status -n openshift-authentication deployment oauth-openshift --timeout=90s

oc wait $oldpods -n openshift-authentication --for delete --timeout=90s

rm -f secretfile

ServiceAccount, Role, RoleBinding, Job and CronJob

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/nodes/index#nodes-nodes-jobs

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/authentication_and_authorization/index#ldap-auto-syncing_ldap-syncing-groups

$ oc get pods -A -o jsonpath='{.items[*].spec.containers[*].image}' | sed 's/ /\n/g' | sort | uniq

$ oc new-project audit

$ oc create serviceaccount audit-sa

$ oc create clusterrole audit-cr --verb=get,list,watch --resource=pods

$ oc create clusterrolebinding audit-crb --clusterrole=audit-cr --serviceaccount=audit:audit-sa

apiVersion: batch/v1
kind: Job
metadata:
  name: audit-job
  namespace: audit
spec:
  parallelism: 1
  completions: 1
  activeDeadlineSeconds: 1800
  backoffLimit: 6
  template:
    metadata:
      name: audit-job
    spec:
      serviceAccount: audit-sa
      serviceAccountName: audit-sa
      restartPolicy: "Never"
      containers:
        - name: audit-job
          image: "registry.redhat.io/openshift4/ose-cli:latest"
          command:
            - "/bin/bash"
            - "-c"
            - "oc get pods --all-namespaces -o jsonpath='{.items[*].spec.containers[*].image}' | sed 's/ /\\\n/g' | sort | uniq"
        
$ echo "Hello from OCP $(date +'%F %T')"

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello-cr
  namespace: audit
spec:
  schedule: "*/1 * * * *"  
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        metadata:
          name: "hello-cr"
          labels:
            parent: "hello-cr"
        spec:
          serviceAccount: audit-sa
          serviceAccountName: audit-sa
          restartPolicy: "Never"
          containers:
            - name: hello-cr
              image: "registry.redhat.io/openshift4/ose-cli:latest"
              command:
                - "/bin/bash"
                - "-c"
                - echo "Hello from OCP $(date +'%F %T')"

Ansible Playbooks

$ sudo dnf install -y ansible ansible-collection-community-kubernetes jq

$ pip install openshift

https://docs.ansible.com/ansible/2.9/modules/list_of_clustering_modules.html#k8s

- name: Demo k8s modules
  hosts: localhost
  become: false
  vars:
    namespace: automation-hello
  module_defaults:
    group/k8s:
      namespace: "{{ namespace }}"
      # ca_cert: "/etc/pki/tls/certs/ca-bundle.crt"
      validate_certs: false
  tasks:
    - name: Create project
      k8s:
        api_version: project.openshift.io/v1
        kind: Project
        name: "{{ namespace }}"
        state: present
        namespace: ""

    - name: Create deployment, service and route
      k8s:
        state: present
        src: "/tmp/hello.yaml"

    - name: Get a pod info
      k8s_info:
        kind: Pod

#    - name: Scale deployment
#      k8s_scale:
#        kind: Deployment
#        name: hello
#        replicas: 3

    - name: Get hostname from the route
      k8s_info:
        kind: Route
        name: hello
      register: route

    - name: Test access
      uri:
        url: "http://{{ route.resources[0].spec.host }}"
        return_content: yes
      register: response
      until: response.status == 200
      retries: 10
      delay: 5

    - name: Display response
      debug:
        var: response.content
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hello
  name: hello
  namespace: automation-hello
spec:
  replicas: 1
  selector:
    matchLabels:
      deployment: hello
  template:
    metadata:
      labels:
        deployment: hello
    spec:
      containers:
      - image: quay.io/redhattraining/versioned-hello:v1.0
        name: hello
        ports:
        - containerPort: 8080
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: hello
  name: hello
  namespace: automation-hello
spec:
  ports:
  - name: 8080-tcp
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    deployment: hello
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  labels:
    app: hello
  name: hello
  namespace: automation-hello
spec:
  port:
    targetPort: 8080-tcp
  to:
    kind: Service
    name: hello
$ ansible-playbook /tmp/k8s.yml

October 16, 2022

OpenShift 4.6 Automation and Integration: Kubernetes vs OpenShift, Kustomize and Image Streams

Kubernetes vs OpenShift

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/applications/index#what-deployments-are

Kubernetes OpenShift
Namespace Project
Ingress Route

Deployment

  • Emphasizes availability over consistency.
  • Uses ReplicaSets that support set-based match selectors.
  • Red Hat recommends using Deployments unless you need specific DeploymentConfigs feature.

DeploymentConfig

  • Emphasizes consistency over availability.
Kustomize Template
$ kubectl create -f hello.yml

$ kubectl apply -f hello.yml

$ kubectl get ingresses.v1.networking.k8s.io

Kustomize

A kustomization is a directory containing a kustomization.yml file.

https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - mydeployment.yml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - mydeployment.yaml
images:
  - name: image
    newName: new-image
    newTag: new-tag

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - mydeployment.yaml
secretGenerator:
  - name: mycert
    namespace: openshift-config
    files:
      - tls.crt=my-priv-cert.crt
      - tls.key=my-priv-cert.key
generatorOptions:
  disableNameSuffixHash: true

A kustomization without a bases field is a base.

An overlay includes all resources in its bases.

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - path-to-kustomization

Validate your Kustomize configurations.

$ oc kustomize kustomize_folder

$ oc apply --dry-run -k config

Apply your Kustomize configurations.

$ kubectl apply -k directory_name

Image Streams

Image streams use a unique SHA256 identifier instead of a mutable image tag. This is more robust since image tags (:latest or :v1.1) can change without further notice.

https://access.redhat.com/documentation/en-us/openshift_container_platform/4.6/html-single/images/index#managing-image-streams

Annotating Deployments with Image Stream Triggers

Key: image.openshift.io/triggers
Value:
[
 {
   "from": {
     "kind": "ImageStreamTag",
     "name": "example:latest",
     "namespace": "myapp"
   },
   "fieldPath": "spec.template.spec.containers[?(@.name==\"web\")].image",
   "paused": false
 },
 ...
]
$ skopeo copy \
  docker://quay.io/redhattraining/versioned-hello:v1.0 \
  docker://quay.io/your_account/versioned-hello:latest
  
$ oc get imagestreams

Import image and create image streams and set periodically scheduled (--scheduled) imports to get latest updates.

$ oc import-image quay.io/your_account/versioned-hello:latest --confirm --scheduled

$ oc set triggers deployment/hello --from-image versioned-hello:latest -c hello

September 22, 2022

Java Smart Card Authentication Fails on RHEL 8 with Java 8u261 and SunMSCAPI

Background

In Java 8u261 was MSCAPI completely rewritten [1] and also was support for MS Cryptography next generation (CNG) added [2].

[1] JDK-8213009 Refactoring existing SunMSCAPI classes

[2] JDK-8026953 Add support for MS Cryptography next generation (CNG)

"The CNG API integrates with the smart card subsystem by including a Base Smart Card Cryptographic Service Provider (Base CSP) module which encapsulates the smart card API. Smart card manufacturers just have to make their devices compatible with this, rather than provide a from-scratch solution." https://en.wikipedia.org/wiki/Microsoft_CryptoAPI#Cryptography_API:_Next_Generation

See source code for Java 8 http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/windows/classes/sun/security/mscapi/SunMSCAPI.java

And especially sun.security.mscapi.RSASignature http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/windows/classes/sun/security/mscapi/RSASignature.java

See source code for Java 11 https://github.com/openjdk/jdk11u/tree/master/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi

And especially the new class sun.security.mscapi.CSignature that has replaced sun.security.mscapi.RSASignature https://github.com/openjdk/jdk11u/blob/master/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CSignature.java

See release notes Java 8u261 https://www.oracle.com/java/technologies/javase/8all-relnotes.html#R180_261

security-libs/javax.net.ssl TLS Support for RSASSA-PSS Signature Algorithms

security-libs/javax.net.ssl JEP 332: Transport Layer Security (TLS) 1.3

For Consolidated Release Notes for JDK 8 and JDK 8 Update Releases, see https://www.oracle.com/java/technologies/javase/8all-relnotes.html

Problem

The problem is that most smart card does not support RSASSA-PSS.

See "We are trying to disable RSASSA-PSS, because it is not supported in the JCE PKCS11 wrapper, and causes errors when setting up TLS1.2 errors." JDK-8226374 Restrict TLS signature schemes and named groups

And also "was unsupported by the open source smart card driver OpenSC, as well as an overall industry-wide problematic treatment of certificates with RSASSA-PSS. The issue with RSASSA-PSS in certificates was quite fundamental due to their unique and complex design, and was ultimately addressed by the TLS working group by making the RSASSA-PSS in certificates optional." RED HAT BLOG Transport Layer Security version 1.3 in Red Hat Enterprise Linux 8

The stacktrace from a Java client connecting with SunMSCAPI

$ java -Djavax.net.ssl.keyStore=NONE \
-Djavax.net.ssl.keyStoreType=Windows-MY \
-Djavax.net.ssl.keyStoreProvider=SunMSCAPI \
-Djavax.net.ssl.trustStore=NONE \
-Djavax.net.ssl.trustStoreType=Windows-ROOT \
-Djavax.net.ssl.trustStoreProvider=SunMSCAPI \
se.mkk.Main
...
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Cannot produce CertificateVerify signature
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:353)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:296)
        at java.base/sun.security.ssl.CertificateVerify$T12CertificateVerifyMessage.<init>(CertificateVerify.java:611)
        at java.base/sun.security.ssl.CertificateVerify$T12CertificateVerifyProducer.produce(CertificateVerify.java:761)
        at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:436)
        at java.base/sun.security.ssl.ServerHelloDone$ServerHelloDoneConsumer.consume(ServerHelloDone.java:182)
        at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
        at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:183)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
        at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)
        at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1416)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:456)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:427)
        at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:572)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:201)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1592)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
        at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:527)
        at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:334)
        at se.mkk.HttpURLConnectionBuilder.send(HttpURLConnectionBuilder.java:116)
        at se.mkk.Main.getHttpURLConnection(Main.java:54)
        at se.mkk.Main.httpURLConnection(Main.java:44)
        at se.mkk.Main.main(Main.java:18)
Caused by: java.security.SignatureException: Unknown error
        at jdk.crypto.mscapi/sun.security.mscapi.CSignature.signCngHash(Native Method)
        at jdk.crypto.mscapi/sun.security.mscapi.CSignature$PSS.engineSign(CSignature.java:607)
        at java.base/java.security.Signature$Delegate.engineSign(Signature.java:1404)
        at java.base/java.security.Signature.sign(Signature.java:713)
        at java.base/sun.security.ssl.CertificateVerify$T12CertificateVerifyMessage.<init>(CertificateVerify.java:609)

One way to get around this is to disable RSASSA-PSS, but this only works for > Java 11, due to that above bug JDK-8226374 is not backpoarted to Java 8.

%JAVA_HOME%\conf\security\java.security
…
jdk.tls.disabledAlgorithms=…, RSASSA-PSS
…

Other References

Sean Mullan Technical Lead of the Java Security Libraries Team at Oracle

Additional information on Oracle's JDK and JRE Cryptographic Algorithms