From 95a4536a9b51188079eff0932cce6d22cd0f5e4d Mon Sep 17 00:00:00 2001 From: Richard Lavey <6595659+rlaveycal@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:47:12 +0100 Subject: [PATCH 1/3] add bolt plugin include plugin ports in network policy add staefulset annotaions and labels add https and tls settings add cert-manager certificate --- charts/arcadedb/README.md | 337 ++++++++++++------ charts/arcadedb/templates/_helpers.tpl | 54 ++- charts/arcadedb/templates/certificate.yaml | 38 ++ charts/arcadedb/templates/networkpolicy.yaml | 7 +- charts/arcadedb/templates/service.yaml | 38 ++ charts/arcadedb/templates/statefulset.yaml | 36 +- charts/arcadedb/tests/certificate_test.yaml | 95 +++++ charts/arcadedb/tests/helpers_test.yaml | 30 +- charts/arcadedb/tests/networkpolicy_test.yaml | 17 +- charts/arcadedb/tests/service_test.yaml | 42 +++ charts/arcadedb/tests/statefulset_test.yaml | 82 +++++ charts/arcadedb/values.yaml | 69 +++- 12 files changed, 699 insertions(+), 146 deletions(-) create mode 100644 charts/arcadedb/templates/certificate.yaml create mode 100644 charts/arcadedb/tests/certificate_test.yaml diff --git a/charts/arcadedb/README.md b/charts/arcadedb/README.md index c0d13ca..78687c4 100644 --- a/charts/arcadedb/README.md +++ b/charts/arcadedb/README.md @@ -31,23 +31,57 @@ The command removes all the Kubernetes components associated with the chart and ## Parameters +| Name | Description | Value | +|-----------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| `fullnameOverride` | | `""` | +| `nameOverride` | This is to override the chart name. | `""` | +| `podAnnotations` | Annotations added to every pod. | `{}` | +| `podLabels` | Labels added to every pod. | `{}` | +| `podSecurityContext` | Pod-level security context. UID/GID 1000 matches the arcadedb user in the Docker image. | `{runAsNonRoot: true, fsGroup: 1000}` | +| `replicaCount` | Number of replicas. Values greater than 1 enable Raft HA automatically. | `1` | +| `securityContext` | Container-level security context. | `{runAsUser: 1000, runAsGroup: 1000, allowPrivilegeEscalation: false, capabilities.drop: [ALL]}` | +| `statefulSetAnnotations` | Annotations added to the StatefulSet. | `{}` | +| `statefulSetLabels` | Labels added to the StatefulSet. | `{}` | + ### arcadedb -| Name | Description | Value | -|------------------------------|--------------------------------------------------------------------|------------------------------------------| -| `arcadedb.databaseDirectory` | Database storage directory inside the container | `/home/arcadedb/databases` | -| `arcadedb.defaultDatabases` | Databases to create at startup. Empty = none. | `""` | -| `arcadedb.extraCommands` | Extra JVM -D arguments appended to the startup command | `["-Darcadedb.server.mode=production"]` | -| `arcadedb.extraEnvironment` | Additional environment variables to pass to the ArcadeDB container | `[]` | -| `arcadedb.installDirectory` | Directory the ArcadeDB distribution lives in inside the image | `/home/arcadedb` | -| `arcadedb.consoleWorkingDirectory` | Writable working directory for interactive tools (console) | `/tmp` | +| Name | Description | Value | +|----------------------------------------|--------------------------------------------------------------------|------------------------------------------| +| `arcadedb.configDirectory` | Config storage directory inside the container | `/home/arcadedb/config` | +| `arcadedb.databaseDirectory` | Database storage directory inside the container | `/home/arcadedb/databases` | +| `arcadedb.defaultDatabases` | Databases to create at startup. Empty = none. | `""` | +| `arcadedb.extraCommands` | Extra JVM -D arguments appended to the startup command | `["-Darcadedb.server.mode=production"]` | +| `arcadedb.extraEnvironment` | Additional environment variables to pass to the ArcadeDB container | `[]` | +| `arcadedb.logsDirectory` | Directory where the server writes log files | `/home/arcadedb/log` | +| `arcadedb.installDirectory` | Directory the ArcadeDB distribution lives in inside the image | `/home/arcadedb` | +| `arcadedb.consoleWorkingDirectory` | Writable working directory for interactive tools (console) | `/tmp` | +| `arcadedb.ha.raftStorageDirectory` | Parent directory for per-node Raft storage | `/home/arcadedb/raft` | + +### arcadedb.plugins + +Enable plugins by adding a plugin entry under `arcadedb.plugins`. + +| Name | Description | Value | +|-------------------------------------------|---------------------------------------------------------|------------------------| +| `arcadedb.plugins` | Wire-protocol and metrics plugin configuration | `{}` | +| `arcadedb.plugins.bolt.enabled` | Enable Bolt protocol support | not set | +| `arcadedb.plugins.bolt.port` | Bolt protocol port | `7687` when enabled | +| `arcadedb.plugins.gremlin.enabled` | Enable Gremlin protocol support | not set | +| `arcadedb.plugins.gremlin.port` | Gremlin protocol port | `8182` when enabled | +| `arcadedb.plugins.postgres.enabled` | Enable PostgreSQL protocol support | not set | +| `arcadedb.plugins.postgres.port` | PostgreSQL protocol port | `5432` when enabled | +| `arcadedb.plugins.mongo.enabled` | Enable MongoDB protocol support | not set | +| `arcadedb.plugins.mongo.port` | MongoDB protocol port | `27017` when enabled | +| `arcadedb.plugins.redis.enabled` | Enable Redis protocol support | not set | +| `arcadedb.plugins.redis.port` | Redis protocol port | `6379` when enabled | +| `arcadedb.plugins.prometheus.enabled` | Enable Prometheus metrics support | not set | +| `arcadedb.plugins.prometheus.requireAuthentication` | Require authentication for Prometheus metrics | `true` when set | +| `arcadedb.plugins..enabled` | Enable a custom plugin | not set | +| `arcadedb.plugins..port` | Custom plugin port, or `false` for a portless plugin | required unless portless | +| `arcadedb.plugins..class` | Custom plugin implementation class | required | ### arcadedb.credentials -### arcadedb.credentials.rootPassword - -### arcadedb.credentials.secret - | Name | Description | Value | |-------------------------------------------------|-------------------------------|-------| | `arcadedb.credentials.rootPassword.secret.name` | Name of existing secret | `nil` | @@ -62,38 +96,64 @@ The command removes all the Kubernetes components associated with the chart and | `image.pullPolicy` | This sets the pull policy for images. | `IfNotPresent` | | `image.tag` | Overrides the image tag whose default is the chart appVersion. | `""` | | `imagePullSecrets` | This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ | `[]` | -| `nameOverride` | This is to override the chart name. | `""` | -| `fullnameOverride` | | `""` | -### This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +### serviceAccount -| Name | Description | Value | -|------------------------------|---------------------------------------------------------|--------| -| `serviceAccount.create` | Specifies whether a service account should be created | `true` | -| `serviceAccount.automount` | Mount the ServiceAccount token into pods. ArcadeDB does not call the Kubernetes API - keep false. | `false` | -| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | -| `serviceAccount.name` | The name of the service account to use. | `""` | -| `podAnnotations` | Annotations added to every pod. | `{}` | -| `podLabels` | Labels added to every pod. | `{}` | -| `podSecurityContext` | Pod-level security context. UID/GID 1000 matches the arcadedb user in the Docker image. | `{runAsNonRoot: true, fsGroup: 1000}` | -| `securityContext` | Container-level security context. | `{runAsUser: 1000, runAsGroup: 1000, allowPrivilegeEscalation: false, capabilities.drop: [ALL]}` | +| Name | Description | Value | +|------------------------------|------------------------------------------------------------------------------------------------------|--------| +| `serviceAccount.create` | Specifies whether a service account should be created | `true` | +| `serviceAccount.automount` | Mount the ServiceAccount token into pods. ArcadeDB does not call the Kubernetes API - keep false. | `false` | +| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | +| `serviceAccount.name` | The name of the service account to use. | `""` | -### This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +### Service ### http -| Name | Description | Value | -|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------| -| `service.http.type` | Service type. Use LoadBalancer or configure ingress for external access. | `ClusterIP` | -| `service.http.port` | This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports | `2480` | +| Name | Description | Value | +|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| `service.http.type` | Service type. Use LoadBalancer or configure ingress for external access. | `ClusterIP` | +| `service.http.port` | This sets the ports. More information: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports | `2480` | + +### https + +| Name | Description | Value | +|---------------------|-----------------------------|-------| +| `service.https.port` | HTTPS / Studio port | `2490` | ### rpc -| Name | Description | Value | -|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------| -| `service.rpc.port` | Raft gRPC port (ha-raft subsystem). | `2434` | +| Name | Description | Value | +|--------------------|--------------------------------------------------|--------| +| `service.rpc.port` | Raft gRPC port (ha-raft subsystem). | `2434` | + +### external + +| Name | Description | Value | +|----------------------------|--------------------------------------------------|----------------| +| `service.external.enabled` | Expose all protocol ports through an external Service | `false` | +| `service.external.type` | External Service type | `LoadBalancer` | -### ingress This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +### tls + +| Name | Description | Value | +|-----------------------------------|--------------------------------------------------|------------------------| +| `tls.enabled` | Enable TLS and mount the certificate Secret | `false` | +| `tls.bolt` | Bolt TLS mode: OPTIONAL or REQUIRED | `OPTIONAL` | +| `tls.mountPath` | Certificate mount path inside the container | `/etc/certs/arcadedb` | +| `tls.secretRef.name` | Secret containing TLS certificates | `arcadedb-tls` | +| `tls.secretRef.password` | Certificate store password | `$(rootPassword)` | +| `tls.secretRef.keyStore.key` | KeyStore key in the Secret | `keystore.p12` | +| `tls.secretRef.trustStore.format` | TrustStore format | `JKS` | +| `tls.secretRef.trustStore.key` | TrustStore key in the Secret | `truststore.jks` | +| `tls.certManager.enabled` | Create a cert-manager Certificate resource | `false` | +| `tls.certManager.extraDnsNames` | Adds extra DNS names to the TLS certificate | `[]` | +| `tls.certManager.issuerRef.kind` | cert-manager issuer kind | `ClusterIssuer` | +| `tls.certManager.issuerRef.name` | cert-manager issuer name | `my-issuer` | + +### ingress + +This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ | Name | Description | Value | |-----------------------|-------------|---------| @@ -114,37 +174,53 @@ The command removes all the Kubernetes components associated with the chart and | `ingress.hosts[0].paths[0].path` | | `/` | | `ingress.hosts[0].paths[0].pathType` | | `ImplementationSpecific` | | `ingress.tls` | | `[]` | -| `resources` | | `{}` | -### This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +### resources + +| Name | Description | Value | +|-------------|------------------------------|-------| +| `resources` | Resource requests and limits | `{}` | + +### livenessProbe + +This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ ### livenessProbe.httpGet -| Name | Description | Value | -|------------------------------|-------------|-----------------| +| Name | Description | Value | +|------------------------------|-------------|------------------| | `livenessProbe.httpGet.path` | | `/api/v1/health` | -| `livenessProbe.httpGet.port` | | `http` | +| `livenessProbe.httpGet.port` | | `http` | + +### readinessProbe -### This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ ### readinessProbe.httpGet -| Name | Description | Value | -|-------------------------------|-------------|-----------------| +| Name | Description | Value | +|-------------------------------|-------------|----------------| | `readinessProbe.httpGet.path` | | `/api/v1/ready` | -| `readinessProbe.httpGet.port` | | `http` | +| `readinessProbe.httpGet.port` | | `http` | -### This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +### autoscaling -| Name | Description | Value | -|----------------------------------------------|-----------------------------------------------------------------------|---------| -| `autoscaling.enabled` | Enable HorizontalPodAutoscaler. When enabled, server list is pre-sized to maxReplicas for KubernetesAutoJoin. | `false` | -| `autoscaling.minReplicas` | Minimum replicas. Must satisfy Raft quorum when HA is active: >= floor(maxReplicas/2)+1. | `1` | -| `autoscaling.maxReplicas` | Maximum replicas. Chart enforces quorum guard at render time. | `5` | -| `autoscaling.targetCPUUtilizationPercentage` | | `80` | -| `volumes` | Pod volumes, rendered verbatim. Defaults back every reserved `arcadedb-*` mount with an `emptyDir`. | see `values.yaml` | -| `volumeMounts` | Pod volume mounts, rendered verbatim. Defaults mount the reserved `arcadedb-*` volumes at the data / config / log / tmp / raft directories. | see `values.yaml` | -| `volumeClaimTemplates` | StatefulSet per-replica PVCs, rendered verbatim. Empty by default. | `[]` | +This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ + +| Name | Description | Value | +|----------------------------------------------|----------------------------------------------------------------------------------------------------------|---------| +| `autoscaling.enabled` | Enable HorizontalPodAutoscaler. Server list is pre-sized to maxReplicas for KubernetesAutoJoin. | `false` | +| `autoscaling.minReplicas` | Minimum replicas. Must satisfy Raft quorum: >= floor(maxReplicas/2)+1. | `1` | +| `autoscaling.maxReplicas` | Maximum replicas. Chart enforces quorum guard at render time. | `5` | +| `autoscaling.targetCPUUtilizationPercentage` | | `80` | + +### storage + +| Name | Description | Value | +|------------------------|------------------------------------------------------------|------------------| +| `volumeMounts` | Pod volume mounts, rendered verbatim. | see `values.yaml` | +| `volumes` | Pod volumes, rendered verbatim. | see `values.yaml` | +| `volumeClaimTemplates` | StatefulSet per-replica PVCs, rendered verbatim. Empty by default. | `[]` | ### persistence @@ -152,78 +228,90 @@ The command removes all the Kubernetes components associated with the chart and > `NOTES.txt`; they no longer create any PVC. Use `volumeClaimTemplates` instead > (see [Persistence](#persistence-1) below). -| Name | Description | Value | -|----------------------------|-----------------------------------------------------------------------------------------------------|-----------------| -| `persistence.enabled` | Persist the database directory with a PVC. Set false only for ephemeral/dev deployments. | `true` | -| `persistence.size` | PVC size. | `8Gi` | -| `persistence.accessMode` | PVC access mode. | `ReadWriteOnce` | -| `persistence.storageClass` | StorageClass name. Empty string uses the cluster default. | `""` | +| Name | Description | Value | +|--------------------------------|--------------------------------------------------------------------------------------------------|-----------------| +| `persistence.enabled` | Persist the database directory with a PVC. Set false only for ephemeral/dev deployments. | `true` | +| `persistence.size` | PVC size. | `8Gi` | +| `persistence.accessMode` | PVC access mode. | `ReadWriteOnce` | +| `persistence.storageClass` | StorageClass name. Empty string uses the cluster default. | `""` | +| `persistence.config.enabled` | Persist the config directory with a separate PVC. | `false` | +| `persistence.config.size` | PVC size for the config directory. | `1Gi` | +| `persistence.config.accessMode` | PVC access mode for the config directory. | `ReadWriteOnce` | +| `persistence.config.storageClass` | StorageClass name for the config PVC. Empty uses the cluster default. | `""` | ### networkPolicy -| Name | Description | Value | -|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------|---------| -| `networkPolicy.enabled` | Create NetworkPolicy resources. HTTP (2480) open to all cluster pods; Raft gRPC (2434) restricted to ArcadeDB pods only. | `false` | -| `nodeSelector` | | `{}` | -| `tolerations` | | `[]` | +| Name | Description | Value | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------|---------| +| `networkPolicy.enabled` | Create NetworkPolicy resources. Protocol ports are open to cluster traffic; Raft gRPC is restricted to ArcadeDB pods. | `false` | + +### scheduling + +| Name | Description | Value | +|----------------|------------------|-------| +| `nodeSelector` | Node selector | `{}` | +| `tolerations` | Pod tolerations | `[]` | ### affinity -### Set the anti-affinity selector scope to arcadedb servers. +Set the anti-affinity selector scope to arcadedb servers. + +| Name | Description | Value | +|---------------------------------------------------------------------------------------|-------------|-------| +| `affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight` | | `100` | -### preferredDuringSchedulingIgnoredDuringExecution +### extraManifests -| Name | Description | Value | -|--------------------------------------------------------------------------------------|---------------------------------------------------|-------| -| `affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight` | | `100` | -| `extraManifests` | - Include any amount of extra arbitrary manifests | `{}` | +| Name | Description | Value | +|------------------|-------------------------------------------------|-------| +| `extraManifests` | Include any amount of extra arbitrary manifests | `{}` | -### observability +## observability Opt-in, behavior-preserving observability (ArcadeDB 26.7.1+). Every knob below defaults off; existing deployments are unchanged. ### observability.metrics -| Name | Description | Value | -|-----------------------------------------------------------------------|----------------------------------------------------------|--------------------------| -| `observability.metrics.prometheus.serviceMonitor.enabled` | Create a Prometheus Operator ServiceMonitor | `false` | -| `observability.metrics.prometheus.serviceMonitor.interval` | Scrape interval | `30s` | -| `observability.metrics.prometheus.serviceMonitor.scrapeTimeout` | Scrape timeout (empty = Prometheus default) | `""` | -| `observability.metrics.prometheus.serviceMonitor.path` | Metrics path | `/prometheus` | -| `observability.metrics.prometheus.serviceMonitor.labels` | Extra labels (e.g. release: kube-prometheus-stack) | `{}` | -| `observability.metrics.prometheus.serviceMonitor.annotations` | Extra annotations | `{}` | -| `observability.metrics.prometheus.serviceMonitor.relabelings` | Prometheus relabelings | `[]` | -| `observability.metrics.prometheus.serviceMonitor.metricRelabelings` | Prometheus metric relabelings | `[]` | -| `observability.metrics.prometheus.serviceMonitor.basicAuth.enabled` | Scrape with basic auth | `false` | -| `observability.metrics.prometheus.serviceMonitor.basicAuth.secretName` | Secret with scrape credentials (username + password keys) | `""` | -| `observability.metrics.prometheus.serviceMonitor.basicAuth.usernameKey` | Secret key holding the username | `username` | -| `observability.metrics.prometheus.serviceMonitor.basicAuth.passwordKey` | Secret key holding the password | `password` | -| `observability.metrics.prometheus.podAnnotations.enabled` | Add prometheus.io/* scrape annotations to pods | `false` | -| `observability.metrics.prometheus.podAnnotations.path` | Scrape path annotation value | `/prometheus` | -| `observability.metrics.prometheus.podAnnotations.port` | Scrape port (empty = service.http.port) | `""` | -| `observability.metrics.otlp.enabled` | Enable the OTLP metrics registry | `false` | -| `observability.metrics.otlp.endpoint` | OTLP/gRPC metrics endpoint | `http://localhost:4317` | +| Name | Description | Value | +|-------------------------------------------------------------------------|---------------------------------------------------------------|-------------------------------| +| `observability.metrics.prometheus.serviceMonitor.enabled` | Create a Prometheus Operator ServiceMonitor | `false` | +| `observability.metrics.prometheus.serviceMonitor.interval` | Scrape interval | `30s` | +| `observability.metrics.prometheus.serviceMonitor.scrapeTimeout` | Scrape timeout (empty = Prometheus default) | `""` | +| `observability.metrics.prometheus.serviceMonitor.path` | Metrics path | `/prometheus` | +| `observability.metrics.prometheus.serviceMonitor.labels` | Extra labels (e.g. release: kube-prometheus-stack) | `{}` | +| `observability.metrics.prometheus.serviceMonitor.annotations` | Extra annotations | `{}` | +| `observability.metrics.prometheus.serviceMonitor.relabelings` | Prometheus relabelings | `[]` | +| `observability.metrics.prometheus.serviceMonitor.metricRelabelings` | Prometheus metric relabelings | `[]` | +| `observability.metrics.prometheus.serviceMonitor.basicAuth.enabled` | Scrape with basic auth | `false` | +| `observability.metrics.prometheus.serviceMonitor.basicAuth.secretName` | Secret with scrape credentials (username + password keys) | `""` | +| `observability.metrics.prometheus.serviceMonitor.basicAuth.usernameKey` | Secret key holding the username | `username` | +| `observability.metrics.prometheus.serviceMonitor.basicAuth.passwordKey` | Secret key holding the password | `password` | +| `observability.metrics.prometheus.podAnnotations.enabled` | Add prometheus.io/* scrape annotations to pods | `false` | +| `observability.metrics.prometheus.podAnnotations.path` | Scrape path annotation value | `/prometheus` | +| `observability.metrics.prometheus.podAnnotations.port` | Scrape port (empty = service.http.port) | `""` | +| `observability.metrics.otlp.enabled` | Enable the OTLP metrics registry | `false` | +| `observability.metrics.otlp.endpoint` | OTLP/HTTP metrics endpoint | `http://localhost:4318/v1/metrics` | ### observability.tracing -| Name | Description | Value | -|-----------------------------------|--------------------------------------|-------------------------| -| `observability.tracing.enabled` | Enable distributed tracing | `false` | -| `observability.tracing.endpoint` | OTLP/gRPC trace endpoint | `http://localhost:4317` | -| `observability.tracing.samplingRate` | Parent-based sampling ratio [0.0, 1.0] | `0.0` | +| Name | Description | Value | +|-----------------------------------|---------------------------------------|-------------------------| +| `observability.tracing.enabled` | Enable distributed tracing | `false` | +| `observability.tracing.endpoint` | OTLP/gRPC trace endpoint | `http://localhost:4317` | +| `observability.tracing.samplingRate` | Parent-based sampling ratio [0.0, 1.0] | `0.0` | ### observability.logging -| Name | Description | Value | -|-------------------------------------|----------------------------------------------------------|--------| -| `observability.logging.format` | Log format: text or json | `text` | +| Name | Description | Value | +|-------------------------------------|----------------------------------------------------------|---------| +| `observability.logging.format` | Log format: text or json | `text` | | `observability.logging.includeTrace` | Append [traceId=…] to text logs while a trace is active | `false` | ### observability.health -| Name | Description | Value | -|--------------------------------------------|---------------------------------------------------|---------| -| `observability.health.readinessRequiresHA` | /api/v1/ready waits for Raft join on HA clusters | `false` | +| Name | Description | Value | +|--------------------------------------------|--------------------------------------------------------------------------------------------------------------|---------| +| `observability.health.readinessRequiresHA` | /api/v1/ready waits for Raft join on HA clusters | `false` | | `observability.health.readinessHAMaxLag` | Max Raft log entries a follower may lag behind commit index and still report Ready (requires readinessRequiresHA) | `100` | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example: @@ -243,13 +331,13 @@ helm install my-arcadedb ./arcadedb -f values.yaml Storage is fully value-driven: `volumes`, `volumeMounts`, and `volumeClaimTemplates` are rendered verbatim into the StatefulSet. The chart ships five reserved volumes, mounted at the directories the server actually uses: -| Volume | Mount path | Contents | -|-------------------|---------------------------|-----------------------------------| -| `arcadedb-data` | `/home/arcadedb/databases`| Databases | -| `arcadedb-config` | `/home/arcadedb/config` | Users, tokens, server settings | -| `arcadedb-logs` | `/home/arcadedb/log` | Server logs | -| `arcadedb-tmp` | `/tmp` | Scratch space | -| `arcadedb-raft` | `/home/arcadedb/raft` | Raft state (HA) | +| Volume | Mount path | Contents | +|-------------------|----------------------------|-------------------------------| +| `arcadedb-data` | `/home/arcadedb/databases` | Databases | +| `arcadedb-config` | `/home/arcadedb/config` | Users, tokens, server settings | +| `arcadedb-logs` | `/home/arcadedb/log` | Server logs | +| `arcadedb-tmp` | `/tmp` | Scratch space | +| `arcadedb-raft` | `/home/arcadedb/raft` | Raft state (HA) | **All five default to `emptyDir`, so nothing survives a Pod restart out of the box.** To persist a directory, drop its `emptyDir` entry from `volumes` and declare a `volumeClaimTemplates` entry of the same name — a StatefulSet auto-mounts a @@ -305,18 +393,33 @@ environment variable, so it keeps resolving `config/` and `backups/` exactly whe Point `arcadedb.consoleWorkingDirectory` at another writable mount to keep the history elsewhere, or set it to `""` to drop both settings. -## Ingress +## TLS And Certificates. + +TLS can be enabled for those protocols that support it by setting `tls.enabled` to `true`. -This chart provides support for Ingress resource. To enable Ingress, set `ingress.enabled` to `true` and configure the +A key Store and trust Store are required from a secret. The keystore must be in `PKCS12` format. +The trust store defaults to `JKS` in line with Java defaults but `PKCS12` is supported - set `tls.secretRef.trustStore.format`. + +The chart supports using `cert-manager` to create a certificate with the correct stores. Set the `tls.certManager` values. The stores will use the root password. + +## Ingress And External Access + +This chart provides support for exposing ArcadeDB outside the cluster: + +1. Ingress resource. To enable Ingress, set `ingress.enabled` to `true` and configure the `ingress.hosts` parameter. For example: -```yaml -ingress: - enabled: true - hosts: - - host: arcadedb.local - paths: [ ] -``` + ```yaml + ingress: + enabled: true + hosts: + - host: arcadedb.local + paths: [ ] + ``` + +1. HTTP service as `NodePort` or `LoadBalancer`. Set `service.http.type` accordingly. +1. All protocol ports as `NodePort` or `LoadBalancer`. Set `service.external.enabled` to `true`. +This exposes HTTP, HTTPS (if `tls.enabled` is `true`) and all plugins with a `port`. ## Resources diff --git a/charts/arcadedb/templates/_helpers.tpl b/charts/arcadedb/templates/_helpers.tpl index da80a76..9fca37b 100644 --- a/charts/arcadedb/templates/_helpers.tpl +++ b/charts/arcadedb/templates/_helpers.tpl @@ -83,10 +83,8 @@ KubernetesAutoJoin can resolve any pod ordinal up to the maximum scale. {{- $names := list -}} {{- $fullname := (include "arcadedb.fullname" .) -}} {{- $k8sSuffix := (include "arcadedb.k8sSuffix" .) -}} -{{- $rpcPort := int .Values.service.rpc.port -}} -{{- $httpPort := int .Values.service.http.port -}} {{- range $i, $_ := until $replicas }} -{{- $names = append $names (printf "%s-%d%s:%d:%d" $fullname $i $k8sSuffix $rpcPort $httpPort) }} +{{- $names = append $names (printf "%s-%d%s" $fullname $i $k8sSuffix) }} {{- end }} {{- join "," $names -}} {{- end }} @@ -98,7 +96,9 @@ Preparing a list of plugin ports to build plugin configurations. {{- range $plugin, $config := .Values.arcadedb.plugins -}} {{- if $config.enabled }} {{- $port := int 0}} - {{- if eq $plugin "gremlin" }} + {{- if eq $plugin "bolt" }} + {{- $port = default 7687 $config.port }} + {{- else if eq $plugin "gremlin" }} {{- $port = default 8182 $config.port }} {{- else if eq $plugin "postgres" }} {{- $port = default 5432 $config.port }} @@ -141,7 +141,10 @@ Create a comma separated list of plugins to be enabled in arcadedb {{- $plugins := list -}} {{- $params := list -}} {{- range $plugin, $config := (include "_arcadedb.plugin.ports" . | fromYaml) -}} - {{- if eq $plugin "gremlin" -}} + {{- if eq $plugin "bolt" -}} + {{- $plugins = append $plugins "Bolt:com.arcadedb.bolt.BoltProtocolPlugin" -}} + {{- $params = append $params (printf "-Darcadedb.bolt.port=%d" (int $config.port)) -}} + {{- else if eq $plugin "gremlin" -}} {{- $plugins = append $plugins "GremlinServer:com.arcadedb.server.gremlin.GremlinServerPlugin" -}} {{- $params = append $params (printf "-Darcadedb.gremlin.port=%d" (int $config.port)) -}} {{- else if eq $plugin "postgres" -}} @@ -196,6 +199,19 @@ Create service configuration for the enabled plugins {{- end -}} {{- end -}} +{{/* +Create network policy configuration for the enabled plugins +*/}} +{{- define "arcadedb.plugin.networkPolicy" -}} + {{- $plugins := (include "_arcadedb.plugin.ports" . | fromYaml) }} + {{- range $plugin, $config := $plugins }} + {{- if (gt (int $config.port) 0) }} +- port: {{ $config.port }} + protocol: TCP + {{- end -}} + {{- end -}} +{{- end -}} + {{/* Observability -D JVM args (logging, OTLP metrics, tracing, readiness). All opt-in; emits nothing when defaults are unchanged. @@ -227,6 +243,34 @@ All opt-in; emits nothing when defaults are unchanged. {{- end }} {{- end -}} +{{/* +TLS parameters +*/}} +{{- define "arcadedb.tls.parameters" -}} +{{- if .Values.tls.enabled }} +- -Darcadedb.ssl.enabled=true +- -Darcadedb.server.httpsIncomingPort={{ .Values.service.https.port }} + {{- if and (hasKey .Values.arcadedb.plugins "bolt") .Values.arcadedb.plugins.bolt.enabled }} +- -Darcadedb.bolt.ssl={{ .Values.tls.bolt }} + {{- end }} + {{- $keyStoreKey := .Values.tls.secretRef.keyStore.key }} + {{- $trustStoreKey := .Values.tls.secretRef.trustStore.key }} + {{- $trustStoreFormat := .Values.tls.secretRef.trustStore.format }} + {{- if .Values.tls.certManager.enabled }} + {{- $keyStoreKey = "keystore.p12" -}} + {{- $trustStoreFormat = "PKCS12" -}} + {{- $trustStoreKey = "truststore.p12" -}} + {{- end }} + {{- if ne "JKS" $trustStoreFormat }} +- -Djavax.net.ssl.trustStoreType={{ $trustStoreFormat }} + {{- end }} +- -Darcadedb.ssl.keyStore={{ printf "%s/%s" .Values.tls.mountPath $keyStoreKey }} +- -Darcadedb.ssl.keyStorePassword={{ .Values.tls.secretRef.password }} +- -Darcadedb.ssl.trustStore={{ printf "%s/%s" .Values.tls.mountPath $trustStoreKey }} +- -Darcadedb.ssl.trustStorePassword={{ .Values.tls.secretRef.password }} +{{- end }} +{{- end -}} + {{/* Guard: scrape discovery (ServiceMonitor or pod annotations) needs the prometheus plugin so /prometheus is actually served. diff --git a/charts/arcadedb/templates/certificate.yaml b/charts/arcadedb/templates/certificate.yaml new file mode 100644 index 0000000..35a6c19 --- /dev/null +++ b/charts/arcadedb/templates/certificate.yaml @@ -0,0 +1,38 @@ +{{- if and .Values.tls.enabled .Values.tls.certManager.enabled }} +kind: Certificate +apiVersion: cert-manager.io/v1 +metadata: + name: {{ include "arcadedb.fullname" . }} + labels: + {{- include "arcadedb.labels" . | nindent 4 }} +spec: + dnsNames: + - {{ include "arcadedb.fullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local + {{- $nodes := (include "arcadedb.nodenames" .) -}} + {{- range $n := split "," $nodes }} + - {{ $n }} + {{- end }} + {{- with .Values.tls.certManager.extraDnsNames }} + {{- toYaml . | nindent 4 }} + {{- end }} + subject: + organizations: + - {{ include "arcadedb.fullname" . }} + isCA: false + usages: + - server auth + - client auth + secretName: {{ .Values.tls.secretRef.name }} + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + kind: {{ .Values.tls.certManager.issuerRef.kind }} + name: {{ .Values.tls.certManager.issuerRef.name }} + keystores: + pkcs12: + create: true + passwordSecretRef: + name: arcadedb-credentials-secret + key: rootPassword +{{- end }} diff --git a/charts/arcadedb/templates/networkpolicy.yaml b/charts/arcadedb/templates/networkpolicy.yaml index 27f5d33..1c2b8fa 100644 --- a/charts/arcadedb/templates/networkpolicy.yaml +++ b/charts/arcadedb/templates/networkpolicy.yaml @@ -2,7 +2,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: {{ include "arcadedb.fullname" . }}-http + name: {{ include "arcadedb.fullname" . }}-protocols labels: {{- include "arcadedb.labels" . | nindent 4 }} spec: @@ -15,6 +15,11 @@ spec: - ports: - port: {{ .Values.service.http.port }} protocol: TCP + {{- if .Values.tls.enabled }} + - port: {{ .Values.service.https.port }} + protocol: TCP + {{- end }} + {{- include "arcadedb.plugin.networkPolicy" . | nindent 8 }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy diff --git a/charts/arcadedb/templates/service.yaml b/charts/arcadedb/templates/service.yaml index d5a4df1..64c8230 100644 --- a/charts/arcadedb/templates/service.yaml +++ b/charts/arcadedb/templates/service.yaml @@ -36,6 +36,12 @@ spec: targetPort: http protocol: TCP name: http + {{- if .Values.tls.enabled }} + - port: {{ .Values.service.https.port }} + targetPort: https + protocol: TCP + name: https + {{- end }} - port: {{ .Values.service.rpc.port }} targetPort: rpc protocol: TCP @@ -43,3 +49,35 @@ spec: {{- include "arcadedb.plugin.service" . | nindent 4 }} selector: {{- include "arcadedb.selectorLabels" . | nindent 4 }} +--- +{{/* +External service for exposing all protocol ports outside the cluster. +*/}} +{{- if .Values.service.external.enabled }} +{{- if ne .Values.service.http.type "ClusterIP" }} +{{- fail "http.type must be ClusterIP when external service is enabled" -}} +{{- end }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "arcadedb.fullname" . }}-external + labels: + {{- include "arcadedb.labels" . | nindent 4 }} + app.kubernetes.io/component: external +spec: + type: {{ .Values.service.external.type }} + ports: + - port: {{ .Values.service.http.port }} + targetPort: http + protocol: TCP + name: http + {{- if .Values.tls.enabled }} + - port: {{ .Values.service.https.port }} + targetPort: https + protocol: TCP + name: https + {{- end }} + {{- include "arcadedb.plugin.service" . | nindent 4 }} + selector: + {{- include "arcadedb.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/arcadedb/templates/statefulset.yaml b/charts/arcadedb/templates/statefulset.yaml index bdeb21e..2e6dfb5 100644 --- a/charts/arcadedb/templates/statefulset.yaml +++ b/charts/arcadedb/templates/statefulset.yaml @@ -3,8 +3,15 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ include "arcadedb.fullname" . }} + {{- with .Values.statefulSetAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} labels: {{- include "arcadedb.labels" . | nindent 4 }} + {{- with .Values.statefulSetLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: serviceName: {{ include "arcadedb.fullname" . }} podManagementPolicy: Parallel @@ -47,6 +54,11 @@ spec: - name: http containerPort: {{ .Values.service.http.port }} protocol: TCP + {{- if .Values.tls.enabled }} + - name: https + containerPort: {{ .Values.service.https.port }} + protocol: TCP + {{- end }} - name: rpc containerPort: {{ .Values.service.rpc.port }} protocol: TCP @@ -64,6 +76,7 @@ spec: {{- end }} - -Darcadedb.dumpConfigAtStartup=true - -Darcadedb.server.name=$(HOSTNAME) + - -Darcadedb.server.httpIncomingPort={{ .Values.service.http.port }} - -Darcadedb.server.rootPassword=$(rootPassword) - -Darcadedb.server.databaseDirectory={{ .Values.arcadedb.databaseDirectory }} - -Darcadedb.server.defaultDatabases={{ .Values.arcadedb.defaultDatabases }} @@ -80,6 +93,7 @@ spec: {{- end }} {{- include "arcadedb.plugin.parameters" . | nindent 12 }} {{- include "arcadedb.observability.args" . | nindent 12 }} + {{- include "arcadedb.tls.parameters" . | nindent 12 }} {{- with .Values.livenessProbe }} livenessProbe: {{- toYaml . | nindent 12 }} @@ -92,10 +106,15 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.volumeMounts }} volumeMounts: - {{- toYaml . | nindent 12 }} - {{- end }} + {{- if .Values.tls.enabled }} + - name: tls-certs + mountPath: {{ .Values.tls.mountPath }} + readOnly: true + {{- end }} + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} env: - name: HOSTNAME valueFrom: @@ -127,10 +146,15 @@ spec: {{- with .Values.arcadedb.extraEnvironment }} {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.volumes }} volumes: - {{- toYaml . | nindent 8 }} - {{- end }} + {{- if .Values.tls.enabled }} + - name: tls-certs + secret: + secretName: {{ .Values.tls.secretRef.name }} + {{- end }} + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/arcadedb/tests/certificate_test.yaml b/charts/arcadedb/tests/certificate_test.yaml new file mode 100644 index 0000000..75446ae --- /dev/null +++ b/charts/arcadedb/tests/certificate_test.yaml @@ -0,0 +1,95 @@ +suite: Certificate +templates: + - certificate.yaml +release: + name: test + namespace: default +tests: + - it: is not rendered unless TLS and cert-manager are enabled + asserts: + - hasDocuments: + count: 0 + + - it: renders the cert-manager Certificate with service and StatefulSet DNS names + set: + tls: + enabled: true + secretRef: + name: custom-arcadedb-tls + certManager: + enabled: true + issuerRef: + kind: Issuer + name: arcadedb-issuer + replicaCount: 3 + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Certificate + - equal: + path: apiVersion + value: cert-manager.io/v1 + - equal: + path: metadata.name + value: test-arcadedb + - contains: + path: spec.dnsNames + content: test-arcadedb.default.svc.cluster.local + - contains: + path: spec.dnsNames + content: test-arcadedb-0.test-arcadedb.default.svc.cluster.local + - contains: + path: spec.dnsNames + content: test-arcadedb-1.test-arcadedb.default.svc.cluster.local + - contains: + path: spec.dnsNames + content: test-arcadedb-2.test-arcadedb.default.svc.cluster.local + - equal: + path: spec.subject.organizations[0] + value: test-arcadedb + - equal: + path: spec.isCA + value: false + - contains: + path: spec.usages + content: server auth + - contains: + path: spec.usages + content: client auth + - equal: + path: spec.secretName + value: custom-arcadedb-tls + - equal: + path: spec.privateKey.algorithm + value: ECDSA + - equal: + path: spec.privateKey.size + value: 256 + - equal: + path: spec.issuerRef.kind + value: Issuer + - equal: + path: spec.issuerRef.name + value: arcadedb-issuer + - equal: + path: spec.keystores.pkcs12.create + value: true + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.name + value: arcadedb-credentials-secret + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.key + value: rootPassword + + - it: includes an extra DNS name in the cert-manager Certificate + set: + tls: + enabled: true + certManager: + enabled: true + extraDnsNames: ["extra-dns-name.local"] + asserts: + - contains: + path: spec.dnsNames + content: extra-dns-name.local diff --git a/charts/arcadedb/tests/helpers_test.yaml b/charts/arcadedb/tests/helpers_test.yaml index 865a1fe..2a3b3c8 100644 --- a/charts/arcadedb/tests/helpers_test.yaml +++ b/charts/arcadedb/tests/helpers_test.yaml @@ -35,7 +35,7 @@ tests: asserts: - contains: path: spec.template.spec.containers[0].command - content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-2.test-arcadedb.default.svc.cluster.local:2434:2480" + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local,test-arcadedb-1.test-arcadedb.default.svc.cluster.local,test-arcadedb-2.test-arcadedb.default.svc.cluster.local" - it: arcadedb.nodenames sizes to autoscaling.maxReplicas when HPA enabled and larger than replicaCount set: @@ -46,16 +46,7 @@ tests: asserts: - contains: path: spec.template.spec.containers[0].command - content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-2.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-3.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-4.test-arcadedb.default.svc.cluster.local:2434:2480" - - - it: arcadedb.nodenames uses custom rpc port - set: - replicaCount: 2 - service.rpc.port: 5555 - asserts: - - contains: - path: spec.template.spec.containers[0].command - content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:5555:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:5555:2480" + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local,test-arcadedb-1.test-arcadedb.default.svc.cluster.local,test-arcadedb-2.test-arcadedb.default.svc.cluster.local,test-arcadedb-3.test-arcadedb.default.svc.cluster.local,test-arcadedb-4.test-arcadedb.default.svc.cluster.local" - it: arcadedb.plugin.parameters emits gremlin plugin entry and port set: @@ -69,6 +60,17 @@ tests: path: spec.template.spec.containers[0].command content: "-Darcadedb.gremlin.port=8182" + - it: arcadedb.plugin.parameters emits bolt plugin entry and port + set: + arcadedb.plugins.bolt.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.server.plugins=Bolt:com.arcadedb.bolt.BoltProtocolPlugin" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.bolt.port=7687" + - it: arcadedb.plugin.parameters emits postgres plugin entry and port set: arcadedb.plugins.postgres.enabled: true @@ -104,11 +106,11 @@ tests: - it: custom plugin without class fails the template render set: - arcadedb.plugins.bolt.enabled: true - arcadedb.plugins.bolt.port: 7687 + arcadedb.plugins.myplugin.enabled: true + arcadedb.plugins.myplugin.port: 1234 asserts: - failedTemplate: - errorPattern: "Custom plugin 'bolt' has no class specified." + errorMessage: "Custom plugin 'myplugin' has no class specified." - it: custom plugin with port=false renders without a port param and does not fail set: diff --git a/charts/arcadedb/tests/networkpolicy_test.yaml b/charts/arcadedb/tests/networkpolicy_test.yaml index c2d4d39..283d8aa 100644 --- a/charts/arcadedb/tests/networkpolicy_test.yaml +++ b/charts/arcadedb/tests/networkpolicy_test.yaml @@ -26,7 +26,7 @@ tests: of: NetworkPolicy - equal: path: metadata.name - value: test-arcadedb-http + value: test-arcadedb-protocols - equal: path: spec.podSelector.matchLabels["app.kubernetes.io/name"] value: arcadedb @@ -81,6 +81,21 @@ tests: path: spec.ingress[0].ports[0].port value: 9090 + - it: protocols policy includes HTTPS and enabled plugin ports + set: + networkPolicy.enabled: true + tls.enabled: true + arcadedb.plugins.gremlin.enabled: true + arcadedb.plugins.gremlin.port: 8182 + documentIndex: 0 + asserts: + - contains: + path: spec.ingress[0].ports + content: { port: 2490, protocol: TCP } + - contains: + path: spec.ingress[0].ports + content: { port: 8182, protocol: TCP } + - it: raft policy port reflects custom service.rpc.port set: networkPolicy.enabled: true diff --git a/charts/arcadedb/tests/service_test.yaml b/charts/arcadedb/tests/service_test.yaml index f86ddc3..2bd98f5 100644 --- a/charts/arcadedb/tests/service_test.yaml +++ b/charts/arcadedb/tests/service_test.yaml @@ -78,6 +78,15 @@ tests: path: spec.ports content: { port: 5000, targetPort: rpc, protocol: TCP, name: rpc } + - it: headless service exposes HTTPS when TLS is enabled + set: + tls.enabled: true + documentIndex: 1 + asserts: + - contains: + path: spec.ports + content: { port: 2490, targetPort: https, protocol: TCP, name: https } + - it: headless service exposes gremlin plugin port when enabled set: arcadedb.plugins.gremlin.enabled: true @@ -158,3 +167,36 @@ tests: - equal: path: spec.selector["app.kubernetes.io/instance"] value: test + + - it: external service exposes configured protocol ports + set: + service.external.enabled: true + service.external.type: NodePort + tls.enabled: true + arcadedb.plugins.gremlin.enabled: true + arcadedb.plugins.gremlin.port: 8182 + documentIndex: 2 + asserts: + - isKind: { of: Service } + - equal: { path: metadata.name, value: test-arcadedb-external } + - equal: + path: metadata.labels["app.kubernetes.io/component"] + value: external + - equal: { path: spec.type, value: NodePort } + - contains: + path: spec.ports + content: { port: 2480, targetPort: http, protocol: TCP, name: http } + - contains: + path: spec.ports + content: { port: 2490, targetPort: https, protocol: TCP, name: https } + - contains: + path: spec.ports + content: { port: 8182, targetPort: 8182, protocol: TCP, name: gremlin-port } + + - it: external service fails when the client service is not ClusterIP + set: + service.external.enabled: true + service.http.type: LoadBalancer + asserts: + - failedTemplate: + errorMessage: "http.type must be ClusterIP when external service is enabled" diff --git a/charts/arcadedb/tests/statefulset_test.yaml b/charts/arcadedb/tests/statefulset_test.yaml index 7322ec1..c329efe 100644 --- a/charts/arcadedb/tests/statefulset_test.yaml +++ b/charts/arcadedb/tests/statefulset_test.yaml @@ -165,6 +165,88 @@ tests: asserts: - equal: { path: spec.template.spec.serviceAccountName, value: my-sa } + - it: StatefulSet metadata accepts custom annotations and labels + set: + statefulSetAnnotations: + example.com/owner: platform + statefulSetLabels: + app.kubernetes.io/component: database + asserts: + - equal: + path: metadata.annotations["example.com/owner"] + value: platform + - equal: + path: metadata.labels["app.kubernetes.io/component"] + value: database + + - it: TLS adds HTTPS port, certificate mounts, and SSL startup arguments + set: + tls.enabled: true + tls.bolt: REQUIRED + arcadedb.plugins.bolt.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: { name: https, containerPort: 2490, protocol: TCP } + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: tls-certs + mountPath: /etc/certs/arcadedb + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: tls-certs + secret: + secretName: arcadedb-tls + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.enabled=true" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.server.httpsIncomingPort=2490" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.bolt.ssl=REQUIRED" + + - it: TLS volume mount uses the configured path and secret + set: + tls.enabled: true + tls.mountPath: /var/run/arcadedb-tls + tls.secretRef.name: custom-arcadedb-tls + asserts: + - equal: + path: spec.template.spec.containers[0].volumeMounts[0].name + value: tls-certs + - equal: + path: spec.template.spec.containers[0].volumeMounts[0].mountPath + value: /var/run/arcadedb-tls + - equal: + path: spec.template.spec.containers[0].volumeMounts[0].readOnly + value: true + - equal: + path: spec.template.spec.volumes[0].name + value: tls-certs + - equal: + path: spec.template.spec.volumes[0].secret.secretName + value: custom-arcadedb-tls + + - it: cert-manager TLS uses PKCS12 certificate store names + set: + tls.enabled: true + tls.certManager.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Djavax.net.ssl.trustStoreType=PKCS12" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.keyStore=/etc/certs/arcadedb/keystore.p12" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.trustStore=/etc/certs/arcadedb/truststore.p12" + - it: env contains HOSTNAME from metadata.name field reference asserts: - contains: diff --git a/charts/arcadedb/values.yaml b/charts/arcadedb/values.yaml index 777f3c4..f0876dd 100644 --- a/charts/arcadedb/values.yaml +++ b/charts/arcadedb/values.yaml @@ -55,6 +55,9 @@ arcadedb: ## @section arcadedb.plugins ## Uncomment and configure to enable wire-protocol plugins. plugins: {} + # bolt: + # enabled: true + # port: 7687 # gremlin: # enabled: true # port: 8182 @@ -144,10 +147,72 @@ service: type: ClusterIP ## @param service.http.port HTTP / Studio port port: 2480 + ## @section service.https + https: + ## @param service.https.port HTTPS / Studio port + port: 2490 ## @section service.rpc rpc: ## @param service.rpc.port Raft gRPC port (new ha-raft subsystem). Was 2424 in old binary-protocol HA. port: 2434 + ## @section service.external + external: + ## @param service.external.enabled Expose all protocol ports externally. Use instead of http.type + enabled: false + ## @param service.external.type External service type + type: LoadBalancer + +## @param statefulSetAnnotations Annotations added to the statefulset +statefulSetAnnotations: {} + +## @param statefulSetLabels Labels added to the statefulset +statefulSetLabels: {} + +## @section tls +tls: + ## @param tls.enabled Enable TLS + enabled: false + + ## @param tls.bolt Bolt TLS option. OPTIONAL or REQUIRED + bolt: OPTIONAL + + ## @param tls.mountPath Path where TLS certificates are mounted in the container. + mountPath: /etc/certs/arcadedb + + ## @section tls.secretRef + secretRef: + ## @param tls.secretRef.name Name of the secret containing TLS certificates + name: arcadedb-tls + + ## @param tls.secretRef.password Password for the certificate stores + password: $(rootPassword) + + ## @section tls.secretRef.keyStore + keyStore: + ## @param tls.secretRef.keyStore.key Key in the secret containing the key store + key: keystore.p12 + + ## @section tls.secretRef.trustStore + trustStore: + ## @param tls.secretRef.trustStore.format Override the trust store default format (JKS) by setting `javax.net.ssl.trustStoreType` (e.g. PKCS12) + format: JKS + ## @param tls.secretRef.trustStore.key Key in the secret containing the trust store + key: truststore.jks + + ## @section tls.certManager + certManager: + ## @param tls.certManager.enabled Generate and manage TLS certificates using cert-manager + enabled: false + + ## @param tls.certManager.extraDnsNames Adds extra DNS names to the TLS certificate + extraDnsNames: [] + + ## @section tls.certManager.issuerRef + issuerRef: + ## @param tls.certManager.issuerRef.kind Kind of the cert-manager issuer + kind: ClusterIssuer + ## @param tls.certManager.issuerRef.name Name of the cert-manager issuer + name: my-issuer ## @section ingress ingress: @@ -359,8 +424,8 @@ observability: otlp: ## @param observability.metrics.otlp.enabled Enable the OTLP metrics registry enabled: false - ## @param observability.metrics.otlp.endpoint OTLP/gRPC metrics endpoint - endpoint: http://localhost:4317 + ## @param observability.metrics.otlp.endpoint OTLP/http metrics endpoint + endpoint: http://localhost:4318/v1/metrics ## @section observability.tracing tracing: ## @param observability.tracing.enabled Enable distributed tracing (plugin ships in the standard image) From 2ac64925996bcc8baddc8475bb81a45b6a2384aa Mon Sep 17 00:00:00 2001 From: Richard Lavey <6595659+rlaveycal@users.noreply.github.com> Date: Mon, 21 Sep 2026 10:27:18 +0100 Subject: [PATCH 2/3] set env vars for pid and cache directory add backup and mcp config support --- charts/arcadedb/README.md | 6 ++-- charts/arcadedb/templates/statefulset.yaml | 26 ++++++++++++++++ charts/arcadedb/tests/statefulset_test.yaml | 33 +++++++++++++++++++++ charts/arcadedb/values.yaml | 4 +++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/charts/arcadedb/README.md b/charts/arcadedb/README.md index 78687c4..d9f4b55 100644 --- a/charts/arcadedb/README.md +++ b/charts/arcadedb/README.md @@ -48,14 +48,16 @@ The command removes all the Kubernetes components associated with the chart and | Name | Description | Value | |----------------------------------------|--------------------------------------------------------------------|------------------------------------------| | `arcadedb.configDirectory` | Config storage directory inside the container | `/home/arcadedb/config` | +| `arcadedb.backupConfigMap` | Name of the config map containing a [backup.json](https://docs.arcadedb.com/arcadedb/how-to/operations/auto-backup#configuration-file) configuration | | +| `arcadedb.mcpConfigMap` | Name of the config map containing a [mcp-config.json](https://docs.arcadedb.com/arcadedb/reference/mcp/mcp#configuration) configuration | | | `arcadedb.databaseDirectory` | Database storage directory inside the container | `/home/arcadedb/databases` | | `arcadedb.defaultDatabases` | Databases to create at startup. Empty = none. | `""` | | `arcadedb.extraCommands` | Extra JVM -D arguments appended to the startup command | `["-Darcadedb.server.mode=production"]` | | `arcadedb.extraEnvironment` | Additional environment variables to pass to the ArcadeDB container | `[]` | | `arcadedb.logsDirectory` | Directory where the server writes log files | `/home/arcadedb/log` | | `arcadedb.installDirectory` | Directory the ArcadeDB distribution lives in inside the image | `/home/arcadedb` | -| `arcadedb.consoleWorkingDirectory` | Writable working directory for interactive tools (console) | `/tmp` | -| `arcadedb.ha.raftStorageDirectory` | Parent directory for per-node Raft storage | `/home/arcadedb/raft` | +| `arcadedb.consoleWorkingDirectory` | Writable working directory for interactive tools (console) | `/tmp` | +| `arcadedb.ha.raftStorageDirectory` | Parent directory for per-node Raft storage | `/home/arcadedb/raft` | ### arcadedb.plugins diff --git a/charts/arcadedb/templates/statefulset.yaml b/charts/arcadedb/templates/statefulset.yaml index 2e6dfb5..2a2a57f 100644 --- a/charts/arcadedb/templates/statefulset.yaml +++ b/charts/arcadedb/templates/statefulset.yaml @@ -107,6 +107,18 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} volumeMounts: + {{- if .Values.arcadedb.backupConfigMap }} + - name: arcadedb-config-backup-json + mountPath: {{ .Values.arcadedb.configDirectory }}/backup.json + subPath: backup.json + readOnly: true + {{- end }} + {{- if .Values.arcadedb.mcpConfigMap }} + - name: arcadedb-config-mcp-json + mountPath: {{ .Values.arcadedb.configDirectory }}/mcp-config.json + subPath: mcp-config.json + readOnly: true + {{- end }} {{- if .Values.tls.enabled }} - name: tls-certs mountPath: {{ .Values.tls.mountPath }} @@ -139,6 +151,10 @@ spec: {{- end }} - name: ARCADEDB_LOG_DIR value: {{ .Values.arcadedb.logsDirectory | quote }} + - name: ARCADEDB_PID + value: /tmp/arcadedb.pid + - name: XDG_CACHE_HOME + value: /tmp/.cache {{- with .Values.arcadedb.consoleWorkingDirectory }} - name: ARCADEDB_SETTINGS value: {{ printf "-Duser.dir=%s" . | quote }} @@ -147,6 +163,16 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} volumes: + {{- if .Values.arcadedb.backupConfigMap }} + - name: arcadedb-config-backup-json + configMap: + name: {{ .Values.arcadedb.backupConfigMap }} + {{- end }} + {{- if .Values.arcadedb.mcpConfigMap }} + - name: arcadedb-config-mcp-json + configMap: + name: {{ .Values.arcadedb.mcpConfigMap }} + {{- end }} {{- if .Values.tls.enabled }} - name: tls-certs secret: diff --git a/charts/arcadedb/tests/statefulset_test.yaml b/charts/arcadedb/tests/statefulset_test.yaml index c329efe..950e412 100644 --- a/charts/arcadedb/tests/statefulset_test.yaml +++ b/charts/arcadedb/tests/statefulset_test.yaml @@ -606,6 +606,39 @@ tests: name: arcadedb-config emptyDir: {} + - it: backupConfigMap and mcpConfigMap render read-only JSON mounts from named ConfigMaps + set: + arcadedb.configDirectory: /custom/config + arcadedb.backupConfigMap: backup + arcadedb.mcpConfigMap: mcp + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: arcadedb-config-backup-json + mountPath: /custom/config/backup.json + subPath: backup.json + readOnly: true + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: arcadedb-config-mcp-json + mountPath: /custom/config/mcp-config.json + subPath: mcp-config.json + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: arcadedb-config-backup-json + configMap: + name: backup + - contains: + path: spec.template.spec.volumes + content: + name: arcadedb-config-mcp-json + configMap: + name: mcp + - it: log dir is wired via ARCADEDB_LOG_DIR and backed by a writable emptyDir asserts: - contains: diff --git a/charts/arcadedb/values.yaml b/charts/arcadedb/values.yaml index f0876dd..8c5555c 100644 --- a/charts/arcadedb/values.yaml +++ b/charts/arcadedb/values.yaml @@ -7,6 +7,10 @@ arcadedb: databaseDirectory: "/home/arcadedb/databases" ## @param arcadedb.configDirectory Config storage directory inside the container (users, tokens, server settings) configDirectory: "/home/arcadedb/config" + ## @param arcadedb.backupConfigMap ConfigMap containing the backup.json file + backupConfigMap: null + ## @param arcadedb.mcpConfigMap ConfigMap containing the mcp-config.json file + mcpConfigMap: null ## @param arcadedb.defaultDatabases Databases to create at startup. Empty = none. ## Example: "Universe[admin:password]" defaultDatabases: "" From 61c89b74195cec1053ac6e72b0ba7d29bc5ba78d Mon Sep 17 00:00:00 2001 From: robfrank Date: Tue, 6 Oct 2026 09:10:50 +0200 Subject: [PATCH 3/3] fix(tls): harden cert-manager TLS wiring from PR review - Certificate keystore password follows the configured root password secret, or a dedicated tls.secretRef.passwordSecret - Store passwords injected via a secretKeyRef env var, not inline args - issuerProvidesCA=false falls back to keystore.p12 as trust store for issuers that return no ca.crt (truststore.p12 is otherwise missing) - Certificate SANs cover the -http and -external services - Expose HTTPS on the -http service - TLS secret defaults to release-scoped -tls; issuer name is required instead of a placeholder - Restore host:raftPort:httpPort in ha.serverList (plus :0:httpsPort with TLS); certificate uses a separate port-less nodehosts helper - Keep the -http NetworkPolicy name, document opened ports - PID/XDG cache paths follow arcadedb.tmpDirectory - Only emit the external Service separator when it is rendered Co-Authored-By: Claude Opus 5.5 --- charts/arcadedb/README.md | 17 +++-- charts/arcadedb/templates/_helpers.tpl | 60 ++++++++++++++-- charts/arcadedb/templates/certificate.yaml | 27 ++++--- charts/arcadedb/templates/networkpolicy.yaml | 2 +- charts/arcadedb/templates/service.yaml | 10 ++- charts/arcadedb/templates/statefulset.yaml | 15 +++- charts/arcadedb/tests/certificate_test.yaml | 70 +++++++++++++++++++ charts/arcadedb/tests/helpers_test.yaml | 22 +++++- charts/arcadedb/tests/networkpolicy_test.yaml | 4 +- charts/arcadedb/tests/service_test.yaml | 11 ++- charts/arcadedb/tests/statefulset_test.yaml | 60 +++++++++++++++- charts/arcadedb/values.yaml | 33 ++++++--- 12 files changed, 292 insertions(+), 39 deletions(-) diff --git a/charts/arcadedb/README.md b/charts/arcadedb/README.md index d9f4b55..20812b8 100644 --- a/charts/arcadedb/README.md +++ b/charts/arcadedb/README.md @@ -57,6 +57,7 @@ The command removes all the Kubernetes components associated with the chart and | `arcadedb.logsDirectory` | Directory where the server writes log files | `/home/arcadedb/log` | | `arcadedb.installDirectory` | Directory the ArcadeDB distribution lives in inside the image | `/home/arcadedb` | | `arcadedb.consoleWorkingDirectory` | Writable working directory for interactive tools (console) | `/tmp` | +| `arcadedb.tmpDirectory` | Writable dir for the PID file and `XDG_CACHE_HOME` (`""` = image defaults) | `/tmp` | | `arcadedb.ha.raftStorageDirectory` | Parent directory for per-node Raft storage | `/home/arcadedb/raft` | ### arcadedb.plugins @@ -143,15 +144,17 @@ Enable plugins by adding a plugin entry under `arcadedb.plugins`. | `tls.enabled` | Enable TLS and mount the certificate Secret | `false` | | `tls.bolt` | Bolt TLS mode: OPTIONAL or REQUIRED | `OPTIONAL` | | `tls.mountPath` | Certificate mount path inside the container | `/etc/certs/arcadedb` | -| `tls.secretRef.name` | Secret containing TLS certificates | `arcadedb-tls` | -| `tls.secretRef.password` | Certificate store password | `$(rootPassword)` | +| `tls.secretRef.name` | Secret containing TLS certificates (`""` = `-tls`) | `""` | +| `tls.secretRef.passwordSecret.name` | Secret holding the store password (`""` = root password secret) | `""` | +| `tls.secretRef.passwordSecret.key` | Key inside `passwordSecret.name` | `""` | | `tls.secretRef.keyStore.key` | KeyStore key in the Secret | `keystore.p12` | | `tls.secretRef.trustStore.format` | TrustStore format | `JKS` | | `tls.secretRef.trustStore.key` | TrustStore key in the Secret | `truststore.jks` | | `tls.certManager.enabled` | Create a cert-manager Certificate resource | `false` | | `tls.certManager.extraDnsNames` | Adds extra DNS names to the TLS certificate | `[]` | +| `tls.certManager.issuerProvidesCA` | Set `false` for issuers without a `ca.crt` (e.g. ACME): the key store doubles as trust store | `true` | | `tls.certManager.issuerRef.kind` | cert-manager issuer kind | `ClusterIssuer` | -| `tls.certManager.issuerRef.name` | cert-manager issuer name | `my-issuer` | +| `tls.certManager.issuerRef.name` | cert-manager issuer name (required) | `""` | ### ingress @@ -402,7 +405,13 @@ TLS can be enabled for those protocols that support it by setting `tls.enabled` A key Store and trust Store are required from a secret. The keystore must be in `PKCS12` format. The trust store defaults to `JKS` in line with Java defaults but `PKCS12` is supported - set `tls.secretRef.trustStore.format`. -The chart supports using `cert-manager` to create a certificate with the correct stores. Set the `tls.certManager` values. The stores will use the root password. +The store password is read from a Secret and injected through the `TLS_STORE_PASSWORD` environment variable, so it never +appears in the pod spec. It defaults to the root password Secret; set `tls.secretRef.passwordSecret` to use another one. + +The chart supports using `cert-manager` to create a certificate with the correct stores. Set the `tls.certManager` values +(`issuerRef.name` is required). The certificate covers the headless, `-http` and (when enabled) `-external` services plus +every pod FQDN; add more names with `tls.certManager.extraDnsNames`. cert-manager only writes `truststore.p12` when the +issuer returns a CA certificate, so for issuers that do not (e.g. ACME) set `tls.certManager.issuerProvidesCA` to `false`. ## Ingress And External Access diff --git a/charts/arcadedb/templates/_helpers.tpl b/charts/arcadedb/templates/_helpers.tpl index 9fca37b..53fb824 100644 --- a/charts/arcadedb/templates/_helpers.tpl +++ b/charts/arcadedb/templates/_helpers.tpl @@ -71,11 +71,11 @@ Create the name of the service account to use {{- end }} {{/* -Create a comma-separated list of StatefulSet pod FQDNs for the Raft HA server list. +Create a comma-separated list of StatefulSet pod FQDNs (no ports). When HPA is enabled, the list is sized to autoscaling.maxReplicas so that KubernetesAutoJoin can resolve any pod ordinal up to the maximum scale. */}} -{{- define "arcadedb.nodenames" -}} +{{- define "arcadedb.nodehosts" -}} {{- $replicas := int .Values.replicaCount -}} {{- if and .Values.autoscaling.enabled (gt (int .Values.autoscaling.maxReplicas) $replicas) -}} {{- $replicas = int .Values.autoscaling.maxReplicas -}} @@ -89,6 +89,23 @@ KubernetesAutoJoin can resolve any pod ordinal up to the maximum scale. {{- join "," $names -}} {{- end }} +{{/* +Create the Raft HA server list: host:raftPort:httpPort per pod, plus +:priority:httpsPort when TLS is on. Declaring the ports keeps peer HTTP/HTTPS +endpoints explicit instead of relying on ArcadeDB's local-port fallback. +*/}} +{{- define "arcadedb.nodenames" -}} +{{- $ports := printf "%d:%d" (int .Values.service.rpc.port) (int .Values.service.http.port) -}} +{{- if .Values.tls.enabled -}} + {{- $ports = printf "%s:0:%d" $ports (int .Values.service.https.port) -}} +{{- end -}} +{{- $names := list -}} +{{- range $host := split "," (include "arcadedb.nodehosts" .) }} +{{- $names = append $names (printf "%s:%s" $host $ports) }} +{{- end }} +{{- join "," $names -}} +{{- end }} + {{/* Preparing a list of plugin ports to build plugin configurations. */}} @@ -244,7 +261,37 @@ All opt-in; emits nothing when defaults are unchanged. {{- end -}} {{/* -TLS parameters +Name of the secret holding the TLS key store / trust store. +*/}} +{{- define "arcadedb.tls.secretName" -}} +{{- default (printf "%s-tls" (include "arcadedb.fullname" .)) .Values.tls.secretRef.name -}} +{{- end -}} + +{{/* +Secret name and key holding the key store / trust store password. +Defaults to the root password secret. +*/}} +{{- define "arcadedb.tls.passwordSecretName" -}} +{{- with .Values.tls.secretRef.passwordSecret.name -}} +{{- . -}} +{{- else -}} +{{- default "arcadedb-credentials-secret" .Values.arcadedb.credentials.rootPassword.secret.name -}} +{{- end -}} +{{- end -}} + +{{- define "arcadedb.tls.passwordSecretKey" -}} +{{- if .Values.tls.secretRef.passwordSecret.name -}} +{{- required "tls.secretRef.passwordSecret.key is required when tls.secretRef.passwordSecret.name is set" .Values.tls.secretRef.passwordSecret.key -}} +{{- else if .Values.arcadedb.credentials.rootPassword.secret.name -}} +{{- .Values.arcadedb.credentials.rootPassword.secret.key -}} +{{- else -}} +rootPassword +{{- end -}} +{{- end -}} + +{{/* +TLS parameters. Store passwords come from the TLS_STORE_PASSWORD env var +(a secretKeyRef), so they never appear in the pod spec. */}} {{- define "arcadedb.tls.parameters" -}} {{- if .Values.tls.enabled }} @@ -259,15 +306,16 @@ TLS parameters {{- if .Values.tls.certManager.enabled }} {{- $keyStoreKey = "keystore.p12" -}} {{- $trustStoreFormat = "PKCS12" -}} - {{- $trustStoreKey = "truststore.p12" -}} + {{- /* cert-manager only writes truststore.p12 when the issuer returns a CA */ -}} + {{- $trustStoreKey = ternary "truststore.p12" "keystore.p12" .Values.tls.certManager.issuerProvidesCA -}} {{- end }} {{- if ne "JKS" $trustStoreFormat }} - -Djavax.net.ssl.trustStoreType={{ $trustStoreFormat }} {{- end }} - -Darcadedb.ssl.keyStore={{ printf "%s/%s" .Values.tls.mountPath $keyStoreKey }} -- -Darcadedb.ssl.keyStorePassword={{ .Values.tls.secretRef.password }} +- -Darcadedb.ssl.keyStorePassword=$(TLS_STORE_PASSWORD) - -Darcadedb.ssl.trustStore={{ printf "%s/%s" .Values.tls.mountPath $trustStoreKey }} -- -Darcadedb.ssl.trustStorePassword={{ .Values.tls.secretRef.password }} +- -Darcadedb.ssl.trustStorePassword=$(TLS_STORE_PASSWORD) {{- end }} {{- end -}} diff --git a/charts/arcadedb/templates/certificate.yaml b/charts/arcadedb/templates/certificate.yaml index 35a6c19..42a5fc9 100644 --- a/charts/arcadedb/templates/certificate.yaml +++ b/charts/arcadedb/templates/certificate.yaml @@ -1,15 +1,24 @@ {{- if and .Values.tls.enabled .Values.tls.certManager.enabled }} +{{- $fullname := include "arcadedb.fullname" . }} +{{- $services := list $fullname (printf "%s-http" $fullname) }} +{{- if .Values.service.external.enabled }} +{{- $services = append $services (printf "%s-external" $fullname) }} +{{- end }} kind: Certificate apiVersion: cert-manager.io/v1 metadata: - name: {{ include "arcadedb.fullname" . }} + name: {{ $fullname }} labels: {{- include "arcadedb.labels" . | nindent 4 }} spec: dnsNames: - - {{ include "arcadedb.fullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local - {{- $nodes := (include "arcadedb.nodenames" .) -}} - {{- range $n := split "," $nodes }} + {{- range $svc := $services }} + - {{ $svc }} + - {{ $svc }}.{{ $.Release.Namespace }} + - {{ $svc }}.{{ $.Release.Namespace }}.svc + - {{ $svc }}.{{ $.Release.Namespace }}.svc.cluster.local + {{- end }} + {{- range $n := split "," (include "arcadedb.nodehosts" .) }} - {{ $n }} {{- end }} {{- with .Values.tls.certManager.extraDnsNames }} @@ -17,22 +26,22 @@ spec: {{- end }} subject: organizations: - - {{ include "arcadedb.fullname" . }} + - {{ $fullname }} isCA: false usages: - server auth - client auth - secretName: {{ .Values.tls.secretRef.name }} + secretName: {{ include "arcadedb.tls.secretName" . }} privateKey: algorithm: ECDSA size: 256 issuerRef: kind: {{ .Values.tls.certManager.issuerRef.kind }} - name: {{ .Values.tls.certManager.issuerRef.name }} + name: {{ required "tls.certManager.issuerRef.name is required when tls.certManager.enabled" .Values.tls.certManager.issuerRef.name }} keystores: pkcs12: create: true passwordSecretRef: - name: arcadedb-credentials-secret - key: rootPassword + name: {{ include "arcadedb.tls.passwordSecretName" . }} + key: {{ include "arcadedb.tls.passwordSecretKey" . }} {{- end }} diff --git a/charts/arcadedb/templates/networkpolicy.yaml b/charts/arcadedb/templates/networkpolicy.yaml index 1c2b8fa..330e8af 100644 --- a/charts/arcadedb/templates/networkpolicy.yaml +++ b/charts/arcadedb/templates/networkpolicy.yaml @@ -2,7 +2,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: {{ include "arcadedb.fullname" . }}-protocols + name: {{ include "arcadedb.fullname" . }}-http labels: {{- include "arcadedb.labels" . | nindent 4 }} spec: diff --git a/charts/arcadedb/templates/service.yaml b/charts/arcadedb/templates/service.yaml index 64c8230..a98e671 100644 --- a/charts/arcadedb/templates/service.yaml +++ b/charts/arcadedb/templates/service.yaml @@ -12,6 +12,12 @@ spec: targetPort: http protocol: TCP name: http + {{- if .Values.tls.enabled }} + - port: {{ .Values.service.https.port }} + targetPort: https + protocol: TCP + name: https + {{- end }} selector: {{- include "arcadedb.selectorLabels" . | nindent 4 }} @@ -49,14 +55,14 @@ spec: {{- include "arcadedb.plugin.service" . | nindent 4 }} selector: {{- include "arcadedb.selectorLabels" . | nindent 4 }} ---- {{/* External service for exposing all protocol ports outside the cluster. */}} {{- if .Values.service.external.enabled }} {{- if ne .Values.service.http.type "ClusterIP" }} -{{- fail "http.type must be ClusterIP when external service is enabled" -}} +{{- fail "service.http.type must be ClusterIP when service.external.enabled is true (the external service replaces it)" -}} {{- end }} +--- apiVersion: v1 kind: Service metadata: diff --git a/charts/arcadedb/templates/statefulset.yaml b/charts/arcadedb/templates/statefulset.yaml index 2a2a57f..487bec1 100644 --- a/charts/arcadedb/templates/statefulset.yaml +++ b/charts/arcadedb/templates/statefulset.yaml @@ -151,10 +151,19 @@ spec: {{- end }} - name: ARCADEDB_LOG_DIR value: {{ .Values.arcadedb.logsDirectory | quote }} + {{- with .Values.arcadedb.tmpDirectory }} - name: ARCADEDB_PID - value: /tmp/arcadedb.pid + value: {{ printf "%s/arcadedb.pid" . | quote }} - name: XDG_CACHE_HOME - value: /tmp/.cache + value: {{ printf "%s/.cache" . | quote }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: TLS_STORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "arcadedb.tls.passwordSecretName" . }} + key: {{ include "arcadedb.tls.passwordSecretKey" . }} + {{- end }} {{- with .Values.arcadedb.consoleWorkingDirectory }} - name: ARCADEDB_SETTINGS value: {{ printf "-Duser.dir=%s" . | quote }} @@ -176,7 +185,7 @@ spec: {{- if .Values.tls.enabled }} - name: tls-certs secret: - secretName: {{ .Values.tls.secretRef.name }} + secretName: {{ include "arcadedb.tls.secretName" . }} {{- end }} {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} diff --git a/charts/arcadedb/tests/certificate_test.yaml b/charts/arcadedb/tests/certificate_test.yaml index 75446ae..5f58a5b 100644 --- a/charts/arcadedb/tests/certificate_test.yaml +++ b/charts/arcadedb/tests/certificate_test.yaml @@ -89,7 +89,77 @@ tests: certManager: enabled: true extraDnsNames: ["extra-dns-name.local"] + issuerRef: + name: arcadedb-issuer asserts: - contains: path: spec.dnsNames content: extra-dns-name.local + + - it: covers the client-facing http and external services in the DNS names + set: + tls.enabled: true + tls.certManager.enabled: true + tls.certManager.issuerRef.name: arcadedb-issuer + service.external.enabled: true + asserts: + - contains: + path: spec.dnsNames + content: test-arcadedb-http + - contains: + path: spec.dnsNames + content: test-arcadedb-http.default.svc + - contains: + path: spec.dnsNames + content: test-arcadedb-external.default.svc.cluster.local + - equal: + path: spec.secretName + value: test-arcadedb-tls + + - it: omits the external service DNS names when it is disabled + set: + tls.enabled: true + tls.certManager.enabled: true + tls.certManager.issuerRef.name: arcadedb-issuer + asserts: + - notContains: + path: spec.dnsNames + content: test-arcadedb-external + + - it: keystore password follows a user-supplied root password secret + set: + tls.enabled: true + tls.certManager.enabled: true + tls.certManager.issuerRef.name: arcadedb-issuer + arcadedb.credentials.rootPassword.secret.name: my-root + arcadedb.credentials.rootPassword.secret.key: pw + asserts: + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.name + value: my-root + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.key + value: pw + + - it: keystore password uses an explicit TLS password secret + set: + tls.enabled: true + tls.certManager.enabled: true + tls.certManager.issuerRef.name: arcadedb-issuer + tls.secretRef.passwordSecret.name: tls-pw + tls.secretRef.passwordSecret.key: store + asserts: + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.name + value: tls-pw + - equal: + path: spec.keystores.pkcs12.passwordSecretRef.key + value: store + + - it: fails when the issuer name is missing + set: + tls.enabled: true + tls.certManager.enabled: true + asserts: + - failedTemplate: + errorMessage: "tls.certManager.issuerRef.name is required when tls.certManager.enabled" diff --git a/charts/arcadedb/tests/helpers_test.yaml b/charts/arcadedb/tests/helpers_test.yaml index 2a3b3c8..cd6f029 100644 --- a/charts/arcadedb/tests/helpers_test.yaml +++ b/charts/arcadedb/tests/helpers_test.yaml @@ -35,7 +35,7 @@ tests: asserts: - contains: path: spec.template.spec.containers[0].command - content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local,test-arcadedb-1.test-arcadedb.default.svc.cluster.local,test-arcadedb-2.test-arcadedb.default.svc.cluster.local" + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-2.test-arcadedb.default.svc.cluster.local:2434:2480" - it: arcadedb.nodenames sizes to autoscaling.maxReplicas when HPA enabled and larger than replicaCount set: @@ -46,7 +46,25 @@ tests: asserts: - contains: path: spec.template.spec.containers[0].command - content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local,test-arcadedb-1.test-arcadedb.default.svc.cluster.local,test-arcadedb-2.test-arcadedb.default.svc.cluster.local,test-arcadedb-3.test-arcadedb.default.svc.cluster.local,test-arcadedb-4.test-arcadedb.default.svc.cluster.local" + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-2.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-3.test-arcadedb.default.svc.cluster.local:2434:2480,test-arcadedb-4.test-arcadedb.default.svc.cluster.local:2434:2480" + + - it: arcadedb.nodenames uses custom rpc port + set: + replicaCount: 2 + service.rpc.port: 5555 + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:5555:2480,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:5555:2480" + + - it: arcadedb.nodenames declares the HTTPS port when TLS is enabled + set: + replicaCount: 2 + tls.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ha.serverList=test-arcadedb-0.test-arcadedb.default.svc.cluster.local:2434:2480:0:2490,test-arcadedb-1.test-arcadedb.default.svc.cluster.local:2434:2480:0:2490" - it: arcadedb.plugin.parameters emits gremlin plugin entry and port set: diff --git a/charts/arcadedb/tests/networkpolicy_test.yaml b/charts/arcadedb/tests/networkpolicy_test.yaml index 283d8aa..f58f6f0 100644 --- a/charts/arcadedb/tests/networkpolicy_test.yaml +++ b/charts/arcadedb/tests/networkpolicy_test.yaml @@ -26,7 +26,7 @@ tests: of: NetworkPolicy - equal: path: metadata.name - value: test-arcadedb-protocols + value: test-arcadedb-http - equal: path: spec.podSelector.matchLabels["app.kubernetes.io/name"] value: arcadedb @@ -81,7 +81,7 @@ tests: path: spec.ingress[0].ports[0].port value: 9090 - - it: protocols policy includes HTTPS and enabled plugin ports + - it: http policy includes HTTPS and enabled plugin ports set: networkPolicy.enabled: true tls.enabled: true diff --git a/charts/arcadedb/tests/service_test.yaml b/charts/arcadedb/tests/service_test.yaml index 2bd98f5..e4ac384 100644 --- a/charts/arcadedb/tests/service_test.yaml +++ b/charts/arcadedb/tests/service_test.yaml @@ -87,6 +87,15 @@ tests: path: spec.ports content: { port: 2490, targetPort: https, protocol: TCP, name: https } + - it: client http service exposes HTTPS when TLS is enabled + set: + tls.enabled: true + documentIndex: 0 + asserts: + - contains: + path: spec.ports + content: { port: 2490, targetPort: https, protocol: TCP, name: https } + - it: headless service exposes gremlin plugin port when enabled set: arcadedb.plugins.gremlin.enabled: true @@ -199,4 +208,4 @@ tests: service.http.type: LoadBalancer asserts: - failedTemplate: - errorMessage: "http.type must be ClusterIP when external service is enabled" + errorMessage: "service.http.type must be ClusterIP when service.external.enabled is true (the external service replaces it)" diff --git a/charts/arcadedb/tests/statefulset_test.yaml b/charts/arcadedb/tests/statefulset_test.yaml index 950e412..b9854f3 100644 --- a/charts/arcadedb/tests/statefulset_test.yaml +++ b/charts/arcadedb/tests/statefulset_test.yaml @@ -199,7 +199,7 @@ tests: content: name: tls-certs secret: - secretName: arcadedb-tls + secretName: test-arcadedb-tls - contains: path: spec.template.spec.containers[0].command content: "-Darcadedb.ssl.enabled=true" @@ -247,6 +247,64 @@ tests: path: spec.template.spec.containers[0].command content: "-Darcadedb.ssl.trustStore=/etc/certs/arcadedb/truststore.p12" + - it: cert-manager TLS falls back to the key store when the issuer provides no CA + set: + tls.enabled: true + tls.certManager.enabled: true + tls.certManager.issuerProvidesCA: false + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.trustStore=/etc/certs/arcadedb/keystore.p12" + + - it: TLS store passwords come from a secret-backed env var, never inline + set: + tls.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.keyStorePassword=$(TLS_STORE_PASSWORD)" + - contains: + path: spec.template.spec.containers[0].command + content: "-Darcadedb.ssl.trustStorePassword=$(TLS_STORE_PASSWORD)" + - contains: + path: spec.template.spec.containers[0].env + content: + name: TLS_STORE_PASSWORD + valueFrom: + secretKeyRef: + name: arcadedb-credentials-secret + key: rootPassword + + - it: TLS_STORE_PASSWORD is absent without TLS + asserts: + - notContains: + path: spec.template.spec.containers[0].env + any: true + content: + name: TLS_STORE_PASSWORD + + - it: PID file and XDG cache follow arcadedb.tmpDirectory + set: + arcadedb.tmpDirectory: /scratch + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: { name: ARCADEDB_PID, value: /scratch/arcadedb.pid } + - contains: + path: spec.template.spec.containers[0].env + content: { name: XDG_CACHE_HOME, value: /scratch/.cache } + + - it: empty arcadedb.tmpDirectory keeps the image defaults + set: + arcadedb.tmpDirectory: "" + asserts: + - notContains: + path: spec.template.spec.containers[0].env + any: true + content: + name: ARCADEDB_PID + - it: env contains HOSTNAME from metadata.name field reference asserts: - contains: diff --git a/charts/arcadedb/values.yaml b/charts/arcadedb/values.yaml index 8c5555c..994fd4e 100644 --- a/charts/arcadedb/values.yaml +++ b/charts/arcadedb/values.yaml @@ -36,6 +36,10 @@ arcadedb: ## backed by a writable volume: it defaults to the arcadedb-tmp emptyDir mounted ## at /tmp. Set to "" to opt out. consoleWorkingDirectory: "/tmp" + ## @param arcadedb.tmpDirectory Writable directory for the server PID file + ## (ARCADEDB_PID) and XDG_CACHE_HOME. Must be backed by a writable volume + ## (defaults to the arcadedb-tmp emptyDir). Set to "" to keep the image defaults. + tmpDirectory: "/tmp" ## @section arcadedb.ha ha: @@ -185,11 +189,17 @@ tls: ## @section tls.secretRef secretRef: - ## @param tls.secretRef.name Name of the secret containing TLS certificates - name: arcadedb-tls - - ## @param tls.secretRef.password Password for the certificate stores - password: $(rootPassword) + ## @param tls.secretRef.name Name of the secret containing TLS certificates ("" = -tls) + name: "" + + ## @section tls.secretRef.passwordSecret + ## Secret holding the key store / trust store password. When name is empty the + ## root password secret is used. Injected through an env var, never inlined. + passwordSecret: + ## @param tls.secretRef.passwordSecret.name Secret name ("" = root password secret) + name: "" + ## @param tls.secretRef.passwordSecret.key Key inside the secret + key: "" ## @section tls.secretRef.keyStore keyStore: @@ -209,14 +219,20 @@ tls: enabled: false ## @param tls.certManager.extraDnsNames Adds extra DNS names to the TLS certificate + ## (the services and pod FQDNs are always included) extraDnsNames: [] + ## @param tls.certManager.issuerProvidesCA Set false when the issuer does not + ## return a ca.crt (e.g. ACME): cert-manager then writes no truststore.p12, so + ## the key store is used as the trust store instead. + issuerProvidesCA: true + ## @section tls.certManager.issuerRef issuerRef: ## @param tls.certManager.issuerRef.kind Kind of the cert-manager issuer kind: ClusterIssuer ## @param tls.certManager.issuerRef.name Name of the cert-manager issuer - name: my-issuer + name: "" ## @section ingress ingress: @@ -377,8 +393,9 @@ extraManifests: {} ## @section networkPolicy networkPolicy: ## @param networkPolicy.enabled Create NetworkPolicy resources. - ## When enabled: HTTP (2480) is open to all cluster pods; Raft gRPC (2434) is restricted - ## to ArcadeDB pods only. Recommended for production multi-tenant clusters. + ## When enabled: HTTP (2480), HTTPS (when TLS is on) and enabled plugin ports are open to + ## all cluster pods; Raft gRPC (2434) is restricted to ArcadeDB pods only. Recommended for + ## production multi-tenant clusters. enabled: false ## @section observability