Grizzly with TLS deprecated

This guide explains how to enable HTTPS in addition to HTTP for Grizzly. The guide follows the assumption that your deployment has been set up with the OX App Suite Stack Chart, and you are running Istio.

Istio

Any given request to an Istio gateway will have two connections.

Gateway Connections

  1. The inbound request, initiated by some client such as OX App Suite UI. This is often called the downstream connection.
  2. The outbound request, initiated by the gateway to some backend. This is often called the upstream connection.

Both of these connections have independent TLS configurations.¹ Usually, securing the inbound traffic is sufficient. TLS will be terminated at the Gateway, and the outbound traffic will be sent upstream unencrypted. For enhanced security, it is possible to encrypt the upstream traffic too.

This guide will describe how to secure the upstream traffic to Grizzly.


References:

¹ Istio Documentation - Gateway TLS Configuration

Outbound Encryption

Outbound encryption is configured by the TLS settings of DestinationRules. The OX App Suite Stack Chart configures DestinationRules for all core-mw services that are accessible by end-users. Those services are:

  • <RELEASE>-core-mw-http-api
  • <RELEASE>-core-mw-sync
  • <RELEASE>-core-mw-businessmobility

In order to enable TLS for outbound connections, set the TLS mode to SIMPLE. Moreover, changing the destination port of the VirtualServices to 443 is helpful to emphasize that TLS is being used:

appsuite:
  core-mw:
    istio:
      virtualServices:
        destinationPort: 443
  istio:
    destinationRules:
      appsuite:
        tls:
          mode: SIMPLE
      dav:
        tls:
          mode: SIMPLE
      businessmobility:
        tls:
          mode: SIMPLE

Certificates

After enabling outbound encryption, we need to create certificates for all services. This section will guide you through the process.

Prerequisites

Create Certificates

The following script generates RSA 4096-bit key pairs with self-signed certificates for each service. The key pairs and certificates are stored within a password-protected PKCS#12 keystore, which will be used by the open-xchange daemon. Ensure that the release name, namespace, and services match your deployment. Additionally, you can adjust parameters such as algorithm, key strength, validity, and keystore password as desired.

#!/bin/bash -xe

release=example-release
namespace=example-namespace

services=(
  ${release}-core-mw-http-api
  ${release}-core-mw-sync
  ${release}-core-mw-admin
  ${release}-core-mw-businessmobility
  ${release}-core-mw-request-analyzer
)

# Create a certificate authority
cat <<EOF | cfssl gencert -initca - | cfssljson -bare ca
{
  "CN": "Open-Xchange Certificate Authority",
  "key": {
    "algo": "rsa",
    "size": 4096
  }
}
EOF

# Create signing configuration
cat > signing-config.json<< EOF
{
    "signing": {
        "default": {
            "usages": [
                "digital signature",
                "key encipherment",
                "server auth"
            ],
            "expiry": "876000h",
            "ca_constraint": {
                "is_ca": false
            }
        }
    }
}
EOF

for service in ${services[@]}; do

  # Delete existing signing requests
  kubectl delete CertificateSigningRequest ${service} --ignore-not-found

  # Generate a private key and certificate signing request
  cat <<EOF | cfssl genkey - | cfssljson -bare ${service}
{
  "hosts": [
    "${service}.${namespace}.svc.cluster.local"
  ],
  "CN": "${service}.${namespace}.svc.cluster.local",
  "key": {
    "algo": "ecdsa",
    "size": 256
  }
}
EOF

  # Generate a CSR manifest and send it to the API server
  cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: ${service}
spec:
  request: $(cat ${service}.csr | base64 | tr -d '\n')
  signerName: open-xchange.com/signer
  usages:
  - digital signature
  - key encipherment
  - server auth
EOF

  # Approve the certificate signing request
  kubectl certificate approve ${service}

  # Sign the certificate request
  kubectl get csr ${service} -o jsonpath='{.spec.request}' | \
    base64 --decode | \
    cfssl sign -ca ca.pem -ca-key ca-key.pem -config signing-config.json - | \
    cfssljson -bare ca-signed-${service}

  # Upload the signed certificate
  kubectl get csr ${service} -o json | \
    jq '.status.certificate = "'$(base64 ca-signed-${service}.pem | tr -d '\n')'"' | \
    kubectl replace --raw "/apis/certificates.k8s.io/v1/certificatesigningrequests/${service}/status" -f -

  # Download the issued certificate and save it to a ${service}.crt file
  kubectl get csr ${service} -o jsonpath='{.status.certificate}' \
      | base64 --decode > ${service}.crt

  # Create PKCS12 keystore file for the service certificate and key
  openssl pkcs12 -export -out ${service}.p12 -inkey ${service}-key.pem -in ${service}.crt -name ${service}.${namespace}.svc.cluster.local -passout pass:secret

  # Import key and certificate to the final Java keystore
  keytool -importkeystore -srckeystore ${service}.p12 -srcstoretype PKCS12 -srcalias ${service}.${namespace}.svc.cluster.local -srcstorepass secret -destkeystore grizzly.jks -deststoretype PKCS12 -deststorepass secret -noprompt

done

For more information about managing TLS in a cluster, please refer to the Kubernetes documentation

Grizzly Configuration

Grizzly can be configured via the core-mw Helm chart, which is part of the OX App Suite Stack Chart.

appsuite:
  core-mw:
    properties:
      com.openexchange.http.grizzly.hasSSLEnabled: "true"
      com.openexchange.http.grizzly.keystorePath: "/opt/open-xchange/etc/grizzly.jks"
      com.openexchange.http.grizzly.keystorePassword: "secret"
    etcBinaries:
      - name: grizzly
        filename: grizzly.jks
        b64Content: <YOUR_BASE64_ENCODED_KEYSTORE>

This configuration makes Grizzly listen on port 8010 in addition to 8009, with 8010 accepting HTTPS connections only.

To expose port 8010 for services, we need to update the container ports:

appsuite:
  core-mw:
    containerPorts:
      - containerPort: 8009
        name: http
      - containerPort: 8010
        name: https

After that, we can modify the services created by the core-mw chart. The services must accept the traffic on port 443 to match the destination port of the VirtualServices. The target port needs to be set to 8010 to match the exposed container port.

appsuite:
  core-mw:
    roles:
      http-api:
        services:
          - type: ClusterIP
            ports:
              - port: 443
                targetPort: 8010
                protocol: TCP
                name: https
      sync:
        services:
          - type: ClusterIP
            ports:
              - port: 443
                targetPort: 8010
                protocol: TCP
                name: https
      admin:
        services:
          - type: ClusterIP
            ports:
              - port: 443
                targetPort: 8010
                protocol: TCP
                name: https
      businessmobility:
        services:
          - type: ClusterIP
            ports:
              - port: 443
                targetPort: 8010
                protocol: TCP
                name: https
        values:
          features:
            status:
              usm-eas: enabled
          properties:
            com.openexchange.usm.ox.url: http://localhost:8009/appsuite/api/
      request-analyzer:
        services:
          - type: ClusterIP
            ports:
              - port: 443
                targetPort: 8010
                protocol: TCP
                name: https

Optionally, you can adjust the readiness and startup probes to the secure port:

appsuite:
  core-mw:
    probe:
      readiness:
        httpGet:
          scheme: HTTPS
          port: 8010
      startup:
        httpGet:
          scheme: HTTPS
          port: 8010