diff --git a/deployment-configuration/helm/templates/certs/letsencrypt.yaml b/deployment-configuration/helm/templates/certs/letsencrypt.yaml index 7135e79c..67aa0062 100644 --- a/deployment-configuration/helm/templates/certs/letsencrypt.yaml +++ b/deployment-configuration/helm/templates/certs/letsencrypt.yaml @@ -1,4 +1,16 @@ -{{- if and (not .Values.local) (not (not .Values.tls)) }} +{{- $le := .Values.ingress.letsencrypt }} +{{- if and (not .Values.local) (not (not .Values.tls)) (ne $le.enabled false) }} +{{- range $name, $data := $le.secrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + namespace: {{ $.Values.namespace }} +type: Opaque +stringData: +{{ toYaml $data | indent 2 }} +--- +{{- end }} apiVersion: cert-manager.io/v1 kind: Issuer metadata: @@ -6,11 +18,15 @@ metadata: spec: acme: server: https://acme-v02.api.letsencrypt.org/directory - email: {{ .Values.ingress.letsencrypt.email }} + email: {{ $le.email }} privateKeySecretRef: - name: tls-secret-issuer + name: {{ $le.privateKeySecretName | default "tls-secret-issuer" }} solvers: +{{- if $le.solvers }} +{{ toYaml $le.solvers | indent 4 }} +{{- else }} - http01: ingress: class: {{ .Values.ingress.ingressClass }} +{{- end }} {{ end }} diff --git a/deployment-configuration/helm/templates/httproute.yaml b/deployment-configuration/helm/templates/httproute.yaml index 4b619078..4684b275 100644 --- a/deployment-configuration/helm/templates/httproute.yaml +++ b/deployment-configuration/helm/templates/httproute.yaml @@ -88,7 +88,7 @@ apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: {{ .Values.ingress.name | quote }} - {{- if and (not .Values.local) $tls }} + {{- if and (not .Values.local) $tls (ne .Values.ingress.letsencrypt.enabled false) }} annotations: cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" .Values.namespace }} {{- end }} diff --git a/deployment-configuration/helm/templates/ingress.yaml b/deployment-configuration/helm/templates/ingress.yaml index d8036c7e..5b305cf2 100644 --- a/deployment-configuration/helm/templates/ingress.yaml +++ b/deployment-configuration/helm/templates/ingress.yaml @@ -70,7 +70,7 @@ metadata: name: {{ $appIngressName | quote }} annotations: kubernetes.io/ingress.class: {{ $.Values.ingress.ingressClass }} # Deprecated by Kubernetes, however still required for GKE - {{- if and (not $.Values.local) $tls }} + {{- if and (not $.Values.local) $tls (ne $.Values.ingress.letsencrypt.enabled false) }} kubernetes.io/tls-acme: 'true' cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" $.Values.namespace }} {{- end }} @@ -152,7 +152,7 @@ metadata: name: {{ printf "%s-proxy" $appIngressName | quote }} annotations: kubernetes.io/ingress.class: {{ $.Values.ingress.ingressClass }} # Deprecated by Kubernetes, however still required for GKE - {{- if and (not $.Values.local) $tls }} + {{- if and (not $.Values.local) $tls (ne $.Values.ingress.letsencrypt.enabled false) }} kubernetes.io/tls-acme: 'true' cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" $.Values.namespace }} {{- end }} diff --git a/deployment-configuration/helm/templates/tls-secret.yaml b/deployment-configuration/helm/templates/tls-secret.yaml index 2979c988..6e9a7763 100644 --- a/deployment-configuration/helm/templates/tls-secret.yaml +++ b/deployment-configuration/helm/templates/tls-secret.yaml @@ -1,17 +1,25 @@ -{{- if and .Values.local .Values.tls }} +{{- $byoMode := or .Values.local (eq .Values.ingress.letsencrypt.enabled false) }} +{{- if and .Values.tls $byoMode }} +{{- $certs := default (dict) .Values.ingress.tls.certs }} +{{- $sharedCrt := $.Files.Get "resources/certs/tls.crt" }} +{{- $sharedKey := $.Files.Get "resources/certs/tls.key" }} {{- range $app := .Values.apps }} {{- if or $app.harness.subdomain $app.harness.domain $app.harness.aliases }} {{- $appIngressName := default $app.harness.name $app.harness.service.name }} + {{- $perApp := default (dict) (index $certs $appIngressName) }} + {{- $crt := default $sharedCrt $perApp.crt }} + {{- $key := default $sharedKey $perApp.key }} + {{- if and $crt $key }} apiVersion: v1 kind: Secret metadata: name: {{ printf "tls-secret-%s" $appIngressName }} type: kubernetes.io/tls data: - tls.crt: {{ $.Files.Get "resources/certs/tls.crt" | b64enc | quote }} - tls.key: {{ $.Files.Get "resources/certs/tls.key" | b64enc | quote }} + tls.crt: {{ $crt | b64enc | quote }} + tls.key: {{ $key | b64enc | quote }} --- + {{- end }} {{- end }} {{- end }} {{- end }} - diff --git a/deployment-configuration/helm/values.yaml b/deployment-configuration/helm/values.yaml index 0c9f7d99..df011e14 100644 --- a/deployment-configuration/helm/values.yaml +++ b/deployment-configuration/helm/values.yaml @@ -42,8 +42,53 @@ ingress: # -- Enables/disables SSL redirect. ssl_redirect: true letsencrypt: + # -- Whether to provision a cert-manager ACME Issuer for Let's Encrypt. Set to + # false to use externally provided TLS certificates (e.g. ACM/ALB, commercial + # wildcard, internal CA, air-gapped) — the per-app `tls-secret-` Secrets + # must then exist in the namespace. + enabled: true # -- Email for letsencrypt. email: cloudharness@metacell.us + # -- Name of the Secret cert-manager uses to store the ACME account private key. + privateKeySecretName: tls-secret-issuer + # -- ACME solvers passed through to the cert-manager Issuer. If empty, defaults to + # an http01 solver using `ingress.ingressClass`. Override with one or more dns01 + # solvers to obtain certificates for non-public domains. See `secrets` below to + # declare any credential Secrets referenced by `*SecretRef` here. + # Example (Cloudflare): + # solvers: + # - dns01: + # cloudflare: + # apiTokenSecretRef: + # name: cloudflare-api-token + # key: api-token + solvers: [] + # -- Credential Secrets created in the namespace alongside the Issuer. Each entry + # becomes a Kubernetes Secret named after the key, with the inner map rendered as + # `stringData`. Reference these from `solvers` above. Leave empty if you create + # provider credential secrets out-of-band (e.g. with sealed-secrets / external + # secrets / `kubectl create secret`). + # Example: + # secrets: + # cloudflare-api-token: + # api-token: ${CLOUDFLARE_API_TOKEN} + secrets: {} + # -- BYO TLS certificates (used when `letsencrypt.enabled` is false, or for local + # deployments). Two paths, used together: + # 1. File-based shared cert: drop PEM at `resources/certs/tls.crt|key` and it is + # applied to every app that needs TLS. + # 2. Per-app overrides: `tls.certs..{crt,key}` (PEM strings) materialize + # a `tls-secret-` of type `kubernetes.io/tls` and override the + # file-based shared cert for that app. + # Use `${VAR}` interpolation to keep raw PEM out of committed values files. + tls: + # -- Map of `` to `{crt, key}` PEM strings. + # Example: + # certs: + # myapp: + # crt: ${MYAPP_TLS_CRT} + # key: ${MYAPP_TLS_KEY} + certs: {} # -- Default regex segment for routes (used in paths like '/(pattern)'). path: "/" # -- The pathType for the Ingress path. Default is Prefix. For regex paths, set to ImplementationSpecific diff --git a/docs/README.md b/docs/README.md index d24afed7..e159b56e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ - [Send errors to Sentry](./sentry.md) - [Use the events queue to send notifications](./notifications.md) - [Network policies](./network-policies.md) + - [Ingress, domains, proxies and TLS](./ingress-domains-proxies.md) - [Writing and running automated tests](./testing.md) - [Tutorial: Writing a simple webapp with cloud-harness](./tutorials/simple-date-clock-application.adoc) diff --git a/docs/ingress-domains-proxies.md b/docs/ingress-domains-proxies.md index 178e6465..98780737 100644 --- a/docs/ingress-domains-proxies.md +++ b/docs/ingress-domains-proxies.md @@ -111,3 +111,269 @@ harness: Customization notes: - The pattern is inserted into the generated Ingress `path` field. Make sure the regex is valid for your ingress controller and matches the expected path syntax. + +## TLS and Let's Encrypt + +TLS is enabled by default for non-local deployments. Cloud Harness provisions a +`cert-manager` ACME `Issuer` named `letsencrypt-` and annotates every +generated Ingress (and Gateway, when using the Gateway API) so that certificates +are obtained and renewed automatically. + +All configuration lives under `ingress.letsencrypt` in +`deployment-configuration/helm/values.yaml`: + +```yaml +ingress: + letsencrypt: + enabled: true # provision the ACME Issuer + email: cloudharness@metacell.us # ACME account email + privateKeySecretName: tls-secret-issuer # ACME account private-key Secret + solvers: [] # solver list; empty = http01 default + secrets: {} # credential Secrets created in-namespace +``` + +### Default — public domains via http01 + +For publicly reachable domains, the defaults are sufficient. Set `email` and +leave the rest untouched: + +```yaml +ingress: + letsencrypt: + email: ops@example.com +``` + +This renders a single `http01` solver bound to the configured `ingressClass`. +HTTP01 requires no credentials, so the rest of this section only applies to +DNS01 setups. + +### Defining credential secrets + +Every DNS01 solver references its provider credentials through a `*SecretRef` +block (the exact field name depends on the provider — `apiTokenSecretRef`, +`tokenSecretRef`, `secretAccessKeySecretRef`, `serviceAccountSecretRef`, +`clientSecretSecretRef`, `tsigSecretSecretRef`, …). All of them have the same +shape: + +```yaml +someProviderSecretRef: + name: # name of the Secret in the release namespace + key: # which field of the Secret holds the credential +``` + +You have two options for providing the referenced Secret: + +**Option 1 — declare it inline (Cloud Harness creates it):** add an entry under +`ingress.letsencrypt.secrets`. The top-level key becomes the `Secret`'s name; +the nested map becomes its `stringData`. Each inner key is one credential +field, and the `*SecretRef.key` in the solver must match one of those inner +keys exactly. + +```yaml +ingress: + letsencrypt: + secrets: + cloudflare-api-token: # → Secret/cloudflare-api-token + api-token: s3cr3t # → stringData.api-token = "s3cr3t" + route53-credentials: # → Secret/route53-credentials + secret-access-key: AKIA... # → stringData.secret-access-key = "AKIA..." +``` + +A single Secret can hold multiple keys, so you can group related credentials +together (e.g. an `accessKey` and a `secretAccessKey`) and reference each by +its own key. Names you choose for both the Secret and its keys are arbitrary — +the only constraint is that the `*SecretRef.name`/`key` in the solver match. + +Because `values.yaml` is committed to source control, prefer injecting real +secrets via environment-variable interpolation at deploy time: + +```yaml +ingress: + letsencrypt: + secrets: + cloudflare-api-token: + api-token: ${CLOUDFLARE_API_TOKEN} # resolved by harness-deployment +``` + +**Option 2 — provision the Secret out-of-band:** create the `Secret` with any +external tool (`kubectl create secret`, sealed-secrets, External Secrets +Operator, a CI/CD pipeline secret, etc.) in the same namespace as the Issuer. +Leave `ingress.letsencrypt.secrets` empty and just reference the existing +Secret from the solver. + +```bash +kubectl -n ch create secret generic cloudflare-api-token \ + --from-literal=api-token="$CLOUDFLARE_API_TOKEN" +``` + +```yaml +ingress: + letsencrypt: + # secrets: {} ← intentionally omitted; the Secret already exists + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token + key: api-token +``` + +Both options are interchangeable from cert-manager's perspective — pick the +one that matches how you manage other secrets in the cluster. + +### DNS01 — Cloudflare (non-public domains) + +DNS01 challenges work for any domain — including domains that aren't reachable +from the internet — as long as cert-manager can update the zone's TXT records. + +```yaml +ingress: + letsencrypt: + email: ops@example.com + secrets: + cloudflare-api-token: + api-token: ${CLOUDFLARE_API_TOKEN} + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token + key: api-token +``` + +The `secrets:` map materializes a `Secret` per entry in the release namespace. +Skip it if you provision credentials out-of-band (sealed-secrets, External +Secrets, manual `kubectl create secret`, etc.) — only the `solvers` entry is +required in that case. + +### Multiple solvers (mixed http01 + DNS01) + +Selectors route hostnames to the matching solver. Default solver applies to +anything that doesn't match a selector: + +```yaml +ingress: + letsencrypt: + email: ops@example.com + secrets: + cloudflare-api-token: { api-token: ${CLOUDFLARE_API_TOKEN} } + solvers: + - http01: + ingress: + class: nginx + - dns01: + cloudflare: + apiTokenSecretRef: { name: cloudflare-api-token, key: api-token } + selector: + dnsZones: ["internal.example.com"] +``` + +### Other DNS providers + +Any provider supported by `cert-manager` works — the `solvers[*].dns01` block +is passed through verbatim. Each provider expects credentials via a +`*SecretRef` (see [Defining credential secrets](#defining-credential-secrets)). +Common shapes: + +```yaml +# --- Route 53 --- +secrets: + route53-credentials: + secret-access-key: ${AWS_SECRET_ACCESS_KEY} +solvers: + - dns01: + route53: + region: us-east-1 + accessKeyID: AKIA... + secretAccessKeySecretRef: { name: route53-credentials, key: secret-access-key } + +# --- Google Cloud DNS --- +secrets: + clouddns-sa: + key.json: ${GCP_SERVICE_ACCOUNT_JSON} # entire JSON service-account file +solvers: + - dns01: + cloudDNS: + project: my-gcp-project + serviceAccountSecretRef: { name: clouddns-sa, key: key.json } + +# --- DigitalOcean --- +secrets: + digitalocean-dns: + access-token: ${DO_TOKEN} +solvers: + - dns01: + digitalocean: + tokenSecretRef: { name: digitalocean-dns, key: access-token } +``` + +See the [cert-manager DNS01 reference](https://cert-manager.io/docs/configuration/acme/dns01/) +for the full per-provider schema (Azure DNS, RFC2136, AcmeDNS, webhook, …). + +### Bring your own certificates (no ACME) + +Set `letsencrypt.enabled: false` to skip the ACME Issuer and the +`cert-manager.io/issuer` annotation on every generated Ingress/Gateway. Each +app's Ingress still references a Secret named `tls-secret-`, and +Cloud Harness can populate that Secret for you in two ways (used together). + +**Option 1 — file-based shared cert.** Drop a PEM cert pair at +`resources/certs/tls.crt|key` inside the Helm chart. It is applied to every +app that has a `subdomain`, `domain`, or `aliases`, useful when a single +wildcard cert covers the whole base domain. Same files that already drive +local-mode TLS. + +```yaml +ingress: + letsencrypt: + enabled: false +``` + +``` +deployment-configuration/helm/resources/certs/ +├── tls.crt # PEM-encoded cert (or chain) +└── tls.key # PEM-encoded private key +``` + +**Option 2 — per-app inline certs.** Map each app to its own PEM cert/key +under `ingress.tls.certs`. Each entry materializes as one +`tls-secret-` of type `kubernetes.io/tls`. Per-app entries override +the file-based shared cert for that app — apps without an entry fall back to +the shared cert. Use env-variable interpolation to keep raw PEM out of +committed YAML: + +```yaml +ingress: + letsencrypt: + enabled: false + tls: + certs: + myapp: + crt: ${MYAPP_TLS_CRT} + key: ${MYAPP_TLS_KEY} + analytics: + crt: ${ANALYTICS_TLS_CRT} + key: ${ANALYTICS_TLS_KEY} +``` + +**Option 3 — fully out-of-band.** Leave `ingress.tls.certs` empty and don't +stage cert files. No `Secret` is rendered by the chart, and you create each +`tls-secret-` separately with whatever tooling you already use +(`kubectl create secret tls`, sealed-secrets, External Secrets Operator, a CI +job, cloud LB integration, …). The app key must match `harness.name` / +`harness.service.name` so the Ingress reference resolves. + +```bash +kubectl -n ch create secret tls tls-secret-myapp \ + --cert=path/to/myapp.crt --key=path/to/myapp.key +``` + +> **Gateway-API mode.** When deploying via `Gateway` + `HTTPRoute` instead of +> Ingress, the gateway uses a single shared `tls-secret` Secret (not per-app). +> Per-app `ingress.tls.certs` entries don't apply in that mode — provide the +> shared cert via the file-based path or out-of-band. + +### Disabling TLS entirely + +For local or development deployments, set `tls: false` at the root of the +values file. This is what `harness-deployment ... -dtls -l` does for you. diff --git a/docs/model/GatewayGlobalConfig.md b/docs/model/GatewayGlobalConfig.md index 49e12825..12535ace 100644 --- a/docs/model/GatewayGlobalConfig.md +++ b/docs/model/GatewayGlobalConfig.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **path_type** | **str** | Ingress path type | [optional] **path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] **ssl_redirect** | **bool** | | [optional] +**tls** | [**GatewayGlobalConfigAllOfTls**](GatewayGlobalConfigAllOfTls.md) | | [optional] **letsencrypt** | [**GatewayGlobalConfigAllOfLetsencrypt**](GatewayGlobalConfigAllOfLetsencrypt.md) | | [optional] **enabled** | **bool** | | [optional] diff --git a/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md b/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md index 73c79632..c6ba1d36 100644 --- a/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md +++ b/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md @@ -6,7 +6,11 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**enabled** | **bool** | Whether to provision a cert-manager ACME Issuer for Let's Encrypt. Set to false to use externally provided TLS Secrets without ACME (e.g. ACM/ALB, commercial wildcards, internal CAs, air-gapped clusters). | [optional] **email** | **str** | | [optional] +**private_key_secret_name** | **str** | Name of the Secret cert-manager uses to store the ACME account private key. Defaults to `tls-secret-issuer`. | [optional] +**solvers** | **List[Dict[str, object]]** | ACME solvers passed through to the cert-manager Issuer. Defaults to an http01 solver using the configured ingressClass. Set to a dns01 solver list to obtain certificates for non-public domains. | [optional] +**secrets** | **Dict[str, Dict[str, str]]** | Credential Secrets created in the namespace alongside the Issuer. Map of `<secret-name>` to a `<key>: <value>` map rendered as `stringData`. Reference these from `solvers`. | [optional] ## Example diff --git a/docs/model/GatewayGlobalConfigAllOfTls.md b/docs/model/GatewayGlobalConfigAllOfTls.md new file mode 100644 index 00000000..5bcb338d --- /dev/null +++ b/docs/model/GatewayGlobalConfigAllOfTls.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfTls + +BYO TLS certificate configuration. Used when `letsencrypt.enabled` is false or `local` is true. Per-app entries override the file-based shared cert at `resources/certs/tls.crt|key`. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**certs** | [**Dict[str, GatewayGlobalConfigAllOfTlsCerts]**](GatewayGlobalConfigAllOfTlsCerts.md) | Map of `<appName>` to `{crt, key}` PEM strings. Materializes one `tls-secret-<appName>` Secret per entry of type `kubernetes.io/tls`. | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_tls import GatewayGlobalConfigAllOfTls + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfTls from a JSON string +gateway_global_config_all_of_tls_instance = GatewayGlobalConfigAllOfTls.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfTls.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_tls_dict = gateway_global_config_all_of_tls_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfTls from a dict +gateway_global_config_all_of_tls_from_dict = GatewayGlobalConfigAllOfTls.from_dict(gateway_global_config_all_of_tls_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/model/GatewayGlobalConfigAllOfTlsCerts.md b/docs/model/GatewayGlobalConfigAllOfTlsCerts.md new file mode 100644 index 00000000..90cdd019 --- /dev/null +++ b/docs/model/GatewayGlobalConfigAllOfTlsCerts.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfTlsCerts + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**crt** | **str** | | [optional] +**key** | **str** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_tls_certs import GatewayGlobalConfigAllOfTlsCerts + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfTlsCerts from a JSON string +gateway_global_config_all_of_tls_certs_instance = GatewayGlobalConfigAllOfTlsCerts.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfTlsCerts.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_tls_certs_dict = gateway_global_config_all_of_tls_certs_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfTlsCerts from a dict +gateway_global_config_all_of_tls_certs_from_dict = GatewayGlobalConfigAllOfTlsCerts.from_dict(gateway_global_config_all_of_tls_certs_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/cloudharness-common/cloudharness/auth/keycloak.py b/libraries/cloudharness-common/cloudharness/auth/keycloak.py index d925b1b9..7c5c6d6b 100644 --- a/libraries/cloudharness-common/cloudharness/auth/keycloak.py +++ b/libraries/cloudharness-common/cloudharness/auth/keycloak.py @@ -118,7 +118,7 @@ def _get_public_key_cache_path(): from django.conf import settings return os.path.join(settings.PERSISTENT_ROOT, "cloudharness_public_key") except ImportError: - return "/tmp/cloudharness_public_key" + return "/tmp/cloudharness_public_key" except Exception: log.exception("Could not get Django settings, using /tmp for public key cache") return "/tmp/cloudharness_public_key" diff --git a/libraries/models/README.md b/libraries/models/README.md index d6718ae4..8f02d510 100644 --- a/libraries/models/README.md +++ b/libraries/models/README.md @@ -93,6 +93,8 @@ Class | Method | HTTP request | Description - [GatewayConfig](docs/GatewayConfig.md) - [GatewayGlobalConfig](docs/GatewayGlobalConfig.md) - [GatewayGlobalConfigAllOfLetsencrypt](docs/GatewayGlobalConfigAllOfLetsencrypt.md) + - [GatewayGlobalConfigAllOfTls](docs/GatewayGlobalConfigAllOfTls.md) + - [GatewayGlobalConfigAllOfTlsCerts](docs/GatewayGlobalConfigAllOfTlsCerts.md) - [GitDependencyConfig](docs/GitDependencyConfig.md) - [HarnessMainConfig](docs/HarnessMainConfig.md) - [JupyterHubConfig](docs/JupyterHubConfig.md) diff --git a/libraries/models/api/openapi.yaml b/libraries/models/api/openapi.yaml index c5b8fe36..38777143 100644 --- a/libraries/models/api/openapi.yaml +++ b/libraries/models/api/openapi.yaml @@ -908,12 +908,65 @@ components: ssl_redirect: description: '' type: boolean + tls: + description: | + BYO TLS certificate configuration. Used when + `letsencrypt.enabled` is false or `local` is true. + Per-app entries override the file-based shared cert + at `resources/certs/tls.crt|key`. + type: object + properties: + certs: + description: | + Map of `` to `{crt, key}` PEM strings. + Materializes one `tls-secret-` Secret per entry + of type `kubernetes.io/tls`. + type: object + additionalProperties: + type: object + properties: + crt: + type: string + key: + type: string letsencrypt: description: '' type: object properties: + enabled: + description: | + Whether to provision a cert-manager ACME Issuer for + Let's Encrypt. Set to false to use externally provided + TLS Secrets without ACME (e.g. ACM/ALB, commercial + wildcards, internal CAs, air-gapped clusters). + type: boolean email: type: string + privateKeySecretName: + description: | + Name of the Secret cert-manager uses to store the ACME + account private key. Defaults to `tls-secret-issuer`. + type: string + solvers: + description: | + ACME solvers passed through to the cert-manager Issuer. + Defaults to an http01 solver using the configured + ingressClass. Set to a dns01 solver list to obtain + certificates for non-public domains. + type: array + items: + type: object + additionalProperties: true + secrets: + description: | + Credential Secrets created in the namespace alongside the + Issuer. Map of `` to a `: ` map + rendered as `stringData`. Reference these from `solvers`. + type: object + additionalProperties: + type: object + additionalProperties: + type: string enabled: description: '' type: boolean diff --git a/libraries/models/cloudharness_model/models/__init__.py b/libraries/models/cloudharness_model/models/__init__.py index 3b8863a7..013a351c 100644 --- a/libraries/models/cloudharness_model/models/__init__.py +++ b/libraries/models/cloudharness_model/models/__init__.py @@ -40,6 +40,8 @@ from cloudharness_model.models.gateway_config import GatewayConfig from cloudharness_model.models.gateway_global_config import GatewayGlobalConfig from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt +from cloudharness_model.models.gateway_global_config_all_of_tls import GatewayGlobalConfigAllOfTls +from cloudharness_model.models.gateway_global_config_all_of_tls_certs import GatewayGlobalConfigAllOfTlsCerts from cloudharness_model.models.git_dependency_config import GitDependencyConfig from cloudharness_model.models.harness_main_config import HarnessMainConfig from cloudharness_model.models.jupyter_hub_config import JupyterHubConfig diff --git a/libraries/models/cloudharness_model/models/gateway_global_config.py b/libraries/models/cloudharness_model/models/gateway_global_config.py index 12553344..ec484ea7 100644 --- a/libraries/models/cloudharness_model/models/gateway_global_config.py +++ b/libraries/models/cloudharness_model/models/gateway_global_config.py @@ -26,6 +26,7 @@ from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated import importlib from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt +from cloudharness_model.models.gateway_global_config_all_of_tls import GatewayGlobalConfigAllOfTls class GatewayGlobalConfig(CloudHarnessBaseModel): """ @@ -36,10 +37,11 @@ class GatewayGlobalConfig(CloudHarnessBaseModel): path_type: Optional[StrictStr] = Field(default=None, description="Ingress path type ", alias="pathType") path: Optional[StrictStr] = Field(default=None, description="Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. ") ssl_redirect: Optional[StrictBool] = None + tls: Optional[GatewayGlobalConfigAllOfTls] = None letsencrypt: Optional[GatewayGlobalConfigAllOfLetsencrypt] = None enabled: Optional[StrictBool] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["auto", "name", "pathType", "path", "ssl_redirect", "letsencrypt", "enabled"] + __properties: ClassVar[List[str]] = ["auto", "name", "pathType", "path", "ssl_redirect", "tls", "letsencrypt", "enabled"] def to_dict(self) -> Dict[str, Any]: """Return the dictionary representation of the model using alias. @@ -61,6 +63,9 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of tls + if self.tls: + _dict['tls'] = self.tls.to_dict() # override the default output from pydantic by calling `to_dict()` of letsencrypt if self.letsencrypt: _dict['letsencrypt'] = self.letsencrypt.to_dict() @@ -86,6 +91,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "pathType": obj.get("pathType"), "path": obj.get("path"), "ssl_redirect": obj.get("ssl_redirect"), + "tls": GatewayGlobalConfigAllOfTls.from_dict(obj["tls"]) if obj.get("tls") is not None else None, "letsencrypt": GatewayGlobalConfigAllOfLetsencrypt.from_dict(obj["letsencrypt"]) if obj.get("letsencrypt") is not None else None, "enabled": obj.get("enabled") }) diff --git a/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py index f402310d..52be2c89 100644 --- a/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py +++ b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py @@ -30,9 +30,13 @@ class GatewayGlobalConfigAllOfLetsencrypt(CloudHarnessBaseModel): """ """ # noqa: E501 + enabled: Optional[StrictBool] = Field(default=None, description="Whether to provision a cert-manager ACME Issuer for Let's Encrypt. Set to false to use externally provided TLS Secrets without ACME (e.g. ACM/ALB, commercial wildcards, internal CAs, air-gapped clusters). ") email: Optional[StrictStr] = None + private_key_secret_name: Optional[StrictStr] = Field(default=None, description="Name of the Secret cert-manager uses to store the ACME account private key. Defaults to `tls-secret-issuer`. ", alias="privateKeySecretName") + solvers: Optional[List[Dict[str, Any]]] = Field(default=None, description="ACME solvers passed through to the cert-manager Issuer. Defaults to an http01 solver using the configured ingressClass. Set to a dns01 solver list to obtain certificates for non-public domains. ") + secrets: Optional[Dict[str, Dict[str, StrictStr]]] = Field(default=None, description="Credential Secrets created in the namespace alongside the Issuer. Map of `` to a `: ` map rendered as `stringData`. Reference these from `solvers`. ") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["email"] + __properties: ClassVar[List[str]] = ["enabled", "email", "privateKeySecretName", "solvers", "secrets"] def to_dict(self) -> Dict[str, Any]: """Return the dictionary representation of the model using alias. @@ -71,7 +75,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "email": obj.get("email") + "enabled": obj.get("enabled"), + "email": obj.get("email"), + "privateKeySecretName": obj.get("privateKeySecretName"), + "solvers": obj.get("solvers"), + "secrets": obj.get("secrets") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls.py b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls.py new file mode 100644 index 00000000..e6720396 --- /dev/null +++ b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls.py @@ -0,0 +1,96 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from typing import Optional, Set +from typing_extensions import Self + + +from cloudharness_model.base_model import CloudHarnessBaseModel +from pydantic import BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib +from cloudharness_model.models.gateway_global_config_all_of_tls_certs import GatewayGlobalConfigAllOfTlsCerts + +class GatewayGlobalConfigAllOfTls(CloudHarnessBaseModel): + """ + BYO TLS certificate configuration. Used when `letsencrypt.enabled` is false or `local` is true. Per-app entries override the file-based shared cert at `resources/certs/tls.crt|key`. + """ # noqa: E501 + certs: Optional[Dict[str, GatewayGlobalConfigAllOfTlsCerts]] = Field(default=None, description="Map of `` to `{crt, key}` PEM strings. Materializes one `tls-secret-` Secret per entry of type `kubernetes.io/tls`. ") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["certs"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each value in certs (dict) + _field_dict = {} + if self.certs: + for _key_certs in self.certs: + if self.certs[_key_certs]: + _field_dict[_key_certs] = self.certs[_key_certs].to_dict() + _dict['certs'] = _field_dict + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GatewayGlobalConfigAllOfTls from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "certs": dict( + (_k, GatewayGlobalConfigAllOfTlsCerts.from_dict(_v)) + for _k, _v in obj["certs"].items() + ) + if obj.get("certs") is not None + else None + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls_certs.py b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls_certs.py new file mode 100644 index 00000000..090f0e4f --- /dev/null +++ b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_tls_certs.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from typing import Optional, Set +from typing_extensions import Self + + +from cloudharness_model.base_model import CloudHarnessBaseModel +from pydantic import BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib + +class GatewayGlobalConfigAllOfTlsCerts(CloudHarnessBaseModel): + """ + GatewayGlobalConfigAllOfTlsCerts + """ # noqa: E501 + crt: Optional[StrictStr] = None + key: Optional[StrictStr] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["crt", "key"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GatewayGlobalConfigAllOfTlsCerts from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "crt": obj.get("crt"), + "key": obj.get("key") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/docs/ExtraContainerConfig.md b/libraries/models/docs/ExtraContainerConfig.md index cfacdf37..3580df37 100644 --- a/libraries/models/docs/ExtraContainerConfig.md +++ b/libraries/models/docs/ExtraContainerConfig.md @@ -9,7 +9,7 @@ Name | Type | Description | Notes **auto** | **bool** | When true, the extra container is included in the deployment | [optional] **init_container** | **bool** | When true, the container runs as a Kubernetes init container (before the main container). When false, the container runs as a sidecar (alongside the main container). | [optional] **image** | **str** | Docker image for the extra container. If not specified, defaults to the main application image. | [optional] -**commands** | **List[str]** | Command to run in the extra container | [optional] +**command** | **List[str]** | Command to run in the extra container | [optional] **share_volume** | **bool** | When true, the extra container shares the same volume mounts as the main container. | [optional] **resources** | [**DeploymentResourcesConf**](DeploymentResourcesConf.md) | | [optional] diff --git a/libraries/models/docs/GatewayGlobalConfig.md b/libraries/models/docs/GatewayGlobalConfig.md index 49e12825..12535ace 100644 --- a/libraries/models/docs/GatewayGlobalConfig.md +++ b/libraries/models/docs/GatewayGlobalConfig.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **path_type** | **str** | Ingress path type | [optional] **path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] **ssl_redirect** | **bool** | | [optional] +**tls** | [**GatewayGlobalConfigAllOfTls**](GatewayGlobalConfigAllOfTls.md) | | [optional] **letsencrypt** | [**GatewayGlobalConfigAllOfLetsencrypt**](GatewayGlobalConfigAllOfLetsencrypt.md) | | [optional] **enabled** | **bool** | | [optional] diff --git a/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md b/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md index 73c79632..c6ba1d36 100644 --- a/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md +++ b/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md @@ -6,7 +6,11 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**enabled** | **bool** | Whether to provision a cert-manager ACME Issuer for Let's Encrypt. Set to false to use externally provided TLS Secrets without ACME (e.g. ACM/ALB, commercial wildcards, internal CAs, air-gapped clusters). | [optional] **email** | **str** | | [optional] +**private_key_secret_name** | **str** | Name of the Secret cert-manager uses to store the ACME account private key. Defaults to `tls-secret-issuer`. | [optional] +**solvers** | **List[Dict[str, object]]** | ACME solvers passed through to the cert-manager Issuer. Defaults to an http01 solver using the configured ingressClass. Set to a dns01 solver list to obtain certificates for non-public domains. | [optional] +**secrets** | **Dict[str, Dict[str, str]]** | Credential Secrets created in the namespace alongside the Issuer. Map of `<secret-name>` to a `<key>: <value>` map rendered as `stringData`. Reference these from `solvers`. | [optional] ## Example diff --git a/libraries/models/docs/GatewayGlobalConfigAllOfTls.md b/libraries/models/docs/GatewayGlobalConfigAllOfTls.md new file mode 100644 index 00000000..5bcb338d --- /dev/null +++ b/libraries/models/docs/GatewayGlobalConfigAllOfTls.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfTls + +BYO TLS certificate configuration. Used when `letsencrypt.enabled` is false or `local` is true. Per-app entries override the file-based shared cert at `resources/certs/tls.crt|key`. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**certs** | [**Dict[str, GatewayGlobalConfigAllOfTlsCerts]**](GatewayGlobalConfigAllOfTlsCerts.md) | Map of `<appName>` to `{crt, key}` PEM strings. Materializes one `tls-secret-<appName>` Secret per entry of type `kubernetes.io/tls`. | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_tls import GatewayGlobalConfigAllOfTls + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfTls from a JSON string +gateway_global_config_all_of_tls_instance = GatewayGlobalConfigAllOfTls.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfTls.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_tls_dict = gateway_global_config_all_of_tls_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfTls from a dict +gateway_global_config_all_of_tls_from_dict = GatewayGlobalConfigAllOfTls.from_dict(gateway_global_config_all_of_tls_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/GatewayGlobalConfigAllOfTlsCerts.md b/libraries/models/docs/GatewayGlobalConfigAllOfTlsCerts.md new file mode 100644 index 00000000..90cdd019 --- /dev/null +++ b/libraries/models/docs/GatewayGlobalConfigAllOfTlsCerts.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfTlsCerts + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**crt** | **str** | | [optional] +**key** | **str** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_tls_certs import GatewayGlobalConfigAllOfTlsCerts + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfTlsCerts from a JSON string +gateway_global_config_all_of_tls_certs_instance = GatewayGlobalConfigAllOfTlsCerts.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfTlsCerts.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_tls_certs_dict = gateway_global_config_all_of_tls_certs_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfTlsCerts from a dict +gateway_global_config_all_of_tls_certs_from_dict = GatewayGlobalConfigAllOfTlsCerts.from_dict(gateway_global_config_all_of_tls_certs_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +