From 631187afdfd11188ea1bf6d19eb842d71f41bb73 Mon Sep 17 00:00:00 2001 From: Tareque Hossain Date: Thu, 9 Jul 2026 18:38:11 -0700 Subject: [PATCH 1/3] chore(rest-api): Add ingress based TLS support for REST API --- .../installation-options/reference-install.md | 2 +- docs/getting-started/quick-start.md | 37 +++++ helm-prereqs/README.md | 4 +- helm-prereqs/clean.sh | 21 ++- helm-prereqs/health-check.sh | 13 ++ helm-prereqs/helmfile.yaml | 17 +++ .../operators/values/ingress-nginx.yaml | 23 +++ helm-prereqs/setup.sh | 29 +++- helm-prereqs/values/nico-rest.yaml | 19 +++ .../templates/ingress-certificate.yaml | 46 ++++++ .../nico-rest-api/templates/ingress.yaml | 39 ++++++ .../nico-rest-api/tests/ingress_test.yaml | 132 ++++++++++++++++++ .../charts/nico-rest-api/values.yaml | 50 +++++++ 13 files changed, 423 insertions(+), 9 deletions(-) create mode 100644 helm-prereqs/operators/values/ingress-nginx.yaml create mode 100644 helm/rest/nico-rest/charts/nico-rest-api/templates/ingress-certificate.yaml create mode 100644 helm/rest/nico-rest/charts/nico-rest-api/templates/ingress.yaml create mode 100644 helm/rest/nico-rest/charts/nico-rest-api/tests/ingress_test.yaml diff --git a/docs/getting-started/installation-options/reference-install.md b/docs/getting-started/installation-options/reference-install.md index 99ee1e3d2b..5106377ec2 100644 --- a/docs/getting-started/installation-options/reference-install.md +++ b/docs/getting-started/installation-options/reference-install.md @@ -12,7 +12,7 @@ This page collects the maintained, manifest-level references for operators who n `setup.sh` is a thin wrapper over the Helm charts and kustomize manifests in the repository. The source-of-truth guides document each step, the order of operations, the PKI and secrets model, and troubleshooting: -- **Prerequisites and NICo Core** — [`helm-prereqs/README.md`](https://github.com/NVIDIA/infra-controller/blob/main/helm-prereqs/README.md) covers the prerequisite stack (local-path-provisioner, postgres-operator, MetalLB, cert-manager, Vault, External Secrets), the NICo Core deployment, the per-site values files, the `.forge` compatibility DNS, and the `health-check.sh` verification. +- **Prerequisites and NICo Core** — [`helm-prereqs/README.md`](https://github.com/NVIDIA/infra-controller/blob/main/helm-prereqs/README.md) covers the prerequisite stack (local-path-provisioner, postgres-operator, MetalLB, optional ingress-nginx, cert-manager, Vault, External Secrets), the NICo Core deployment, the per-site values files, the `.forge` compatibility DNS, and the `health-check.sh` verification. - **NICo REST** — [`rest-api/deploy/INSTALLATION.md`](https://github.com/NVIDIA/infra-controller/blob/main/rest-api/deploy/INSTALLATION.md) is the prescriptive, manifest-by-manifest bring-up for the REST control plane (PostgreSQL, Keycloak, Temporal, the internal cert-manager, site-manager, API, workflow workers, and site-agent). > **NICo REST is in-tree.** The REST stack lives in this repository under `rest-api/`; it is no longer a separate repository. `setup.sh` resolves it automatically, so no `NCX_REPO` clone is required. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index c0302133e4..c8ae97be92 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -193,6 +193,41 @@ nico-rest-api: When `keycloak.enabled: false`, the Keycloak deployment is still created by `setup.sh`, but `nico-rest-api` will not use it for token validation. +#### Optional: Expose NICo REST over TLS with Ingress + +By default, `setup.sh` exposes `nico-rest-api` through a NodePort in `helm-prereqs/values/nico-rest.yaml`. To expose it through a fully qualified domain name with TLS, enable the `nico-rest-api` ingress and configure its certificate settings in that file: + +```yaml +nico-rest-api: + ingress: + enabled: true + className: nginx + hosts: + - host: rest-api.mysite.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: rest-api-mysite-example-com-tls + hosts: + - rest-api.mysite.example.com + certificate: + enabled: true + secretName: rest-api-mysite-example-com-tls + commonName: rest-api.mysite.example.com +``` + +The chart creates a cert-manager `Certificate` for the ingress TLS Secret when `ingress.certificate.enabled: true`. The certificate is issued by the REST stack's `nico-rest-ca-issuer`, so this is a quick self-signed/private-CA setup suitable for lab and site-local deployments. If your cluster already has a TLS Secret for the domain, set `ingress.certificate.enabled: false` and keep `ingress.tls[].secretName` pointed at that existing Secret. + +The ingress routes to the chart-managed `nico-rest-api` Service on `service.port`; the API pod still serves plain HTTP internally and does not need TLS-specific configuration. If your cluster does not already have an ingress controller, run setup with the optional nginx controller: + +```bash +./setup.sh --install-ingress-nginx +# or +export NICO_INSTALL_INGRESS_NGINX=true +./setup.sh -y +``` + ### 3f. Review site-agent Config The defaults in `helm-prereqs/values/nico-site-agent.yaml` point at the Zalando-managed `nico-pg-cluster` (`DB_ADDR: nico-pg-cluster.postgres.svc.cluster.local`, `DB_DATABASE: nico_rest`), which is the same cluster used by `nico-rest-api`. No changes are needed for a standard deployment. @@ -313,6 +348,7 @@ The `setup.sh` script installs all prerequisites and NICo components in sequenti | 1 | local-path-provisioner + StorageClasses | | 1b | postgres-operator (Zalando) | | 1c | MetalLB + site BGP/L2 config | +| 1d | Optional ingress-nginx controller (`--install-ingress-nginx`) | | 2 | cert-manager + Vault TLS bootstrap (PKI chain) | | 3 | HashiCorp Vault (3-node HA Raft) | | 4 | Vault init + unseal + SSH host key | @@ -325,6 +361,7 @@ The following components are deployed: ``` local-path-provisioner (raw manifest - StorageClasses for Vault + PostgreSQL PVCs) metallb (metallb/metallb 0.14.5 - LoadBalancer IPs via BGP or L2) +ingress-nginx (optional - Ingress controller for REST API FQDN/TLS) postgres-operator (zalando/postgres-operator 1.10.1 - manages nico-pg-cluster) cert-manager (jetstack/cert-manager v1.17.1) vault (hashicorp/vault 0.25.0, 3-node HA Raft, TLS) diff --git a/helm-prereqs/README.md b/helm-prereqs/README.md index 512721144a..08999c463e 100644 --- a/helm-prereqs/README.md +++ b/helm-prereqs/README.md @@ -43,7 +43,7 @@ helm-prereqs/ │ ├── nico-site-agent.yaml # Site-agent deployment values (DB config, gRPC settings) │ └── metallb-config.yaml # MetalLB IP pools, BGP peers, and advertisements ├── templates/ # nico-prereqs Helm chart templates (PKI, ESO, PostgreSQL) -├── operators/ # Raw manifests and operator values (local-path, MetalLB, cert-manager, Vault, ESO) +├── operators/ # Raw manifests and operator values (local-path, MetalLB, ingress-nginx, cert-manager, Vault, ESO) └── keycloak/ # Dev Keycloak deployment and token helper scripts ``` @@ -104,6 +104,7 @@ The tables below summarize the keys that must be set per site. | `NICO_SITE_UUID` | No | Stable UUID for this site. If unset, `setup.sh` tries to reuse the UUID from a prior install (site-agent ConfigMap). If that fails, it adopts an existing REST site with the same name, or mints a UUID and seeds the site record itself. | | `NICO_MANAGE_DEFAULT_STORAGE_CLASS` | No | Whether `setup.sh` marks `local-path` as the default StorageClass. Defaults to `true`. Set to `false` when the cluster already has an operator-managed default StorageClass. | | `NICO_STORAGE_CLASS` | No | StorageClass used by Vault data/audit PVCs. Defaults to `local-path-persistent`. | +| `NICO_INSTALL_INGRESS_NGINX` | No | Install the optional ingress-nginx controller after MetalLB. Defaults to `false`; set to `true` only when the cluster does not already provide an ingress controller. | | `PREFLIGHT_CHECK_IMAGE` | No | Image used for preflight per-node checks. Defaults to `busybox:1.36`; set to a local mirror for air-gapped clusters. | ### `values.yaml` @@ -185,6 +186,7 @@ It supports these common deployment modes: | `--skip-core --skip-rest` | Infrastructure-only run; image tags, image registry, and REST repo are not required. | | `--core-values ` | Use site-specific Core values instead of `helm-prereqs/values/nico-core.yaml`. | | `--metallb-config ` | Use a site-specific MetalLB manifest file or kustomize directory. | +| `--install-ingress-nginx` | Install the optional ingress-nginx controller. The controller Service is `LoadBalancer` and receives its external IP from MetalLB. | | `--site-overlay ` | Apply a site kustomize overlay after Core deploys. | | `--debug` | Enable bash tracing. This can print secrets, so avoid it in shared logs. | diff --git a/helm-prereqs/clean.sh b/helm-prereqs/clean.sh index 7f748e34e8..e323fceded 100755 --- a/helm-prereqs/clean.sh +++ b/helm-prereqs/clean.sh @@ -21,10 +21,11 @@ # 0. NCX stack (nico-rest helm, temporal, keycloak, ncx postgres) # 1. nico core (separate helm release, if installed) # 2. helmfile releases (nico-prereqs, external-secrets, vault, cert-manager, -# postgres-operator) +# postgres-operator, ingress-nginx) # 3. cluster-scoped hook resources (ClusterIssuers, ClusterSecretStore, etc.) # 4. vault init secrets (vault-cluster-keys, vaultunsealkeys, vaultroottoken) -# 5. namespaces (nico-system, cert-manager, vault, external-secrets, postgres) +# 5. namespaces (nico-system, cert-manager, vault, external-secrets, +# postgres, ingress-nginx) # 6. local-path-persistent PVs owned by this stack (Retain policy — not deleted with namespace) # 7. local-path-provisioner + StorageClass (applied via kubectl, not helm-managed) # ============================================================================= @@ -68,7 +69,7 @@ helm uninstall nico -n nico-system 2>/dev/null || true # --------------------------------------------------------------------------- # 2. All helmfile releases in reverse dependency order: -# nico-prereqs → external-secrets → vault → cert-manager → metallb +# nico-prereqs → external-secrets → vault → cert-manager → ingress-nginx → metallb # --------------------------------------------------------------------------- echo "=== [2/8] Destroying helmfile releases ===" @@ -153,6 +154,14 @@ kubectl get clusterrole,clusterrolebinding -o name \ | grep nico-rest \ | xargs kubectl delete --ignore-not-found 2>/dev/null || true +# ingress-nginx cluster-scoped resources are normally removed by helm uninstall, +# but remove stragglers so clean.sh can recover from partial installs. +echo "Removing ingress-nginx cluster-scoped resources..." +kubectl delete ingressclass nginx --ignore-not-found 2>/dev/null || true +kubectl get clusterrole,clusterrolebinding -o name \ + | grep ingress-nginx \ + | xargs kubectl delete --ignore-not-found 2>/dev/null || true + # --------------------------------------------------------------------------- # 3. Cluster-scoped resources created by helm hooks. # These survive helm/helmfile uninstall because hook-delete-policy is @@ -193,12 +202,12 @@ kubectl delete secret vault-cluster-keys vaultunsealkeys vaultroottoken \ # conflict with setup.sh's helmfile install into the external-secrets ns. # --------------------------------------------------------------------------- echo "=== [5/8] Deleting namespaces ===" -kubectl delete ns nico-system cert-manager vault external-secrets postgres metallb-system \ +kubectl delete ns nico-system cert-manager vault external-secrets postgres ingress-nginx metallb-system \ --wait=false --ignore-not-found 2>/dev/null || true echo "Waiting for namespaces to terminate..." kubectl wait --for=delete \ - ns/nico-system ns/cert-manager ns/vault ns/external-secrets ns/postgres ns/metallb-system \ + ns/nico-system ns/cert-manager ns/vault ns/external-secrets ns/postgres ns/ingress-nginx ns/metallb-system \ --timeout=180s 2>/dev/null || true echo "Purging default namespace (ESO and other non-kubespray resources)..." @@ -233,7 +242,7 @@ echo "=== [6/8] Removing Released PersistentVolumes owned by this stack ===" kubectl get pv -o json 2>/dev/null \ | jq -r '.items[] | select( .spec.storageClassName == "local-path-persistent" and - (.spec.claimRef.namespace // "" | test("^(nico-system|cert-manager|vault|external-secrets|postgres|metallb-system|nico-rest|temporal)$")) + (.spec.claimRef.namespace // "" | test("^(nico-system|cert-manager|vault|external-secrets|postgres|ingress-nginx|metallb-system|nico-rest|temporal)$")) ) | .metadata.name' \ | xargs -r kubectl delete pv --ignore-not-found 2>/dev/null || true diff --git a/helm-prereqs/health-check.sh b/helm-prereqs/health-check.sh index 99e14b13a2..66058289c0 100755 --- a/helm-prereqs/health-check.sh +++ b/helm-prereqs/health-check.sh @@ -106,6 +106,10 @@ if [[ -z "${METALLB_NS:-}" ]]; then METALLB_NS=$(kubectl get deployment metallb-controller -A \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || printf 'metallb-system') fi +if [[ -z "${INGRESS_NGINX_NS:-}" ]]; then + INGRESS_NGINX_NS=$(kubectl get deployment ingress-nginx-controller -A \ + -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) +fi printf " %-26s %s\n" "NICo namespace:" "${NICO_NS}" printf " %-26s %s\n" "vault namespace:" "${VAULT_NS}" @@ -114,6 +118,7 @@ printf " %-26s %s\n" "postgres namespace:" "${POSTGRES_NS}" printf " %-26s %s\n" "cert-manager ns:" "${CERT_MANAGER_NS}" printf " %-26s %s\n" "external-secrets ns:" "${ESO_NS}" printf " %-26s %s\n" "metallb ns:" "${METALLB_NS}" +printf " %-26s %s\n" "ingress-nginx ns:" "${INGRESS_NGINX_NS:-not installed}" # -------------------------------------------------------------------------- # Test helpers @@ -241,6 +246,14 @@ else fail "daemonset/metallb-speaker: ${_ready:-0}/${_desired} ready" fi +if [[ -n "${INGRESS_NGINX_NS:-}" ]]; then + section "ingress-nginx" + _check_deployment "${INGRESS_NGINX_NS}" ingress-nginx-controller +else + section "ingress-nginx" + skip "not installed" +fi + # -------------------------------------------------------------------------- # 2. Vault # -------------------------------------------------------------------------- diff --git a/helm-prereqs/helmfile.yaml b/helm-prereqs/helmfile.yaml index febe0cb612..ad69695539 100644 --- a/helm-prereqs/helmfile.yaml +++ b/helm-prereqs/helmfile.yaml @@ -35,6 +35,8 @@ repositories: url: https://opensource.zalando.com/postgres-operator/charts/postgres-operator - name: metallb url: https://metallb.github.io/metallb + - name: ingress-nginx + url: https://kubernetes.github.io/ingress-nginx releases: # --------------------------------------------------------------------------- @@ -55,6 +57,21 @@ releases: values: - operators/values/metallb.yaml + # --------------------------------------------------------------------------- + # ingress-nginx — optional Ingress controller for clusters that do not already + # provide one. setup.sh installs it only when requested; the controller Service + # uses type LoadBalancer and receives an address from MetalLB. + # --------------------------------------------------------------------------- + - name: ingress-nginx + namespace: ingress-nginx + createNamespace: true + chart: ingress-nginx/ingress-nginx + version: "4.12.1" + wait: true + timeout: 600 + values: + - operators/values/ingress-nginx.yaml + # --------------------------------------------------------------------------- # Zalando postgres-operator — manages nico-pg-cluster HA Patroni cluster. # Installed before cert-manager (no TLS dependency). Must be up before diff --git a/helm-prereqs/operators/values/ingress-nginx.yaml b/helm-prereqs/operators/values/ingress-nginx.yaml new file mode 100644 index 0000000000..cab17aa13a --- /dev/null +++ b/helm-prereqs/operators/values/ingress-nginx.yaml @@ -0,0 +1,23 @@ +# ============================================================================= +# operators/values/ingress-nginx.yaml — ingress-nginx Helm chart static values +# +# Optional cluster-level Ingress controller for NICo REST ingress exposure. +# The controller Service is type LoadBalancer and is expected to receive an +# external IP from MetalLB. +# +# Chart: ingress-nginx/ingress-nginx 4.12.1 +# ============================================================================= + +controller: + ingressClassResource: + name: nginx + enabled: true + default: false + controllerValue: k8s.io/ingress-nginx + ingressClass: nginx + watchIngressWithoutClass: false + service: + type: LoadBalancer + externalTrafficPolicy: Cluster + admissionWebhooks: + enabled: true diff --git a/helm-prereqs/setup.sh b/helm-prereqs/setup.sh index cebe6da2b6..a365972f62 100755 --- a/helm-prereqs/setup.sh +++ b/helm-prereqs/setup.sh @@ -44,6 +44,8 @@ # StorageClass. Default: true. # NICO_STORAGE_CLASS StorageClass for Postgres and Vault data and audit PVCs. # Default: local-path-persistent. +# NICO_INSTALL_INGRESS_NGINX +# Install ingress-nginx after MetalLB. Default: false. # VAULT_NS Vault namespace. Default: vault # CERT_MANAGER_NS cert-manager namespace. Default: cert-manager # PREFLIGHT_CHECK_IMAGE Image for preflight per-node checks. @@ -65,6 +67,7 @@ # ./setup.sh --skip-core --skip-rest # fully non-interactive infra-only run # ./setup.sh --core-values /path/to/values.yaml # use site-specific values for Phase 6 # ./setup.sh --metallb-config /path/to/metallb.yaml # use site-specific MetalLB config (file or kustomize dir) +# ./setup.sh --install-ingress-nginx # install optional nginx Ingress controller # ./setup.sh --site-overlay /path/to/kustomize-dir # kubectl apply -k after Phase 6 (NTP services, etc.) # ./setup.sh --debug # enable bash -x trace (or run: bash -x ./setup.sh) # @@ -83,6 +86,7 @@ AUTO_YES=false SKIP_CORE=false SKIP_REST=false SKIP_FLOW=false +INSTALL_INGRESS_NGINX="${NICO_INSTALL_INGRESS_NGINX:-false}" CORE_VALUES="" METALLB_CONFIG="" SITE_OVERLAY="" @@ -92,6 +96,7 @@ while [[ $# -gt 0 ]]; do --skip-core) SKIP_CORE=true ;; --skip-rest) SKIP_REST=true ;; --skip-flow) SKIP_FLOW=true ;; + --install-ingress-nginx) INSTALL_INGRESS_NGINX=true ;; --debug) set -x ;; --core-values) [[ -z "${2:-}" ]] && { echo "Error: --core-values requires a file path"; exit 1; } @@ -108,7 +113,7 @@ while [[ $# -gt 0 ]]; do SITE_OVERLAY="$(cd "$(dirname "$2")" && pwd)/$(basename "$2")" [[ ! -d "${SITE_OVERLAY}" ]] && { echo "Error: --site-overlay directory not found: $2"; exit 1; } shift ;; - *) echo "Usage: $0 [-y] [--skip-core] [--skip-rest] [--skip-flow] [--core-values ] [--metallb-config ] [--site-overlay ] [--debug]"; exit 1 ;; + *) echo "Usage: $0 [-y] [--skip-core] [--skip-rest] [--skip-flow] [--install-ingress-nginx] [--core-values ] [--metallb-config ] [--site-overlay ] [--debug]"; exit 1 ;; esac shift done @@ -126,6 +131,10 @@ VAULT_NS="${VAULT_NS:-vault}" CERT_MANAGER_NS="${CERT_MANAGER_NS:-cert-manager}" NICO_MANAGE_DEFAULT_STORAGE_CLASS="${NICO_MANAGE_DEFAULT_STORAGE_CLASS:-true}" NICO_STORAGE_CLASS="${NICO_STORAGE_CLASS:-local-path-persistent}" +case "${INSTALL_INGRESS_NGINX}" in + true|false) ;; + *) echo "Error: NICO_INSTALL_INGRESS_NGINX must be true or false"; exit 1 ;; +esac # --------------------------------------------------------------------------- # Failure handler — offer to run clean.sh if setup exits with an error. @@ -311,6 +320,24 @@ else fi echo "MetalLB ready" +# --------------------------------------------------------------------------- +# 1d. ingress-nginx — optional Ingress controller. +# Install after MetalLB so the controller LoadBalancer Service can receive +# an external address. Skip this when the cluster already has an Ingress +# controller or uses a different implementation. +# --------------------------------------------------------------------------- +if [[ "${INSTALL_INGRESS_NGINX}" == "true" ]]; then + _SETUP_PHASE="[1d] ingress-nginx" + echo "=== [1d] ingress-nginx ===" + helmfile sync -l name=ingress-nginx + echo "Waiting for ingress-nginx controller to be ready..." + kubectl wait --for=condition=Available deployment/ingress-nginx-controller \ + -n ingress-nginx --timeout=120s + echo "ingress-nginx ready" +else + echo "Skipping ingress-nginx (set NICO_INSTALL_INGRESS_NGINX=true or pass --install-ingress-nginx to install it)" +fi + # --------------------------------------------------------------------------- # 2. cert-manager + Prometheus CRDs + Vault TLS bootstrap # cert-manager must be up before we can issue certs for vault. diff --git a/helm-prereqs/values/nico-rest.yaml b/helm-prereqs/values/nico-rest.yaml index fa7d404f79..e75f2ef9d2 100644 --- a/helm-prereqs/values/nico-rest.yaml +++ b/helm-prereqs/values/nico-rest.yaml @@ -18,6 +18,25 @@ nico-rest-api: nodePort: enabled: true port: 30388 + # To expose REST through the optional ingress-nginx controller, run setup.sh + # with --install-ingress-nginx and configure a DNS name here. The ingress + # routes to the chart-managed ClusterIP Service on service.port. + # ingress: + # enabled: true + # className: nginx + # hosts: + # - host: api.example.com + # paths: + # - path: / + # pathType: Prefix + # tls: + # - secretName: api-example-com-tls + # hosts: + # - api.example.com + # certificate: + # enabled: true + # secretName: api-example-com-tls + # commonName: api.example.com config: keycloak: enabled: true diff --git a/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress-certificate.yaml b/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress-certificate.yaml new file mode 100644 index 0000000000..039eb24d8b --- /dev/null +++ b/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress-certificate.yaml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +{{- if and .Values.ingress.enabled .Values.ingress.certificate.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ .Values.ingress.certificate.secretName }} + namespace: {{ include "nico-rest-api.namespace" . }} + labels: + {{- include "nico-rest-api.labels" . | nindent 4 }} +spec: + secretName: {{ .Values.ingress.certificate.secretName }} + duration: {{ .Values.ingress.certificate.duration }} + renewBefore: {{ .Values.ingress.certificate.renewBefore }} + {{- if .Values.ingress.certificate.subject.organizations }} + subject: + organizations: + {{- toYaml .Values.ingress.certificate.subject.organizations | nindent 6 }} + {{- end }} + commonName: {{ .Values.ingress.certificate.commonName }} + isCA: false + privateKey: + algorithm: {{ .Values.ingress.certificate.privateKey.algorithm }} + encoding: {{ .Values.ingress.certificate.privateKey.encoding }} + size: {{ .Values.ingress.certificate.privateKey.size }} + usages: + - server auth + - client auth + dnsNames: + {{- if .Values.ingress.certificate.dnsNames }} + {{- toYaml .Values.ingress.certificate.dnsNames | nindent 4 }} + {{- else }} + {{- range .Values.ingress.hosts }} + - {{ .host }} + {{- end }} + {{- end }} + {{- if .Values.ingress.certificate.uris }} + uris: + {{- toYaml .Values.ingress.certificate.uris | nindent 4 }} + {{- end }} + issuerRef: + name: {{ .Values.global.certificate.issuerRef.name }} + kind: {{ .Values.global.certificate.issuerRef.kind }} + group: {{ .Values.global.certificate.issuerRef.group }} +{{- end }} diff --git a/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress.yaml b/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress.yaml new file mode 100644 index 0000000000..5a4190f50f --- /dev/null +++ b/helm/rest/nico-rest/charts/nico-rest-api/templates/ingress.yaml @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "nico-rest-api.name" . }} + namespace: {{ include "nico-rest-api.namespace" . }} + labels: + {{- include "nico-rest-api.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ default "Prefix" .pathType }} + backend: + service: + name: {{ include "nico-rest-api.name" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/rest/nico-rest/charts/nico-rest-api/tests/ingress_test.yaml b/helm/rest/nico-rest/charts/nico-rest-api/tests/ingress_test.yaml new file mode 100644 index 0000000000..13ec9bdaf9 --- /dev/null +++ b/helm/rest/nico-rest/charts/nico-rest-api/tests/ingress_test.yaml @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +suite: ingress configurability +templates: + - ingress.yaml + - ingress-certificate.yaml +release: + namespace: nico-rest +set: + # Provided by the nico-rest parent chart at render time; set here so the + # subchart's certificate template renders standalone under helm-unittest. + global.certificate.issuerRef.name: nico-rest-ca-issuer + global.certificate.issuerRef.kind: ClusterIssuer + global.certificate.issuerRef.group: cert-manager.io +tests: + - it: does not render an ingress by default + template: ingress.yaml + asserts: + - hasDocuments: + count: 0 + + - it: does not render an ingress certificate when ingress is disabled + template: ingress-certificate.yaml + asserts: + - hasDocuments: + count: 0 + + - it: renders ingress rules, class, annotations, and tls when enabled + template: ingress.yaml + set: + service: + port: 9443 + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: nico-rest-ca-issuer + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: api.nico.example.com + paths: + - path: / + pathType: Prefix + - path: /v2 + pathType: ImplementationSpecific + tls: + - secretName: nico-rest-api-ingress-tls + hosts: + - api.nico.example.com + asserts: + - isKind: + of: Ingress + - equal: + path: spec.ingressClassName + value: nginx + - equal: + path: metadata.annotations["cert-manager.io/cluster-issuer"] + value: nico-rest-ca-issuer + - equal: + path: metadata.annotations["nginx.ingress.kubernetes.io/ssl-redirect"] + value: "true" + - equal: + path: spec.tls + value: + - secretName: nico-rest-api-ingress-tls + hosts: + - api.nico.example.com + - equal: + path: spec.rules[0].host + value: api.nico.example.com + - equal: + path: spec.rules[0].http.paths[0].backend.service.name + value: nico-rest-api + - equal: + path: spec.rules[0].http.paths[0].backend.service.port.number + value: 9443 + - equal: + path: spec.rules[0].http.paths[1].pathType + value: ImplementationSpecific + + - it: renders ingress certificate from ingress host defaults + template: ingress-certificate.yaml + set: + ingress: + enabled: true + certificate: + commonName: api.nico.example.com + secretName: nico-rest-api-ingress-tls + hosts: + - host: api.nico.example.com + paths: + - path: / + pathType: Prefix + - host: api-alt.nico.example.com + paths: + - path: / + pathType: Prefix + asserts: + - isKind: + of: Certificate + - equal: + path: metadata.name + value: nico-rest-api-ingress-tls + - equal: + path: spec.secretName + value: nico-rest-api-ingress-tls + - equal: + path: spec.commonName + value: api.nico.example.com + - equal: + path: spec.dnsNames + value: + - api.nico.example.com + - api-alt.nico.example.com + - equal: + path: spec.issuerRef + value: + name: nico-rest-ca-issuer + kind: ClusterIssuer + group: cert-manager.io + + - it: skips ingress certificate when using an existing TLS secret + template: ingress-certificate.yaml + set: + ingress: + enabled: true + certificate: + enabled: false + asserts: + - hasDocuments: + count: 0 diff --git a/helm/rest/nico-rest/charts/nico-rest-api/values.yaml b/helm/rest/nico-rest/charts/nico-rest-api/values.yaml index 527ccd6ac5..ad27cee5cb 100644 --- a/helm/rest/nico-rest/charts/nico-rest-api/values.yaml +++ b/helm/rest/nico-rest/charts/nico-rest-api/values.yaml @@ -18,6 +18,56 @@ nodePort: enabled: false port: 30388 +ingress: + enabled: false + className: "" + annotations: {} + # -- Ingress routes to the chart-managed Service on service.port. + # ClusterIP is sufficient for normal ingress controller traffic. + hosts: + - host: nico-rest-api.local + paths: + - path: / + pathType: Prefix + tls: + - secretName: nico-rest-api-ingress-tls + hosts: + - nico-rest-api.local + certificate: + # -- Create a cert-manager Certificate for the ingress TLS Secret when ingress is enabled. + # Set false when ingress.tls points at a pre-existing Secret. + enabled: true + secretName: nico-rest-api-ingress-tls + duration: 2160h + renewBefore: 360h + commonName: nico-rest-api.local + # -- Fully override the certificate dnsNames. When empty, dnsNames are + # generated from ingress.hosts. + dnsNames: [] + # -- SPIFFE/SAN URIs for the certificate. When empty, no uris are set. + uris: [] + subject: + # -- List of organizations for the certificate subject + organizations: [] + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + + # Example: use an existing TLS Secret for a fully qualified domain instead of cert-manager Certificate + # className: nginx + # certificate: + # enabled: false + # hosts: + # - host: api.example.com + # paths: + # - path: / + # pathType: Prefix + # tls: + # - secretName: api-example-com-tls + # hosts: + # - api.example.com + resources: requests: memory: "128Mi" From 14ace921d1103a6ba181518681647d9635b40415 Mon Sep 17 00:00:00 2001 From: Tareque Hossain Date: Fri, 10 Jul 2026 14:03:46 -0700 Subject: [PATCH 2/3] Fix deployment retrieval in health-check --- helm-prereqs/health-check.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/helm-prereqs/health-check.sh b/helm-prereqs/health-check.sh index 66058289c0..578e417903 100755 --- a/helm-prereqs/health-check.sh +++ b/helm-prereqs/health-check.sh @@ -95,20 +95,24 @@ fi # cert-manager, ESO, MetalLB: discover by known deployment names if [[ -z "${CERT_MANAGER_NS:-}" ]]; then - CERT_MANAGER_NS=$(kubectl get deployment cert-manager -A \ + CERT_MANAGER_NS=$(kubectl get deployment -A \ + --field-selector metadata.name=cert-manager \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || printf 'cert-manager') fi if [[ -z "${ESO_NS:-}" ]]; then - ESO_NS=$(kubectl get deployment external-secrets -A \ + ESO_NS=$(kubectl get deployment -A \ + --field-selector metadata.name=external-secrets \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || printf 'external-secrets') fi if [[ -z "${METALLB_NS:-}" ]]; then - METALLB_NS=$(kubectl get deployment metallb-controller -A \ + METALLB_NS=$(kubectl get deployment -A \ + --field-selector metadata.name=metallb-controller \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || printf 'metallb-system') fi if [[ -z "${INGRESS_NGINX_NS:-}" ]]; then - INGRESS_NGINX_NS=$(kubectl get deployment ingress-nginx-controller -A \ - -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) + INGRESS_NGINX_NS=$(kubectl get deployment -A \ + --field-selector metadata.name=ingress-nginx-controller \ + -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || printf 'ingress-nginx') fi printf " %-26s %s\n" "NICo namespace:" "${NICO_NS}" From fef63969a16ba6a1e41c10a1a85bfab8821817d2 Mon Sep 17 00:00:00 2001 From: Tareque Hossain Date: Fri, 10 Jul 2026 14:35:22 -0700 Subject: [PATCH 3/3] Fixed incorrect configmap retrieval --- helm-prereqs/health-check.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helm-prereqs/health-check.sh b/helm-prereqs/health-check.sh index 578e417903..6f9edfb123 100755 --- a/helm-prereqs/health-check.sh +++ b/helm-prereqs/health-check.sh @@ -67,7 +67,8 @@ section "Namespace Detection" # NICo namespace: find the namespace containing vault-cluster-info if [[ -z "${NICO_NS:-}" ]]; then - NICO_NS=$(kubectl get configmap vault-cluster-info -A \ + NICO_NS=$(kubectl get configmap -A \ + --field-selector metadata.name=vault-cluster-info \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) NICO_NS="${NICO_NS:-nico-system}" fi