Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/chart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Lint chart
run: helm lint charts/express-botx

- name: Test TLS chart rendering
run: bash charts/express-botx/tests/tls-smoke.sh

publish:
if: startsWith(github.ref, 'refs/tags/chart-')
needs: lint
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

### Added: HTTPS/TLS serving с hot reload сертификата

- `serve` и `serve --enqueue` поддерживают opt-in HTTPS через YAML, env или
`--tls-cert`/`--tls-key`; минимальная версия — TLS 1.2.
- Сертификат и ключ перечитываются по content hash без остановки listener; ошибка
ротации не сбрасывает последнюю корректную пару.
- Helm умеет создать cert-manager Certificate или смонтировать существующий TLS
Secret, переключает probes/backend port name на HTTPS и сохраняет прежний HTTP
render при `tls.enabled=false`.

## 0.34.0

### ⚠️ Breaking: GitLab-конфигурация только через senders
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ curl -X POST http://localhost:8080/api/v1/send \
-d '{"message": "Test from express-botx web api"}'
```

TLS включается, когда после объединения YAML, переменных окружения и CLI заданы
оба пути. HTTPS заменяет HTTP на том же `--listen` и действует как для обычного
`serve`, так и для `serve --enqueue`:

```bash
express-botx serve \
--listen :8443 \
--tls-cert /etc/express-botx/tls/tls.crt \
--tls-key /etc/express-botx/tls/tls.key

