diff --git a/examples/fabric-ca/ica-orderer-certmanager.yaml b/examples/fabric-ca/ica-orderer-certmanager.yaml new file mode 100644 index 0000000..315fe4b --- /dev/null +++ b/examples/fabric-ca/ica-orderer-certmanager.yaml @@ -0,0 +1,97 @@ +# Example values for deploying Fabric Intermediate CA with cert-manager integration. +# +# Prerequisites: +# 1. cert-manager must be installed in the cluster +# 2. A ClusterIssuer or Issuer must be configured +# 3. The root-ca must be deployed first (with cert-manager if using trustCA) +# +# When trustCA.enabled is true: +# - The root CA's certificate is mounted from a Kubernetes Secret into the init container +# - The init container uses --cacert instead of --insecure to verify the parent CA +# - The secretName should reference a Secret containing the root CA's public certificate +# under the key "ca.crt" (or "tls.crt") +# +# To create the trusted CA secret from the root-ca's certificate: +# kubectl -n orderer create secret generic root-ca-cert \ +# --from-file=ca.crt=<(kubectl -n orderer get secret root-ca-tls-secret -o jsonpath='{.data.ca\.crt}' | base64 -d) + +imagePullSecrets: [] +nameOverride: "ica-orderer" +fullnameOverride: "" +project: yourproject + +replicaCount: 1 + +image: + repository: hyperledger/fabric-ca + pullPolicy: IfNotPresent + tag: "1.5.0" + +init: + image: + repository: npcioss/hlf-builder + pullPolicy: IfNotPresent + tag: "2.4" + +tls_domain: my-hlf-domain.com +ca_server: + csr_names_country: IN + csr_names_st: Maharashtra + csr_names_l: Mumbai + csr_names_o: Your Company Name + csr_names_ou: Your Organization Unit + container_port: 7051 + debug: true + tls_enabled: true + admin_secret: orderer-secret + +retry_seconds: 60 +recreate_intermediate_cert: false + +ica: + enabled: true + parent_ca_endpoint: root-ca.my-hlf-domain.com:30000 + intermediate_tls_cert_dir: /tmp/hyperledger/fabric-ca/root-ca-cert + intermediate_tls_cert_file: cert.pem + +service: + type: ClusterIP + port: 7051 + +ingress: + enabled: true + className: "nginx" + annotations: {} + +# cert-manager integration for ingress TLS. +certManager: + enabled: true + duration: 2160h + renewBefore: 360h + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io + # trustCA: Mount the parent CA's certificate so the ICA init container + # can verify the parent CA without --insecure. + trustCA: + enabled: true + # Name of a Kubernetes Secret containing the parent CA's certificate. + # The certificate should be stored under the key "ca.crt". + # If empty and trustCA is enabled, you must create this secret manually. + secretName: root-ca-cert + caCertPath: /etc/ssl/certs/ca-cert.crt + private: + enabled: false + +storage: + pvc_enabled: true + accessMode: ReadWriteOnce + storageClass: standard + size: 1G + path: /tmp/hyperledger/fabric-ca/crypto + +serviceAccount: + create: true + annotations: {} + name: "" \ No newline at end of file diff --git a/examples/fabric-ca/root-ca-certmanager.yaml b/examples/fabric-ca/root-ca-certmanager.yaml new file mode 100644 index 0000000..2914876 --- /dev/null +++ b/examples/fabric-ca/root-ca-certmanager.yaml @@ -0,0 +1,102 @@ +# Example values for deploying Fabric Root CA with cert-manager integration. +# +# Prerequisites: +# 1. cert-manager must be installed in the cluster: https://cert-manager.io/docs/installation/ +# 2. A ClusterIssuer or Issuer must be configured (see examples below) +# +# ClusterIssuer example (Let's Encrypt): +# apiVersion: cert-manager.io/v1 +# kind: ClusterIssuer +# metadata: +# name: letsencrypt-prod +# spec: +# acme: +# server: https://acme-v02.api.letsencrypt.org/directory +# email: your-email@example.com +# privateKeySecretRef: +# name: letsencrypt-prod +# solvers: +# - http01: +# ingress: +# class: nginx +# +# ClusterIssuer example (self-signed, for testing): +# apiVersion: cert-manager.io/v1 +# kind: ClusterIssuer +# metadata: +# name: selfsigned-issuer +# spec: +# selfSigned: {} + +imagePullSecrets: [] +nameOverride: "root-ca" +fullnameOverride: "" +project: yourproject + +replicaCount: 1 + +image: + repository: hyperledger/fabric-ca + pullPolicy: IfNotPresent + tag: "1.5.0" + +tls_domain: my-hlf-domain.com +ca_server: + csr_names_country: IN + csr_names_st: Maharashtra + csr_names_l: Mumbai + csr_names_o: "Your Company Name" + csr_names_ou: "Your Organization Unit" + container_port: 7051 + debug: true + tls_enabled: true + admin_secret: rca-secret + additional_sans: [] + +ica: + enabled: false + +service: + type: ClusterIP + port: 7051 + +ingress: + enabled: true + className: "nginx" + annotations: {} + path: / + pathType: Prefix + +# cert-manager integration for ingress TLS. +# When enabled, this creates a Certificate CRD that provisions a TLS certificate +# for the CA ingress. The ingress will terminate TLS using the cert-manager-issued +# certificate instead of using ssl-passthrough. +certManager: + enabled: true + duration: 2160h + renewBefore: 360h + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io + # trustCA: When enabled, the parent CA's certificate is mounted as a trusted CA + # in the init container, allowing the ICA to verify the parent CA without --insecure. + # Only relevant when ica.enabled is also true. + trustCA: + enabled: false + secretName: "" + caCertPath: /etc/ssl/certs/ca-cert.crt + private: + enabled: false + +storage: + pvc_enabled: true + accessMode: ReadWriteOnce + storageClass: standard + size: 1G + path: /tmp/hyperledger/fabric-ca/crypto + +serviceAccount: + create: true + annotations: {} + name: "" \ No newline at end of file diff --git a/helm-charts/fabric-ca/Chart.yaml b/helm-charts/fabric-ca/Chart.yaml index 1d09dfe..230fbca 100644 --- a/helm-charts/fabric-ca/Chart.yaml +++ b/helm-charts/fabric-ca/Chart.yaml @@ -4,5 +4,5 @@ apiVersion: v2 name: fabric-ca description: A Helm chart for deploying Fabric CA Server in Kubernetes. type: application -version: 1.2.0 +version: 1.3.0 appVersion: "1.5.0" diff --git a/helm-charts/fabric-ca/README.md b/helm-charts/fabric-ca/README.md index 6b1d032..b53f6f5 100644 --- a/helm-charts/fabric-ca/README.md +++ b/helm-charts/fabric-ca/README.md @@ -99,3 +99,124 @@ The following table lists the configurable parameters of the Fabric-ca chart and | `affinity` | Default affinity | `{}` | | `nodeSelector` | Default nodeSelector | `{}` | | `tolerations` | Default tolerations | `[]` | +| `certManager.enabled` | Enable cert-manager integration for ingress TLS | `false` | +| `certManager.duration` | Certificate duration (e.g., `2160h` for 90 days) | `2160h` | +| `certManager.renewBefore` | How long before expiry to renew | `360h` | +| `certManager.issuerRef.name` | Name of the cert-manager Issuer or ClusterIssuer | `""` | +| `certManager.issuerRef.kind` | Kind of issuer (`Issuer` or `ClusterIssuer`) | `"Issuer"` | +| `certManager.issuerRef.group` | API group of the issuer | `"cert-manager.io"` | +| `certManager.trustCA.enabled` | Mount parent CA cert for verified ICA enrollment | `false` | +| `certManager.trustCA.secretName` | K8s Secret containing the trusted CA certificate | `""` | +| `certManager.trustCA.caCertPath` | Path where the CA cert is mounted in the pod | `"/etc/ssl/certs/ca-cert.crt"` | +| `certManager.private.enabled` | Use a private CA issuer (non-ACME) | `false` | + +## cert-manager Integration + +This chart supports [cert-manager](https://cert-manager.io/) for provisioning TLS certificates at the ingress layer. When enabled, cert-manager replaces the `ssl-passthrough` pattern with standard TLS termination. + +### Architecture + +When `certManager.enabled: true`, the chart creates a `Certificate` CRD that instructs cert-manager to provision a TLS certificate. The ingress terminates TLS using this certificate, and forwards traffic to the Fabric CA pod over HTTPS (the CA pod still uses its own self-signed Fabric TLS certificate internally). + +``` +External Clients + | + v ++----------------------------------+ +| NGINX Ingress Controller | +| (cert-manager TLS termination) | ++----------------------------------+ + | (backend-protocol: HTTPS) + v ++----------------------------------+ +| Fabric CA Pod | +| (Fabric CA self-signed TLS) | ++----------------------------------+ +``` + +**Important:** cert-manager only manages the ingress-facing TLS certificate. Fabric CA's internal TLS (`FABRIC_CA_SERVER_TLS_ENABLED`) and the Fabric PKI hierarchy (root CA, intermediate CA, enrollment certificates) remain unchanged. + +### Prerequisites + +1. Install cert-manager in your cluster: https://cert-manager.io/docs/installation/ +2. Create an Issuer or ClusterIssuer. Examples: + +**Let's Encrypt (ACME HTTP01):** +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: your-email@example.com + privateKeySecretRef: + name: letsencrypt-prod + solvers: + - http01: + ingress: + class: nginx +``` + +**Self-signed (for testing):** +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +``` + +**Private CA (e.g., Vault PKI, Smallstep):** +```yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: private-ca-issuer +spec: + ca: + secretName: private-ca-key-pair +``` + +### Deploying with cert-manager (Root CA) + +```bash +kubectl create ns orderer +kubectl -n orderer create secret generic rca-secret --from-literal=user=rca-admin --from-literal=password=rcaComplexPassword +helm install root-ca -n orderer helm-charts/fabric-ca -f examples/fabric-ca/root-ca-certmanager.yaml +``` + +### Deploying with cert-manager (Intermediate CA with trusted CA) + +When deploying an ICA with `certManager.trustCA.enabled: true`, the init container verifies the parent CA's certificate instead of using `--insecure`: + +1. First, extract the root CA's certificate and create a Secret: +```bash +kubectl -n orderer create secret generic root-ca-cert \ + --from-literal=ca.crt="$(kubectl -n orderer exec root-ca-0 -- cat /tmp/hyperledger/fabric-ca/crypto/ca-cert.pem)" +``` + +2. Then deploy the ICA: +```bash +helm install ica-orderer -n orderer helm-charts/fabric-ca -f examples/fabric-ca/ica-orderer-certmanager.yaml +``` + +### Migration from ssl-passthrough + +To migrate an existing CA deployment from `ssl-passthrough` to cert-manager: + +1. Install cert-manager and create an Issuer/ClusterIssuer +2. Update your values to set `certManager.enabled: true` and configure the issuer +3. Remove `ssl-passthrough` from ingress annotations (it's automatically removed when cert-manager is enabled) +4. Run `helm upgrade` + +The chart automatically adds `nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"` when cert-manager is enabled, ensuring the ingress forwards traffic to the CA pod over HTTPS. + +### Backward Compatibility + +When `certManager.enabled: false` (the default), the chart behaves exactly as before: +- The ingress uses `ssl-passthrough` if specified in annotations +- The ICA init container uses `--insecure` to fetch parent CA certificates +- No Certificate CRD is created diff --git a/helm-charts/fabric-ca/templates/certificate.yaml b/helm-charts/fabric-ca/templates/certificate.yaml new file mode 100644 index 0000000..0c8fd3f --- /dev/null +++ b/helm-charts/fabric-ca/templates/certificate.yaml @@ -0,0 +1,35 @@ +{{/* +Copyright National Payments Corporation of India. All Rights Reserved. +SPDX-License-Identifier: GPL-3.0 +*/}} + +{{- if .Values.certManager.enabled -}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "fabric-ca.fullname" . }}-tls + labels: + {{- include "fabric-ca.labels" . | nindent 4 }} +spec: + secretName: {{ include "fabric-ca.fullname" . }}-tls-secret + duration: {{ .Values.certManager.duration | default "2160h" }} + renewBefore: {{ .Values.certManager.renewBefore | default "360h" }} + {{- with .Values.certManager.issuerRef }} + issuerRef: + {{- toYaml . | nindent 4 }} + {{- end }} + dnsNames: + - {{ include "fabric-ca.fullname" . }}.{{ $.Values.tls_domain }} + {{- range .Values.ca_server.additional_sans }} + - {{ . }} + {{- end }} + usages: + - server auth + - client auth + {{- if .Values.certManager.private.enabled }} + isCA: false + privateKey: + algorithm: RSA + size: 2048 + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm-charts/fabric-ca/templates/configmap.yaml b/helm-charts/fabric-ca/templates/configmap.yaml index 395a1d2..a96d1f9 100644 --- a/helm-charts/fabric-ca/templates/configmap.yaml +++ b/helm-charts/fabric-ca/templates/configmap.yaml @@ -5,6 +5,8 @@ SPDX-License-Identifier: GPL-3.0 {{- if .Values.ica.enabled -}} {{- $ParentCaEndpoint := .Values.ica.parent_ca_endpoint -}} +{{- $trustCAEnabled := and .Values.certManager.enabled .Values.certManager.trustCA.enabled -}} +{{- $caCertPath := .Values.certManager.trustCA.caCertPath | default "/etc/ssl/certs/ca-cert.crt" -}} apiVersion: v1 kind: ConfigMap @@ -15,19 +17,25 @@ metadata: data: init.sh: | FABRIC_TLS_CERT_FILE=$4 + {{- if $trustCAEnabled }} + CURL_VERIFY_FLAG="--cacert {{ $caCertPath }}" + {{- else }} + CURL_VERIFY_FLAG="--insecure" + {{- end }} + function fabric_public_key_fetch() { FABRIC_CA_URL=$1 FABRIC_TLS_CERT_FILE=$2 while true; do - http_response=$(curl -sL -w "%{http_code}" "https://$FABRIC_CA_URL/cainfo" --insecure -o /dev/null) + http_response=$(curl -sL -w "%{http_code}" "https://$FABRIC_CA_URL/cainfo" $CURL_VERIFY_FLAG -o /dev/null) if [ "$http_response" -eq "200" ]; then echo "--------------------------------" echo "Fetching public key cert of $FABRIC_CA_URL, received HTTP response with 200." echo "--------------------------------" - curl https://$FABRIC_CA_URL/cainfo --insecure | jq .result.CAChain | base64 -i -d > $FABRIC_TLS_CERT_FILE - if curl https://$FABRIC_CA_URL/cainfo --insecure | jq .result.CAChain | base64 -i -d > $FABRIC_TLS_CERT_FILE; then + curl https://$FABRIC_CA_URL/cainfo $CURL_VERIFY_FLAG | jq .result.CAChain | base64 -i -d > $FABRIC_TLS_CERT_FILE + if curl https://$FABRIC_CA_URL/cainfo $CURL_VERIFY_FLAG | jq .result.CAChain | base64 -i -d > $FABRIC_TLS_CERT_FILE; then echo "--------------------------------" echo "The downloaded public key cert of $FABRIC_CA_URL" echo "--------------------------------" diff --git a/helm-charts/fabric-ca/templates/deployment.yaml b/helm-charts/fabric-ca/templates/deployment.yaml index 4138c4d..100e22e 100644 --- a/helm-charts/fabric-ca/templates/deployment.yaml +++ b/helm-charts/fabric-ca/templates/deployment.yaml @@ -4,6 +4,9 @@ SPDX-License-Identifier: GPL-3.0 */}} {{- $ParentCaEndpoint := .Values.ica.parent_ca_endpoint -}} +{{- $trustCAEnabled := and .Values.certManager.enabled .Values.certManager.trustCA.enabled -}} +{{- $trustCASecretName := .Values.certManager.trustCA.secretName -}} +{{- $caCertPath := .Values.certManager.trustCA.caCertPath | default "/etc/ssl/certs/ca-cert.crt" -}} apiVersion: apps/v1 kind: Deployment @@ -59,6 +62,12 @@ spec: mountPath: /scripts/init.sh - name: intermediate-ca-cert mountPath: {{ .Values.ica.intermediate_tls_cert_dir }} + {{- if $trustCAEnabled }} + - name: trusted-ca-cert + mountPath: {{ $caCertPath }} + subPath: ca.crt + readOnly: true + {{- end }} {{- end }} containers: - name: {{ .Chart.Name }} @@ -114,6 +123,12 @@ spec: name: {{ include "fabric-ca.fullname" . }}-scripts defaultMode: 0777 {{- end }} + {{- if $trustCAEnabled }} + - name: trusted-ca-cert + secret: + secretName: {{ $trustCASecretName }} + defaultMode: 0644 + {{- end }} restartPolicy: {{ .Values.restartPolicy }} {{- with .Values.nodeSelector }} nodeSelector: @@ -126,4 +141,4 @@ spec: {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} \ No newline at end of file diff --git a/helm-charts/fabric-ca/templates/ingress.yaml b/helm-charts/fabric-ca/templates/ingress.yaml index 6817ac8..84ae08d 100644 --- a/helm-charts/fabric-ca/templates/ingress.yaml +++ b/helm-charts/fabric-ca/templates/ingress.yaml @@ -24,16 +24,26 @@ metadata: name: {{ $fullName }} labels: {{- include "fabric-ca.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} annotations: + {{- if .Values.certManager.enabled }} + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + {{- end }} + {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} - {{- end }} + {{- end }} spec: {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} ingressClassName: {{ .Values.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} tls: + {{- if .Values.certManager.enabled }} + - hosts: + - {{ include "fabric-ca.fullname" . }}.{{ $.Values.tls_domain }} + {{- range .Values.ca_server.additional_sans }} + - {{ . }} + {{- end }} + secretName: {{ include "fabric-ca.fullname" . }}-tls-secret + {{- else if .Values.ingress.tls }} {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} @@ -41,7 +51,7 @@ spec: {{- end }} secretName: {{ .secretName }} {{- end }} - {{- end }} + {{- end }} rules: - host: {{ include "fabric-ca.fullname" $ }}.{{ $.Values.tls_domain }} http: @@ -76,5 +86,5 @@ spec: servicePort: {{ $svcPort }} {{- end }} {{- end }} - {{- end }} -{{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm-charts/fabric-ca/values.yaml b/helm-charts/fabric-ca/values.yaml index 07f0b62..b42709f 100644 --- a/helm-charts/fabric-ca/values.yaml +++ b/helm-charts/fabric-ca/values.yaml @@ -96,6 +96,33 @@ readinessProbe: port: http scheme: HTTPS +# cert-manager integration for ingress TLS. +# When enabled, a Certificate CRD is created to provision a TLS certificate +# for the CA ingress. The ingress terminates TLS using the cert-manager-issued +# certificate and forwards traffic to the CA pod over HTTPS (backend-protocol). +# +# IMPORTANT: When certManager.enabled is true, do NOT set ssl-passthrough in +# ingress.annotations. ssl-passthrough and backend-protocol: HTTPS are mutually +# exclusive. The chart automatically adds backend-protocol: HTTPS when cert-manager +# is enabled. +# +# Fabric CA's own TLS (FABRIC_CA_SERVER_TLS_ENABLED) remains unchanged. cert-manager +# only manages the ingress-facing certificate, not Fabric's internal PKI. +certManager: + enabled: false + duration: 2160h + renewBefore: 360h + issuerRef: + name: "" + kind: Issuer + group: cert-manager.io + trustCA: + enabled: false + secretName: "" + caCertPath: /etc/ssl/certs/ca-cert.crt + private: + enabled: false + podAnnotations: {} podSecurityContext: {} securityContext: {}