curl --cacert ca.crt https://localhost:8443/healthz
```

Сертификат перечитывается без перезапуска. При ошибке ротации сервер продолжает
отдавать последнюю корректную пару.


Эндпоинты (все POST требуют `Authorization: Bearer <key>`):

Expand Down
79 changes: 79 additions & 0 deletions charts/express-botx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ config:
|----------|----------|--------------|
| `mode` | Режим: `serve`, `serve-enqueue`, `worker` | `serve` |
| `replicaCount` | Количество реплик | `1` |
| `containerPort` | Порт контейнера и probes | `8080` |
| `image.repository` | Docker-образ | `lavr/express-botx` |
| `image.tag` | Тег образа | `<appVersion>-rootless` из Chart.yaml |
| `image.pullPolicy` | Pull policy | `IfNotPresent` |
Expand Down Expand Up @@ -76,8 +77,19 @@ config:
| `config.catalog.publish` | Worker публикует catalog | `true` |
| `config.catalog.publish_interval` | Интервал публикации | `30s` |
| `existingSecret` | Имя существующего Secret с `config.yaml` | `""` |
| `tls.enabled` | Включить HTTPS на pod | `false` |
| `tls.mountPath` | Каталог TLS Secret | `/etc/express-botx/tls` |
| `tls.reloadInterval` | Интервал hot reload | `60s` |
| `tls.certManager.enabled` | Создать Certificate | `false` |
| `tls.certManager.issuerRef.name` | Имя issuer | `""` |
| `tls.certManager.issuerRef.kind` | Issuer kind | `ClusterIssuer` |
| `tls.certManager.dnsNames` | SAN или fallback из Ingress hosts | `[]` |
| `tls.certManager.duration` | Опциональная duration | `""` |
| `tls.certManager.renewBefore` | Опциональная renewBefore | `""` |
| `tls.existingSecret` | Secret с `tls.crt`/`tls.key` | `""` |
| `service.type` | Тип сервиса | `ClusterIP` |
| `service.port` | Порт сервиса | `80` |
| `service.targetPort` | Независимый numeric/named targetPort | `8080` |
| `ingress.enabled` | Включить Ingress | `false` |
| `ingress.className` | Ingress class | `""` |
| `ingress.hosts` | Список хостов | `[]` |
Expand All @@ -88,6 +100,73 @@ config:
| `autoscaling.enabled` | Включить HPA | `false` |
| `extraEnv` | Дополнительные переменные окружения | `[]` |

### HTTPS/TLS

cert-manager:

```yaml
containerPort: 8443
service:
targetPort: 8443
config:
server:
listen: ":8443"
tls:
enabled: true
certManager:
enabled: true
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames: [botx.example.com]
```

Существующий Secret с обязательными ключами `tls.crt` и `tls.key`:

```bash
kubectl create secret tls express-botx-tls --cert=cert.pem --key=key.pem
```

Имена `cert.pem` и `key.pem` здесь относятся только к локальным файлам: команда
`kubectl create secret tls` создаёт стандартные ключи `tls.crt` и `tls.key`.

```yaml
containerPort: 8443
service:
targetPort: 8443
config:
server:
listen: ":8443"
tls:
enabled: true
existingSecret: express-botx-tls
```

Для ingress-nginx при pod-level TLS:

```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
```

Chart не добавляет controller-specific аннотации автоматически. `ingress.tls`
настраивает клиент → Ingress, а `tls.enabled` — Ingress/kubelet → pod. При TLS
`service.targetPort` должен совпадать с `containerPort` или именем порта `https`,
а приложение должно слушать тот же numeric port.

Chart не подменяет `EXPRESS_BOTX_SERVER_LISTEN`: оператор явно согласует
`containerPort`, `service.targetPort` и listen из structured config, `configRaw`,
внешнего config Secret или `extraEnv`. `service.targetPort` сохраняет буквальный
default `8080`, чтобы старые `tpl`-выражения продолжали работать. TLS запрещён
для `mode: worker`; `tls.certManager.enabled` и `tls.existingSecret`
взаимоисключающие.

Произвольные имена ключей TLS Secret не переназначаются. Уже существующий Secret,
ключи которого сами называются `cert.pem`/`key.pem`, не поддерживается: при
отсутствии `tls.crt`/`tls.key` kubelet не смонтирует volume и pod не запустит
приложение.

### Deployment patterns

Для async-режима рекомендуется два отдельных Deployment: API-сервер (`serve --enqueue`) и worker.
Expand Down
2 changes: 1 addition & 1 deletion charts/express-botx/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To access the API, run:
kubectl port-forward svc/{{ include "express-botx.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.targetPort }}

Then send a request:
curl http://localhost:{{ .Values.service.port }}/healthz
curl {{ if .Values.tls.enabled }}-k https{{ else }}http{{ end }}://localhost:{{ .Values.service.port }}/healthz
{{- end }}

Configured bots:
Expand Down
75 changes: 75 additions & 0 deletions charts/express-botx/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,78 @@ Config secret name.
{{- define "express-botx.secretName" -}}
{{- default (include "express-botx.fullname" .) .Values.existingSecret }}
{{- end }}

{{- define "express-botx.portName" -}}
{{- if .Values.tls.enabled }}https{{ else }}http{{ end }}
{{- end }}

{{- define "express-botx.tls.secretName" -}}
{{- if .Values.tls.existingSecret -}}
{{- .Values.tls.existingSecret -}}
{{- else -}}
{{- printf "%s-tls" (include "express-botx.fullname" .) -}}
{{- end -}}
{{- end }}

{{- define "express-botx.tls.dnsNames" -}}
{{- $names := list -}}
{{- range .Values.tls.certManager.dnsNames -}}
{{- $name := trim (toString (default "" .)) -}}
{{- if $name }}{{- $names = append $names $name -}}{{- end -}}
{{- end -}}
{{- if and (eq (len $names) 0) .Values.ingress.enabled -}}
{{- range .Values.ingress.hosts -}}
{{- $hostConfig := default dict . -}}
{{- $host := trim (toString (default "" $hostConfig.host)) -}}
{{- if $host }}{{- $names = append $names $host -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- if eq (len $names) 0 -}}
{{- fail "tls.certManager.dnsNames is required (or enable ingress with non-empty hosts)" -}}
{{- end -}}
{{- range $names }}
- {{ . | quote }}
{{- end -}}
{{- end }}

{{- define "express-botx.tls.validate" -}}
{{- if .Values.tls.enabled -}}
{{- if eq .Values.mode "worker" }}{{- fail "tls.enabled is not supported with mode=worker" -}}{{- end -}}
{{- $cm := .Values.tls.certManager.enabled -}}
{{- $tlsSecret := trim (toString (default "" .Values.tls.existingSecret)) -}}
{{- $issuerRef := default dict .Values.tls.certManager.issuerRef -}}
{{- if and $cm $tlsSecret }}{{- fail "tls.certManager.enabled and tls.existingSecret are mutually exclusive" -}}{{- end -}}
{{- if and (not $cm) (not $tlsSecret) }}{{- fail "tls.enabled requires tls.certManager.enabled or tls.existingSecret" -}}{{- end -}}
{{- if and $cm (not (trim (toString (default "" $issuerRef.name)))) }}{{- fail "tls.certManager.issuerRef.name is required" -}}{{- end -}}

{{- $reloadInterval := trim (toString (default "" .Values.tls.reloadInterval)) -}}
{{- $durationPattern := "^[+]?(([0-9]+([.][0-9]*)?|[.][0-9]+)(ns|us|µs|μs|ms|s|m|h))+$" -}}
{{- if or (not (regexMatch $durationPattern $reloadInterval)) (not (regexMatch "[1-9]" $reloadInterval)) -}}
{{- fail (printf "tls.reloadInterval must be a positive Go duration (for example 60s), got %q" $reloadInterval) -}}
{{- end -}}

{{- $container := toString .Values.containerPort -}}
{{- $target := toString .Values.service.targetPort -}}
{{- $portName := include "express-botx.portName" . -}}
{{- if and (ne $target $container) (ne $target $portName) -}}
{{- fail (printf "service.targetPort must equal containerPort (%s) or port name %q when TLS is enabled" $container $portName) -}}
{{- end -}}

{{- $listenEnv := false -}}
{{- range .Values.extraEnv -}}
{{- if eq (default "" .name) "EXPRESS_BOTX_SERVER_LISTEN" }}{{- $listenEnv = true -}}{{- end -}}
{{- end -}}
{{- if and (not .Values.configRaw) (not .Values.existingSecret) (not $listenEnv) -}}
{{- $config := default dict .Values.config -}}
{{- $server := default dict $config.server -}}
{{- $listen := toString (default "" $server.listen) -}}
{{- $suffix := regexFind ":[0-9]+$" $listen -}}
{{- if $suffix -}}
{{- $listenPort := trimPrefix ":" $suffix -}}
{{- if ne $listenPort $container -}}
{{- fail (printf "config.server.listen port (%s) must equal containerPort (%s) when TLS is enabled" $listenPort $container) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
21 changes: 21 additions & 0 deletions charts/express-botx/templates/certificate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{- if and .Values.tls.enabled .Values.tls.certManager.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ printf "%s-tls" (include "express-botx.fullname" .) }}
labels:
{{- include "express-botx.labels" . | nindent 4 }}
spec:
secretName: {{ printf "%s-tls" (include "express-botx.fullname" .) }}
dnsNames:
{{- include "express-botx.tls.dnsNames" . | nindent 4 }}
issuerRef:
name: {{ .Values.tls.certManager.issuerRef.name | quote }}
kind: {{ .Values.tls.certManager.issuerRef.kind | quote }}
{{- with .Values.tls.certManager.duration }}
duration: {{ . | quote }}
{{- end }}
{{- with .Values.tls.certManager.renewBefore }}
renewBefore: {{ . | quote }}
{{- end }}
{{- end }}
38 changes: 34 additions & 4 deletions charts/express-botx/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{{- include "express-botx.tls.validate" . -}}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -50,8 +51,8 @@ spec:
{{- end }}
ports:
{{- if ne .Values.mode "worker" }}
- name: http
containerPort: 8080
- name: {{ include "express-botx.portName" . }}
containerPort: {{ .Values.containerPort }}
protocol: TCP
{{- end }}
{{- if and (eq .Values.mode "worker") .Values.config.worker }}
Expand Down Expand Up @@ -80,13 +81,19 @@ spec:
livenessProbe:
httpGet:
path: /healthz
port: http
port: {{ include "express-botx.portName" . }}
{{- if .Values.tls.enabled }}
scheme: HTTPS
{{- end }}
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: http
port: {{ include "express-botx.portName" . }}
{{- if .Values.tls.enabled }}
scheme: HTTPS
{{- end }}
initialDelaySeconds: 2
periodSeconds: 5
{{- end }}
Expand All @@ -101,9 +108,22 @@ spec:
readOnly: true
- name: cache
mountPath: /tmp/express-botx
{{- if .Values.tls.enabled }}
- name: tls
mountPath: {{ .Values.tls.mountPath | quote }}
readOnly: true
{{- end }}
env:
- name: EXPRESS_BOTX_CONFIG
value: /etc/express-botx/config.yaml
{{- if .Values.tls.enabled }}
- name: EXPRESS_BOTX_SERVER_TLS_CERT
value: {{ printf "%s/tls.crt" (trimSuffix "/" .Values.tls.mountPath) | quote }}
- name: EXPRESS_BOTX_SERVER_TLS_KEY
value: {{ printf "%s/tls.key" (trimSuffix "/" .Values.tls.mountPath) | quote }}
- name: EXPRESS_BOTX_SERVER_TLS_RELOAD_INTERVAL
value: {{ .Values.tls.reloadInterval | quote }}
{{- end }}
{{- with .Values.extraEnv }}
{{- tpl (toYaml .) $ | nindent 12 }}
{{- end }}
Expand All @@ -116,6 +136,16 @@ spec:
path: config.yaml
- name: cache
emptyDir: {}
{{- if .Values.tls.enabled }}
- name: tls
secret:
secretName: {{ include "express-botx.tls.secretName" . | quote }}
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
2 changes: 1 addition & 1 deletion charts/express-botx/templates/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ spec:
service:
name: {{ include "express-botx.fullname" $ }}
port:
name: http
name: {{ include "express-botx.portName" $ }}
{{- end }}
{{- end }}
{{- end }}
2 changes: 1 addition & 1 deletion charts/express-botx/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ spec:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
name: {{ include "express-botx.portName" . }}
selector:
{{- include "express-botx.selectorLabels" . | nindent 4 }}
Loading
Loading