From 80bdf98ff9bd57ee0908d2d8686cc4b25cd0f9c6 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Wed, 25 Jan 2023 15:42:55 -0800 Subject: [PATCH 01/21] Kubernete: Support for tls/x509 redis session connections --- kubernetes/README.md | 2 +- kubernetes/certs/redis-openemr-client.yaml | 25 ++++ kubernetes/certs/redis.yaml | 31 +++++ kubernetes/certs/redisproxy.yaml | 26 ++++ kubernetes/certs/sentinel.yaml | 26 ++++ .../haproxy-28-redis-tools/Dockerfile | 4 + kubernetes/kub-down | 11 +- kubernetes/kub-down.bat | 9 +- kubernetes/kub-up | 9 +- kubernetes/kub-up.bat | 9 +- kubernetes/openemr/deployment.yaml | 20 +++ kubernetes/redis/configmap-haproxy.yaml | 28 ++++ kubernetes/redis/configmap-main.yaml | 14 +- kubernetes/redis/configmap-pipy.yaml | 120 ------------------ kubernetes/redis/deployment-redisproxy.yaml | 45 +++++-- kubernetes/redis/healthcheck-haproxy.yaml | 18 +++ kubernetes/redis/statefulset-redis.yaml | 24 +++- kubernetes/redis/statefulset-sentinel.yaml | 32 ++++- 18 files changed, 305 insertions(+), 148 deletions(-) create mode 100644 kubernetes/certs/redis-openemr-client.yaml create mode 100644 kubernetes/certs/redis.yaml create mode 100644 kubernetes/certs/redisproxy.yaml create mode 100644 kubernetes/certs/sentinel.yaml create mode 100644 kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile create mode 100644 kubernetes/redis/configmap-haproxy.yaml delete mode 100644 kubernetes/redis/configmap-pipy.yaml create mode 100644 kubernetes/redis/healthcheck-haproxy.yaml diff --git a/kubernetes/README.md b/kubernetes/README.md index 3667ecf7..5ff6c990 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -2,7 +2,7 @@ OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections are encrypted over the wire (ssl is enforced by default; X509 can be enforced by following pertinent comments in following scripts: 2 places in mysql/configmap.yaml, 2 places in openemr/deployment.yaml, 1 place in phpmyadmin/configmap.yaml, 1 place in phpmyadmin/deployment.yaml). - - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset, 3 sentinels for another statefulset, and then 2 proxies deployment. The proxies ensure that redis traffic is always directed towards master. The proxy replications can be increased/decreased. However the primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several place in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in kubernetes/redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in kubernetes/redis/configmap-main.yaml, `admin` in kubernetes/redis/configmap-pipy.yaml, `admin` in kubernetes/redis/statefulset-sentinel.yaml. The `default` is the typical worker/app/client user. + - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset, 3 sentinels for another statefulset, and then 2 proxies deployment. The proxies ensure that redis traffic is always directed towards master. The proxy replications can be increased/decreased. However the primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several place in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in redis/configmap-main.yaml, `admin` in redis/configmap-pipy.yaml, `admin` in redis/statefulset-sentinel.yaml, `admin` in redis/healthcheck-haproxy.yaml. The `default` is the typical worker/app/client user. Connections are encrypted over the wire (ssl is enforced by default; X509 can be enforced by following pertinent comments in following scripts: 2 places in openemr/deployment.yaml, 1 place in redis/configmap-main.yaml, 1 place in redis/healthcheck-haproxy.yaml, 1 place in redis/statefulset-redis.yaml, 2 places in redis/statefulset-sentinel.yaml). - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Ports for both http and https. Would not consider this production quality, but will be a good working, starting point, and hopefully open the door to a myriad of other kubernetes based solutions. Note this is supported by 7.0.0 and higher dockers. If wish to use the most recent development codebase, then can change from openemr/openemr:7.0.3 to openemr/openemr:dev (in the openemr/deployment.yaml script), which is built nightly from the development codebase. If you wish to build dynamically from a branch/tag from a github repo or other git repo, then can change from openemr/openemr:7.0.3 to openemr/openemr:flex (in the openemr/deployment.yaml script) (note this will take much longer to start up (probably at least 10 minutes and up to 90 minutes) and is more cpu intensive since each instance of OpenEMR will download codebase and build separately). diff --git a/kubernetes/certs/redis-openemr-client.yaml b/kubernetes/certs/redis-openemr-client.yaml new file mode 100644 index 00000000..5e6c176c --- /dev/null +++ b/kubernetes/certs/redis-openemr-client.yaml @@ -0,0 +1,25 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: redis-openemr-client +spec: + secretName: redis-openemr-client-certs + duration: 87660h # 10y + renewBefore: 360h # 15d + isCA: false + privateKey: + size: 2048 + algorithm: RSA + encoding: PKCS1 + usages: + - digital signature + - key encipherment + - client auth + subject: + organizations: + - openemr + commonName: openemr + issuerRef: + name: ca-issuer + kind: Issuer + group: cert-manager.io \ No newline at end of file diff --git a/kubernetes/certs/redis.yaml b/kubernetes/certs/redis.yaml new file mode 100644 index 00000000..b4f94e45 --- /dev/null +++ b/kubernetes/certs/redis.yaml @@ -0,0 +1,31 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: redis +spec: + secretName: redis-certs + duration: 87660h # 10y + renewBefore: 360h # 15d + isCA: false + privateKey: + size: 2048 + algorithm: RSA + encoding: PKCS1 + usages: + - digital signature + - key encipherment + - server auth + - client auth + subject: + organizations: + - redis + commonName: redis + dnsNames: + - redisproxy + - redis-0.redis + - redis-1.redis + - redis-2.redis + issuerRef: + name: ca-issuer + kind: Issuer + group: cert-manager.io \ No newline at end of file diff --git a/kubernetes/certs/redisproxy.yaml b/kubernetes/certs/redisproxy.yaml new file mode 100644 index 00000000..40fddbfa --- /dev/null +++ b/kubernetes/certs/redisproxy.yaml @@ -0,0 +1,26 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: redisproxy +spec: + secretName: redisproxy-certs + duration: 87660h # 10y + renewBefore: 360h # 15d + isCA: false + privateKey: + size: 2048 + algorithm: RSA + encoding: PKCS1 + usages: + - digital signature + - key encipherment + - server auth + - client auth + subject: + organizations: + - redisproxy + commonName: redisproxy + issuerRef: + name: ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/kubernetes/certs/sentinel.yaml b/kubernetes/certs/sentinel.yaml new file mode 100644 index 00000000..54578ef7 --- /dev/null +++ b/kubernetes/certs/sentinel.yaml @@ -0,0 +1,26 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: sentinel +spec: + secretName: sentinel-certs + duration: 87660h # 10y + renewBefore: 360h # 15d + isCA: false + privateKey: + size: 2048 + algorithm: RSA + encoding: PKCS1 + usages: + - digital signature + - key encipherment + - server auth + - client auth + subject: + organizations: + - sentinel + commonName: sentinel + issuerRef: + name: ca-issuer + kind: Issuer + group: cert-manager.io diff --git a/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile b/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile new file mode 100644 index 00000000..bcc4be55 --- /dev/null +++ b/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile @@ -0,0 +1,4 @@ +FROM haproxy:2.8 +USER root +RUN apt-get update && apt-get install -y --no-install-recommends redis-tools +USER haproxy \ No newline at end of file diff --git a/kubernetes/kub-down b/kubernetes/kub-down index 70ffdd28..52cc8495 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -8,7 +8,11 @@ kubectl delete \ -f certs/mysql-replication.yaml \ -f certs/mysql-openemr-client.yaml \ -f certs/phpmyadmin.yaml \ - -f certs/mysql-phpmyadmin-client.yaml + -f certs/mysql-phpmyadmin-client.yaml \ + -f certs/redis.yaml \ + -f certs/redis-openemr-client.yaml \ + -f certs/sentinel.yaml \ + -f certs/redisproxy.yaml kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml @@ -19,7 +23,8 @@ kubectl delete \ -f mysql/statefulset.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ - -f redis/configmap-pipy.yaml \ + -f redis/configmap-haproxy.yaml \ + -f redis/healthcheck-haproxy.yaml \ -f redis/statefulset-redis.yaml \ -f redis/statefulset-sentinel.yaml \ -f redis/deployment-redisproxy.yaml \ @@ -34,4 +39,4 @@ kubectl delete \ -f volumes/website.yaml \ -f openemr/secret.yaml \ -f openemr/deployment.yaml \ - -f openemr/service.yaml + -f openemr/service.yaml diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index 7249407e..fefdb6eb 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -8,7 +8,11 @@ kubectl delete ^ -f certs/mysql-replication.yaml ^ -f certs/mysql-openemr-client.yaml ^ -f certs/phpmyadmin.yaml ^ - -f certs/mysql-phpmyadmin-client.yaml + -f certs/mysql-phpmyadmin-client.yaml ^ + -f certs/redis.yaml ^ + -f certs/redis-openemr-client.yaml ^ + -f certs/sentinel.yaml ^ + -f certs/redisproxy.yaml kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml @@ -19,7 +23,8 @@ kubectl delete ^ -f mysql/statefulset.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ - -f redis/configmap-pipy.yaml ^ + -f redis/configmap-haproxy.yaml ^ + -f redis/healthcheck-haproxy.yaml ^ -f redis/statefulset-redis.yaml ^ -f redis/statefulset-sentinel.yaml ^ -f redis/deployment-redisproxy.yaml ^ diff --git a/kubernetes/kub-up b/kubernetes/kub-up index 594e574f..60b39de6 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -13,7 +13,11 @@ kubectl apply \ -f certs/mysql-replication.yaml \ -f certs/mysql-openemr-client.yaml \ -f certs/phpmyadmin.yaml \ - -f certs/mysql-phpmyadmin-client.yaml + -f certs/mysql-phpmyadmin-client.yaml \ + -f certs/redis.yaml \ + -f certs/redis-openemr-client.yaml \ + -f certs/sentinel.yaml \ + -f certs/redisproxy.yaml echo "...waiting 15 seconds to ensure certs are created..." sleep 15 @@ -24,7 +28,8 @@ kubectl apply \ -f mysql/statefulset.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ - -f redis/configmap-pipy.yaml \ + -f redis/configmap-haproxy.yaml \ + -f redis/healthcheck-haproxy.yaml \ -f redis/statefulset-redis.yaml \ -f redis/statefulset-sentinel.yaml \ -f redis/deployment-redisproxy.yaml \ diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 1300e9cf..e8f01698 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -11,7 +11,11 @@ kubectl apply ^ -f certs/mysql-replication.yaml ^ -f certs/mysql-openemr-client.yaml ^ -f certs/phpmyadmin.yaml ^ - -f certs/mysql-phpmyadmin-client.yaml + -f certs/mysql-phpmyadmin-client.yaml ^ + -f certs/redis.yaml ^ + -f certs/redis-openemr-client.yaml ^ + -f certs/sentinel.yaml ^ + -f certs/redisproxy.yaml timeout 15 kubectl apply ^ @@ -21,7 +25,8 @@ kubectl apply ^ -f mysql/statefulset.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ - -f redis/configmap-pipy.yaml ^ + -f redis/configmap-haproxy.yaml ^ + -f redis/healthcheck-haproxy.yaml ^ -f redis/statefulset-redis.yaml ^ -f redis/statefulset-sentinel.yaml ^ -f redis/deployment-redisproxy.yaml ^ diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 7a01e8f8..e94bfc0c 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -46,10 +46,17 @@ spec: key: admin-pass - name: OE_USER value: "admin" + - name: PHPREDIS_BUILD + value: "e571a81f8d3009aab38cbb88dde865edeb0607ac" - name: REDIS_SERVER value: "redisproxy" - name: REDIS_PASSWORD value: "defaultpassword" + - name: REDIS_TLS + value: "yes" + # uncomment below if using redis x509 + #- name: REDIS_X509 + # value: "yes" - name: SWARM_MODE value: "yes" - name: FORCE_DATABASE_SSL_CONNECT @@ -68,6 +75,8 @@ spec: volumeMounts: - mountPath: /root/certs/mysql/server name: mysql-openemr-client-certs + - mountPath: /root/certs/redis + name: redis-openemr-client-certs - mountPath: /var/www/localhost/htdocs/openemr/sites name: websitevolume - mountPath: /etc/ssl @@ -87,6 +96,17 @@ spec: # path: mysql-cert #- key: tls.key # path: mysql-key + - name: redis-openemr-client-certs + secret: + secretName: redis-openemr-client-certs + items: + - key: ca.crt + path: redis-ca + # uncomment below if using redis x509 + #- key: tls.crt + # path: redis-cert + #- key: tls.key + # path: redis-key - name: websitevolume persistentVolumeClaim: claimName: websitevolume diff --git a/kubernetes/redis/configmap-haproxy.yaml b/kubernetes/redis/configmap-haproxy.yaml new file mode 100644 index 00000000..b223cdca --- /dev/null +++ b/kubernetes/redis/configmap-haproxy.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: haproxy-config +data: + haproxy.cfg: | + defaults + mode tcp + timeout connect 4s + timeout server 30s + timeout client 30s + global + insecure-fork-wanted + external-check + resolvers mydns + parse-resolv-conf + accepted_payload_size 4096 + hold valid 3s + frontend ft_redis + bind *:6379 + default_backend bk_redis + backend bk_redis + option external-check + external-check command /var/lib/haproxy/healthcheck.sh + default-server check inter 3s fall 1 rise 1 resolvers mydns + server R1 redis-0.redis.default.svc.cluster.local:6379 + server R2 redis-1.redis.default.svc.cluster.local:6379 + server R3 redis-2.redis.default.svc.cluster.local:6379 diff --git a/kubernetes/redis/configmap-main.yaml b/kubernetes/redis/configmap-main.yaml index ec54d93d..269ad0c0 100644 --- a/kubernetes/redis/configmap-main.yaml +++ b/kubernetes/redis/configmap-main.yaml @@ -27,7 +27,17 @@ data: # this is the second ConfigMap will be mounted to. it has the list of users needed. aclfile /conf/acl/users.acl - # port, each redis nodes will be used - port 6379 + # tls certs and setting + tls-cert-file /certs/tls.crt + tls-key-file /certs/tls.key + tls-ca-cert-file /certs/ca.crt + tls-auth-clients no + # uncomment below (and comment line above) if using redis x509 + # tls-auth-clients yes + tls-replication yes + + # port, each redis nodes will be used (only use tls) + port 0 + tls-port 6379 # More configurations are optional, if not provided, redis will consider default values ------ # ------ More details on configuration : https://redis.io/docs/manual/config/ ------ \ No newline at end of file diff --git a/kubernetes/redis/configmap-pipy.yaml b/kubernetes/redis/configmap-pipy.yaml deleted file mode 100644 index 96d2fce1..00000000 --- a/kubernetes/redis/configmap-pipy.yaml +++ /dev/null @@ -1,120 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: pipy-config -data: - config.json: | - { - "redisAdminUser" : "admin", - "redisAdminPass" : "adminpassword", - "debug" : false, - "port" : 6379, - "servers" : ["redis-0.redis:6379", "redis-1.redis:6379", "redis-2.redis:6379"], - "connectTimeout" : "1s", - "readTimeout" : "1s", - "healthcheck" : { - "interval" : "5s", - "connectTimeout" : "1s", - "readTimeout" : "1s" - } - } - - proxy.js: | - ((config, unhealthy_nodes, unhealthy_master) => ( - pipy({ - _servers: new algo.RoundRobinLoadBalancer(config.servers, unhealthy_nodes), - _masters: new algo.RoundRobinLoadBalancer(config.servers, unhealthy_master), - _target: '', - _counter: null, - _reqTime: 0, - _commandCounter: new stats.Counter('command', ['command']), - _requestLatency: new stats.Histogram('request_latency', new Array(16).fill(0).map((_, i) => Math.pow(2, i))), - _regex: new RegExp('^[$*+:]\\d+\r\n[^\r\n]+\r\n(\\w+)\r\n'), - - _check: resp => ( - (data, role) => ( - unhealthy_nodes.remove(_target), - data = resp.shift(40).toString().split('\r\n'), - role = data[3].split(':')[1], - config.debug && console.log(`Role is ${role} for ${_target}`), - role === 'master' && unhealthy_master.remove(_target) - ))() - }) - .listen(config.port) - .handleData( - (data, query, command, master_only) => ( - query = new Data(data).shift(20).toString(), - (command = _regex.exec(query)?.[1].toLowerCase()) && ( - _commandCounter.withLabels(command).increase(), - _target = _masters.select() - ) - ) - ) - .link('connection', () => _target) - - .pipeline('connection') - .handleStreamStart( - () => ( - _reqTime = Date.now() - ) - ) - .handleData( - req => ( - config.debug && console.log(`Sending request to node ${_target}`) - ) - ) - .connect(() => _target, - { - connectTimeout: config.connectTimeout, - readTimeout: config.readTimeout - } - ) - .handleData( - data => ( - _requestLatency.observe(Date.now() - _reqTime), - config.debug && console.log(`Response received from node ${_target}`) - ) - ) - - .task(config.healthcheck.interval) - .handleStreamStart( - () => ( - unhealthy_nodes.clear(), - unhealthy_master.clear(), - config.servers.forEach(t => ( - unhealthy_nodes.set(t, true), - unhealthy_master.set(t, true) - )), - _counter = { n: 0 } - ) - ) - .fork('per-node', - () => (config.servers.map(t => ({ _target: t })))) - .replaceMessage( - new StreamEnd - ) - .wait( - () => _counter.n === 0 - ) - - .pipeline('per-node') - .replaceMessage( - () => ( - _counter.n++, - new Message(`AUTH ${config.redisAdminUser} ${config.redisAdminPass}\r\ninfo replication\r\n`) - ) - ) - .connect( - () => _target, - { - connectTimeout: config.healthcheck.connectTimeout, - readTimeout: config.healthcheck.readTimeout - } - ) - .handleData( - data => _check(data) - ) - .handleStreamEnd( - () => _counter.n-- - ) - ))(JSON.decode(pipy.load('config/config.json')), new algo.Cache(), new algo.Cache()) \ No newline at end of file diff --git a/kubernetes/redis/deployment-redisproxy.yaml b/kubernetes/redis/deployment-redisproxy.yaml index 9aa3e482..f4b59bd5 100644 --- a/kubernetes/redis/deployment-redisproxy.yaml +++ b/kubernetes/redis/deployment-redisproxy.yaml @@ -24,21 +24,42 @@ spec: - name: proxy-init-redis-wait-3 image: busybox:1.28 command: ['sh', '-c', "until nslookup redis-2.redis; do echo waiting for redis-2.redis; sleep 10; done"] + - name: proxy-init-sentinel-wait-1 + image: busybox:1.28 + command: ['sh', '-c', "until nslookup sentinel-0.sentinel; do echo waiting for sentinel-0.sentinel; sleep 10; done"] + - name: proxy-init-sentinel-wait-2 + image: busybox:1.28 + command: ['sh', '-c', "until nslookup sentinel-1.sentinel; do echo waiting for sentinel-1.sentinel; sleep 10; done"] + - name: proxy-init-sentinel-wait-3 + image: busybox:1.28 + command: ['sh', '-c', "until nslookup sentinel-2.sentinel; do echo waiting for sentinel-2.sentinel; sleep 10; done"] containers: - - env: - - name: PIPY_CONFIG_FILE - value: /proxy/proxy.js - image: naqvis/pipy:0.30.0-23 - name: redisproxy + - name: redisproxy + image: openemr/kub-haproxy-redis-tools:2.8 volumeMounts: + - name: redisproxy-certs + mountPath: /certs - name: redisproxyconf - mountPath: /proxy + mountPath: /usr/local/etc/haproxy/haproxy.cfg + subPath: haproxy.cfg + - name: redisproxyhealthcheck + mountPath: /var/lib/haproxy/healthcheck.sh + subPath: healthcheck.sh volumes: + - name: redisproxy-certs + secret: + secretName: redisproxy-certs + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key - name: redisproxyconf configMap: - name: pipy-config - items: - - key: config.json - path: config/config.json - - key: proxy.js - path: proxy.js \ No newline at end of file + name: haproxy-config + - name: redisproxyhealthcheck + configMap: + name: haproxy-healthcheck + defaultMode: 0777 \ No newline at end of file diff --git a/kubernetes/redis/healthcheck-haproxy.yaml b/kubernetes/redis/healthcheck-haproxy.yaml new file mode 100644 index 00000000..1c958438 --- /dev/null +++ b/kubernetes/redis/healthcheck-haproxy.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: haproxy-healthcheck +data: + healthcheck.sh: | + #!/bin/bash + # Set below to true if using redis x509 + REDISX509=false + TLSPARAMETERS="--tls --cacert /certs/ca.crt" + if $REDISX509; then + TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" + fi + REDISREPLY=`/usr/bin/redis-cli --no-auth-warning $TLSPARAMETERS -h $3 --user admin -a adminpassword -p $4 info replication` + if [[ "$REDISREPLY" =~ .*"role:master".* ]]; then + exit 0 + fi + exit 1 diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index bd92adbe..533295ab 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -20,9 +20,15 @@ spec: command: [ "sh", "-c" ] args: - | + # Set below to true if using redis x509 + REDISX509=false + TLSPARAMETERS="--tls --cacert /certs/ca.crt" + if $REDISX509; then + TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" + fi echo "Copying configuration file" cp /tmp/redis/redis.conf /etc/redis/redis.conf - if [ "$(redis-cli -h sentinel -p 5000 ping)" != "PONG" ]; then + if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 5000 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" if [ "$(hostname)" == "redis-0" ]; then echo "This is redis-0, No need to update config." @@ -33,11 +39,13 @@ spec: fi else echo "Sentinel found, finding master" - MASTER="$(redis-cli -h sentinel -p 5000 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" + MASTER="$(redis-cli $TLSPARAMETERS -h sentinel -p 5000 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" echo "Master got: $MASTER, updating this in redis.conf" echo "REPLICAOF $MASTER 6379" >> /etc/redis/redis.conf fi volumeMounts: + - name: redis-certs + mountPath: /certs/ - name: redis-config mountPath: /etc/redis/ - name: config @@ -51,11 +59,23 @@ spec: - containerPort: 6379 name: redis volumeMounts: + - name: redis-certs + mountPath: /certs/ - name: redis-config mountPath: /etc/redis/ - name: config-acl mountPath: /conf/acl/ volumes: + - name: redis-certs + secret: + secretName: redis-certs + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key - name: redis-config emptyDir: {} - name: config diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index 7e20b1f7..c567f806 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -21,13 +21,19 @@ spec: args: - | REDIS_PASSWORD=adminpassword + # Set below to true if using redis x509 + REDISX509=false nodes=redis-0.redis,redis-1.redis,redis-2.redis + TLSPARAMETERS="--tls --cacert /certs/ca.crt" + if $REDISX509; then + TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" + fi echo "Looping through the redis list to see if Redis Master node is available now" while [ 1 ] do for i in ${nodes//,/ } do - MASTER=$(redis-cli --no-auth-warning --raw -h $i --user admin -a $REDIS_PASSWORD info replication | awk '{print $1}' | grep master_host: | cut -d ":" -f2) + MASTER=$(redis-cli $TLSPARAMETERS --no-auth-warning --raw -h $i --user admin -a $REDIS_PASSWORD info replication | awk '{print $1}' | grep master_host: | cut -d ":" -f2) if [ "$MASTER" == "" ]; then echo "no master info found in $i" MASTER= @@ -45,7 +51,15 @@ spec: fi done echo "Creating Sentinel configuration file" - echo "port 5000 + echo "tls-port 5000 + port 0 + tls-cert-file /certs/tls.crt + tls-key-file /certs/tls.key + tls-ca-cert-file /certs/ca.crt + tls-auth-clients no + # uncomment below (and comment line above) if using redis x509 + # tls-auth-clients yes + tls-replication yes sentinel monitor mymaster $MASTER 6379 2 sentinel resolve-hostnames yes sentinel announce-hostnames yes @@ -57,6 +71,8 @@ spec: " > /etc/redis/sentinel.conf cat /etc/redis/sentinel.conf volumeMounts: + - name: sentinel-certs + mountPath: /certs/ - name: redis-config mountPath: /etc/redis/ containers: @@ -68,11 +84,23 @@ spec: - containerPort: 5000 name: sentinel volumeMounts: + - name: sentinel-certs + mountPath: /certs/ - name: redis-config mountPath: /etc/redis/ - name: data mountPath: /data volumes: + - name: sentinel-certs + secret: + secretName: sentinel-certs + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key - name: redis-config emptyDir: {} volumeClaimTemplates: From d5207c238a454927857d899a956c4d4aed709607 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 6 Jun 2023 19:54:42 -0700 Subject: [PATCH 02/21] log debugging trial --- kubernetes/redis/configmap-haproxy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kubernetes/redis/configmap-haproxy.yaml b/kubernetes/redis/configmap-haproxy.yaml index b223cdca..98cae064 100644 --- a/kubernetes/redis/configmap-haproxy.yaml +++ b/kubernetes/redis/configmap-haproxy.yaml @@ -5,11 +5,13 @@ metadata: data: haproxy.cfg: | defaults + log global mode tcp timeout connect 4s timeout server 30s timeout client 30s global + log stdout format raw local0 debug insecure-fork-wanted external-check resolvers mydns From d5b09636895a788779e221fb2fa57b874ad4ed0e Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 6 Jun 2023 22:15:33 -0700 Subject: [PATCH 03/21] test X509 --- kubernetes/openemr/deployment.yaml | 12 ++++++------ kubernetes/redis/configmap-main.yaml | 2 +- kubernetes/redis/healthcheck-haproxy.yaml | 2 +- kubernetes/redis/statefulset-redis.yaml | 2 +- kubernetes/redis/statefulset-sentinel.yaml | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index e94bfc0c..f09bcb93 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -55,8 +55,8 @@ spec: - name: REDIS_TLS value: "yes" # uncomment below if using redis x509 - #- name: REDIS_X509 - # value: "yes" + - name: REDIS_X509 + value: "yes" - name: SWARM_MODE value: "yes" - name: FORCE_DATABASE_SSL_CONNECT @@ -103,10 +103,10 @@ spec: - key: ca.crt path: redis-ca # uncomment below if using redis x509 - #- key: tls.crt - # path: redis-cert - #- key: tls.key - # path: redis-key + - key: tls.crt + path: redis-cert + - key: tls.key + path: redis-key - name: websitevolume persistentVolumeClaim: claimName: websitevolume diff --git a/kubernetes/redis/configmap-main.yaml b/kubernetes/redis/configmap-main.yaml index 269ad0c0..0f773d18 100644 --- a/kubernetes/redis/configmap-main.yaml +++ b/kubernetes/redis/configmap-main.yaml @@ -33,7 +33,7 @@ data: tls-ca-cert-file /certs/ca.crt tls-auth-clients no # uncomment below (and comment line above) if using redis x509 - # tls-auth-clients yes + tls-auth-clients yes tls-replication yes # port, each redis nodes will be used (only use tls) diff --git a/kubernetes/redis/healthcheck-haproxy.yaml b/kubernetes/redis/healthcheck-haproxy.yaml index 1c958438..1d0819bd 100644 --- a/kubernetes/redis/healthcheck-haproxy.yaml +++ b/kubernetes/redis/healthcheck-haproxy.yaml @@ -6,7 +6,7 @@ data: healthcheck.sh: | #!/bin/bash # Set below to true if using redis x509 - REDISX509=false + REDISX509=true TLSPARAMETERS="--tls --cacert /certs/ca.crt" if $REDISX509; then TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 533295ab..426fce66 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -21,7 +21,7 @@ spec: args: - | # Set below to true if using redis x509 - REDISX509=false + REDISX509=true TLSPARAMETERS="--tls --cacert /certs/ca.crt" if $REDISX509; then TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index c567f806..47149087 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -22,7 +22,7 @@ spec: - | REDIS_PASSWORD=adminpassword # Set below to true if using redis x509 - REDISX509=false + REDISX509=true nodes=redis-0.redis,redis-1.redis,redis-2.redis TLSPARAMETERS="--tls --cacert /certs/ca.crt" if $REDISX509; then @@ -58,7 +58,7 @@ spec: tls-ca-cert-file /certs/ca.crt tls-auth-clients no # uncomment below (and comment line above) if using redis x509 - # tls-auth-clients yes + tls-auth-clients yes tls-replication yes sentinel monitor mymaster $MASTER 6379 2 sentinel resolve-hostnames yes From 71c59f09d68891905c1e21fd5e6dd198bc4874d3 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Sat, 17 Jun 2023 19:49:02 -0700 Subject: [PATCH 04/21] interim --- kubernetes/certs/redis.yaml | 3 +++ kubernetes/openemr/deployment.yaml | 4 ++-- kubernetes/redis/configmap-haproxy.yaml | 2 +- kubernetes/redis/configmap-main.yaml | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/kubernetes/certs/redis.yaml b/kubernetes/certs/redis.yaml index b4f94e45..341defb9 100644 --- a/kubernetes/certs/redis.yaml +++ b/kubernetes/certs/redis.yaml @@ -25,6 +25,9 @@ spec: - redis-0.redis - redis-1.redis - redis-2.redis + - redis-0.redis.default.svc.cluster.local + - redis-1.redis.default.svc.cluster.local + - redis-2.redis.default.svc.cluster.local issuerRef: name: ca-issuer kind: Issuer diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index f09bcb93..10397252 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -47,9 +47,9 @@ spec: - name: OE_USER value: "admin" - name: PHPREDIS_BUILD - value: "e571a81f8d3009aab38cbb88dde865edeb0607ac" + value: "35a7cc094c6c264aa37738b074c4c54c4ca73b87" - name: REDIS_SERVER - value: "redisproxy" + value: "redis-0.redis.default.svc.cluster.local" - name: REDIS_PASSWORD value: "defaultpassword" - name: REDIS_TLS diff --git a/kubernetes/redis/configmap-haproxy.yaml b/kubernetes/redis/configmap-haproxy.yaml index 98cae064..1f7e95a1 100644 --- a/kubernetes/redis/configmap-haproxy.yaml +++ b/kubernetes/redis/configmap-haproxy.yaml @@ -24,7 +24,7 @@ data: backend bk_redis option external-check external-check command /var/lib/haproxy/healthcheck.sh - default-server check inter 3s fall 1 rise 1 resolvers mydns + default-server check inter 3s fall 1 rise 1 resolvers mydns ssl verify none server R1 redis-0.redis.default.svc.cluster.local:6379 server R2 redis-1.redis.default.svc.cluster.local:6379 server R3 redis-2.redis.default.svc.cluster.local:6379 diff --git a/kubernetes/redis/configmap-main.yaml b/kubernetes/redis/configmap-main.yaml index 0f773d18..ba80d508 100644 --- a/kubernetes/redis/configmap-main.yaml +++ b/kubernetes/redis/configmap-main.yaml @@ -33,7 +33,7 @@ data: tls-ca-cert-file /certs/ca.crt tls-auth-clients no # uncomment below (and comment line above) if using redis x509 - tls-auth-clients yes + #tls-auth-clients yes tls-replication yes # port, each redis nodes will be used (only use tls) From 5d9398582ffcdb8517cb1e28c0df40dc6d430ccb Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Thu, 22 Jun 2023 21:44:52 -0700 Subject: [PATCH 05/21] add readiness probe to the openemr instance --- kubernetes/openemr/deployment.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 10397252..1339f3ca 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -72,6 +72,13 @@ spec: limits: cpu: 1000m memory: 1G + readinessProbe: + exec: + command: + - cat + - /root/instance-swarm-ready + initialDelaySeconds: 5 + periodSeconds: 30 volumeMounts: - mountPath: /root/certs/mysql/server name: mysql-openemr-client-certs From f93a6c5f80461701a7d6852698c74debcf3e4c84 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Wed, 24 Jan 2024 22:57:00 -0800 Subject: [PATCH 06/21] get rid of PHPREDIS_BUILD setting since no longer needed --- docker/openemr/obsolete/7.0.1/openemr.sh | 26 ---------------------- docker/openemr/obsolete/7.0.2/openemr.sh | 28 ------------------------ kubernetes/openemr/deployment.yaml | 2 -- 3 files changed, 56 deletions(-) diff --git a/docker/openemr/obsolete/7.0.1/openemr.sh b/docker/openemr/obsolete/7.0.1/openemr.sh index df5f9d02..7eccdc35 100644 --- a/docker/openemr/obsolete/7.0.1/openemr.sh +++ b/docker/openemr/obsolete/7.0.1/openemr.sh @@ -242,32 +242,6 @@ if [ "$REDIS_SERVER" != "" ] && # and in swarm mode the docker will be functional after this redis section (ie. if do the below config section first # then the breakage time of the pod will be markedly less). - # Support phpredis build - # This will allow building phpredis towards either most recent development version "develop", - # or a specific sha1 commit id, such as "e571a81f8d3009aab38cbb88dde865edeb0607ac". - # This allows support for tls (ie. encrypted connections) since not available in production - # version 5.3.7 . - if [ "$PHPREDIS_BUILD" != "" ]; then - apk update - apk del --no-cache php81-redis - apk add --no-cache git php81-dev php81-pecl-igbinary gcc make g++ - mkdir /tmpredis - cd /tmpredis - git clone https://github.com/phpredis/phpredis.git - cd /tmpredis/phpredis - if [ "$PHPREDIS_BUILD" != "develop" ]; then - git reset --hard "$PHPREDIS_BUILD" - fi - phpize - ./configure --enable-redis-igbinary - make - make install - echo "extension=redis" > /etc/php81/conf.d/20_redis.ini - rm -fr /tmpredis/phpredis - apk del --no-cache git php81-dev gcc make g++ - cd /var/www/localhost/htdocs/openemr - fi - # Support the following redis auth: # No username and No password set (using redis default user with nopass set) # Both username and password set (using the redis user and pertinent password) diff --git a/docker/openemr/obsolete/7.0.2/openemr.sh b/docker/openemr/obsolete/7.0.2/openemr.sh index ffaf6371..7cb96dc3 100644 --- a/docker/openemr/obsolete/7.0.2/openemr.sh +++ b/docker/openemr/obsolete/7.0.2/openemr.sh @@ -262,34 +262,6 @@ if [ "$REDIS_SERVER" != "" ] && # and in swarm mode the docker will be functional after this redis section (ie. if do the below config section first # then the breakage time of the pod will be markedly less). - # Support phpredis build - # This will allow building phpredis towards either most recent development version "develop", - # or a specific sha1 commit id, such as "e571a81f8d3009aab38cbb88dde865edeb0607ac". - # This allows support for tls (ie. encrypted connections) since not available in production - # version 5.3.7 . - if [ "$PHPREDIS_BUILD" != "" ]; then - apk update - apk del --no-cache php82-redis - apk add --no-cache git php82-dev php82-pecl-igbinary gcc make g++ - mkdir /tmpredis - cd /tmpredis - git clone https://github.com/phpredis/phpredis.git - cd /tmpredis/phpredis - if [ "$PHPREDIS_BUILD" != "develop" ]; then - git reset --hard "$PHPREDIS_BUILD" - fi - # note for php 8.2, needed to change from 'phpize' to: - phpize82 - # note for php 8.2, needed to change from './configure --enable-redis-igbinary' to: - ./configure --with-php-config=/usr/bin/php-config82 --enable-redis-igbinary - make -j $(nproc --all) - make install - echo "extension=redis" > /etc/php82/conf.d/20_redis.ini - rm -fr /tmpredis/phpredis - apk del --no-cache git php82-dev gcc make g++ - cd /var/www/localhost/htdocs/openemr - fi - # Support the following redis auth: # No username and No password set (using redis default user with nopass set) # Both username and password set (using the redis user and pertinent password) diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 1339f3ca..696ab4f6 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -46,8 +46,6 @@ spec: key: admin-pass - name: OE_USER value: "admin" - - name: PHPREDIS_BUILD - value: "35a7cc094c6c264aa37738b074c4c54c4ca73b87" - name: REDIS_SERVER value: "redis-0.redis.default.svc.cluster.local" - name: REDIS_PASSWORD From a43fe31aab7b97e77e7f68434cc48267aaa38c38 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Wed, 11 Jun 2025 14:59:49 -0700 Subject: [PATCH 07/21] interim --- kubernetes/certs/redis.yaml | 3 +- kubernetes/certs/redisproxy.yaml | 26 -------- .../haproxy-28-redis-tools/Dockerfile | 4 -- kubernetes/kub-down | 9 +-- kubernetes/kub-down.bat | 9 +-- kubernetes/kub-up | 7 +- kubernetes/kub-up.bat | 9 +-- kubernetes/redis/configmap-haproxy.yaml | 30 --------- kubernetes/redis/deployment-redisproxy.yaml | 65 ------------------- kubernetes/redis/healthcheck-haproxy.yaml | 18 ----- kubernetes/redis/service-redisproxy.yaml | 12 ---- 11 files changed, 8 insertions(+), 184 deletions(-) delete mode 100644 kubernetes/certs/redisproxy.yaml delete mode 100644 kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile delete mode 100644 kubernetes/redis/configmap-haproxy.yaml delete mode 100644 kubernetes/redis/deployment-redisproxy.yaml delete mode 100644 kubernetes/redis/healthcheck-haproxy.yaml delete mode 100644 kubernetes/redis/service-redisproxy.yaml diff --git a/kubernetes/certs/redis.yaml b/kubernetes/certs/redis.yaml index 341defb9..7c17b752 100644 --- a/kubernetes/certs/redis.yaml +++ b/kubernetes/certs/redis.yaml @@ -21,7 +21,6 @@ spec: - redis commonName: redis dnsNames: - - redisproxy - redis-0.redis - redis-1.redis - redis-2.redis @@ -31,4 +30,4 @@ spec: issuerRef: name: ca-issuer kind: Issuer - group: cert-manager.io \ No newline at end of file + group: cert-manager.io diff --git a/kubernetes/certs/redisproxy.yaml b/kubernetes/certs/redisproxy.yaml deleted file mode 100644 index 40fddbfa..00000000 --- a/kubernetes/certs/redisproxy.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: redisproxy -spec: - secretName: redisproxy-certs - duration: 87660h # 10y - renewBefore: 360h # 15d - isCA: false - privateKey: - size: 2048 - algorithm: RSA - encoding: PKCS1 - usages: - - digital signature - - key encipherment - - server auth - - client auth - subject: - organizations: - - redisproxy - commonName: redisproxy - issuerRef: - name: ca-issuer - kind: Issuer - group: cert-manager.io diff --git a/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile b/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile deleted file mode 100644 index bcc4be55..00000000 --- a/kubernetes/custom-dockers/haproxy-28-redis-tools/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM haproxy:2.8 -USER root -RUN apt-get update && apt-get install -y --no-install-recommends redis-tools -USER haproxy \ No newline at end of file diff --git a/kubernetes/kub-down b/kubernetes/kub-down index 52cc8495..0b46eaa9 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -11,8 +11,7 @@ kubectl delete \ -f certs/mysql-phpmyadmin-client.yaml \ -f certs/redis.yaml \ -f certs/redis-openemr-client.yaml \ - -f certs/sentinel.yaml \ - -f certs/redisproxy.yaml + -f certs/sentinel.yaml kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml @@ -23,14 +22,10 @@ kubectl delete \ -f mysql/statefulset.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ - -f redis/configmap-haproxy.yaml \ - -f redis/healthcheck-haproxy.yaml \ -f redis/statefulset-redis.yaml \ -f redis/statefulset-sentinel.yaml \ - -f redis/deployment-redisproxy.yaml \ -f redis/service-redis.yaml \ -f redis/service-sentinel.yaml \ - -f redis/service-redisproxy.yaml \ -f phpmyadmin/configmap.yaml \ -f phpmyadmin/deployment.yaml \ -f phpmyadmin/service.yaml \ @@ -39,4 +34,4 @@ kubectl delete \ -f volumes/website.yaml \ -f openemr/secret.yaml \ -f openemr/deployment.yaml \ - -f openemr/service.yaml + -f openemr/service.yaml diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index fefdb6eb..48b12ada 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -11,8 +11,7 @@ kubectl delete ^ -f certs/mysql-phpmyadmin-client.yaml ^ -f certs/redis.yaml ^ -f certs/redis-openemr-client.yaml ^ - -f certs/sentinel.yaml ^ - -f certs/redisproxy.yaml + -f certs/sentinel.yaml kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml @@ -23,14 +22,10 @@ kubectl delete ^ -f mysql/statefulset.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ - -f redis/configmap-haproxy.yaml ^ - -f redis/healthcheck-haproxy.yaml ^ -f redis/statefulset-redis.yaml ^ -f redis/statefulset-sentinel.yaml ^ - -f redis/deployment-redisproxy.yaml ^ -f redis/service-redis.yaml ^ -f redis/service-sentinel.yaml ^ - -f redis/service-redisproxy.yaml ^ -f phpmyadmin/configmap.yaml ^ -f phpmyadmin/deployment.yaml ^ -f phpmyadmin/service.yaml ^ @@ -39,4 +34,4 @@ kubectl delete ^ -f volumes/website.yaml ^ -f openemr/secret.yaml ^ -f openemr/deployment.yaml ^ - -f openemr/service.yaml + -f openemr/service.yaml diff --git a/kubernetes/kub-up b/kubernetes/kub-up index 60b39de6..b42d705b 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -16,8 +16,7 @@ kubectl apply \ -f certs/mysql-phpmyadmin-client.yaml \ -f certs/redis.yaml \ -f certs/redis-openemr-client.yaml \ - -f certs/sentinel.yaml \ - -f certs/redisproxy.yaml + -f certs/sentinel.yaml echo "...waiting 15 seconds to ensure certs are created..." sleep 15 @@ -28,14 +27,10 @@ kubectl apply \ -f mysql/statefulset.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ - -f redis/configmap-haproxy.yaml \ - -f redis/healthcheck-haproxy.yaml \ -f redis/statefulset-redis.yaml \ -f redis/statefulset-sentinel.yaml \ - -f redis/deployment-redisproxy.yaml \ -f redis/service-redis.yaml \ -f redis/service-sentinel.yaml \ - -f redis/service-redisproxy.yaml \ -f phpmyadmin/configmap.yaml \ -f phpmyadmin/deployment.yaml \ -f phpmyadmin/service.yaml \ diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index e8f01698..5e1134e4 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -14,8 +14,7 @@ kubectl apply ^ -f certs/mysql-phpmyadmin-client.yaml ^ -f certs/redis.yaml ^ -f certs/redis-openemr-client.yaml ^ - -f certs/sentinel.yaml ^ - -f certs/redisproxy.yaml + -f certs/sentinel.yaml timeout 15 kubectl apply ^ @@ -25,14 +24,10 @@ kubectl apply ^ -f mysql/statefulset.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ - -f redis/configmap-haproxy.yaml ^ - -f redis/healthcheck-haproxy.yaml ^ -f redis/statefulset-redis.yaml ^ -f redis/statefulset-sentinel.yaml ^ - -f redis/deployment-redisproxy.yaml ^ -f redis/service-redis.yaml ^ -f redis/service-sentinel.yaml ^ - -f redis/service-redisproxy.yaml ^ -f phpmyadmin/configmap.yaml ^ -f phpmyadmin/deployment.yaml ^ -f phpmyadmin/service.yaml ^ @@ -41,4 +36,4 @@ kubectl apply ^ -f volumes/website.yaml ^ -f openemr/secret.yaml ^ -f openemr/deployment.yaml ^ - -f openemr/service.yaml + -f openemr/service.yaml diff --git a/kubernetes/redis/configmap-haproxy.yaml b/kubernetes/redis/configmap-haproxy.yaml deleted file mode 100644 index 1f7e95a1..00000000 --- a/kubernetes/redis/configmap-haproxy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: haproxy-config -data: - haproxy.cfg: | - defaults - log global - mode tcp - timeout connect 4s - timeout server 30s - timeout client 30s - global - log stdout format raw local0 debug - insecure-fork-wanted - external-check - resolvers mydns - parse-resolv-conf - accepted_payload_size 4096 - hold valid 3s - frontend ft_redis - bind *:6379 - default_backend bk_redis - backend bk_redis - option external-check - external-check command /var/lib/haproxy/healthcheck.sh - default-server check inter 3s fall 1 rise 1 resolvers mydns ssl verify none - server R1 redis-0.redis.default.svc.cluster.local:6379 - server R2 redis-1.redis.default.svc.cluster.local:6379 - server R3 redis-2.redis.default.svc.cluster.local:6379 diff --git a/kubernetes/redis/deployment-redisproxy.yaml b/kubernetes/redis/deployment-redisproxy.yaml deleted file mode 100644 index f4b59bd5..00000000 --- a/kubernetes/redis/deployment-redisproxy.yaml +++ /dev/null @@ -1,65 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redisproxy -spec: - replicas: 2 - selector: - matchLabels: - app: redisproxy - strategy: - type: Recreate - template: - metadata: - labels: - app: redisproxy - spec: - initContainers: - - name: proxy-init-redis-wait-1 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup redis-0.redis; do echo waiting for redis-0.redis; sleep 10; done"] - - name: proxy-init-redis-wait-2 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup redis-1.redis; do echo waiting for redis-1.redis; sleep 10; done"] - - name: proxy-init-redis-wait-3 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup redis-2.redis; do echo waiting for redis-2.redis; sleep 10; done"] - - name: proxy-init-sentinel-wait-1 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup sentinel-0.sentinel; do echo waiting for sentinel-0.sentinel; sleep 10; done"] - - name: proxy-init-sentinel-wait-2 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup sentinel-1.sentinel; do echo waiting for sentinel-1.sentinel; sleep 10; done"] - - name: proxy-init-sentinel-wait-3 - image: busybox:1.28 - command: ['sh', '-c', "until nslookup sentinel-2.sentinel; do echo waiting for sentinel-2.sentinel; sleep 10; done"] - containers: - - name: redisproxy - image: openemr/kub-haproxy-redis-tools:2.8 - volumeMounts: - - name: redisproxy-certs - mountPath: /certs - - name: redisproxyconf - mountPath: /usr/local/etc/haproxy/haproxy.cfg - subPath: haproxy.cfg - - name: redisproxyhealthcheck - mountPath: /var/lib/haproxy/healthcheck.sh - subPath: healthcheck.sh - volumes: - - name: redisproxy-certs - secret: - secretName: redisproxy-certs - items: - - key: ca.crt - path: ca.crt - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - name: redisproxyconf - configMap: - name: haproxy-config - - name: redisproxyhealthcheck - configMap: - name: haproxy-healthcheck - defaultMode: 0777 \ No newline at end of file diff --git a/kubernetes/redis/healthcheck-haproxy.yaml b/kubernetes/redis/healthcheck-haproxy.yaml deleted file mode 100644 index 1d0819bd..00000000 --- a/kubernetes/redis/healthcheck-haproxy.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: haproxy-healthcheck -data: - healthcheck.sh: | - #!/bin/bash - # Set below to true if using redis x509 - REDISX509=true - TLSPARAMETERS="--tls --cacert /certs/ca.crt" - if $REDISX509; then - TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" - fi - REDISREPLY=`/usr/bin/redis-cli --no-auth-warning $TLSPARAMETERS -h $3 --user admin -a adminpassword -p $4 info replication` - if [[ "$REDISREPLY" =~ .*"role:master".* ]]; then - exit 0 - fi - exit 1 diff --git a/kubernetes/redis/service-redisproxy.yaml b/kubernetes/redis/service-redisproxy.yaml deleted file mode 100644 index d37729bc..00000000 --- a/kubernetes/redis/service-redisproxy.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: redisproxy -spec: - clusterIP: None # "None" make it a headless service. No cluster IP. - ports: - - port: 6379 - targetPort: 6379 - name: redisproxy - selector: - app: redisproxy \ No newline at end of file From eb8e75bbb1d8907ef2193df2116d0f184938fbc0 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Mon, 20 Apr 2026 23:03:51 -0700 Subject: [PATCH 08/21] initial fixups by claude Generated-By: Claude --- kubernetes/README.md | 26 ++++++++++++++++------ kubernetes/certs/sentinel.yaml | 7 ++++++ kubernetes/openemr/deployment.yaml | 5 +++-- kubernetes/redis/configmap-main.yaml | 10 +++++---- kubernetes/redis/statefulset-redis.yaml | 2 +- kubernetes/redis/statefulset-sentinel.yaml | 4 +--- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index 5ff6c990..b65c8a16 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -2,9 +2,27 @@ OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections are encrypted over the wire (ssl is enforced by default; X509 can be enforced by following pertinent comments in following scripts: 2 places in mysql/configmap.yaml, 2 places in openemr/deployment.yaml, 1 place in phpmyadmin/configmap.yaml, 1 place in phpmyadmin/deployment.yaml). - - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset, 3 sentinels for another statefulset, and then 2 proxies deployment. The proxies ensure that redis traffic is always directed towards master. The proxy replications can be increased/decreased. However the primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several place in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in redis/configmap-main.yaml, `admin` in redis/configmap-pipy.yaml, `admin` in redis/statefulset-sentinel.yaml, `admin` in redis/healthcheck-haproxy.yaml. The `default` is the typical worker/app/client user. Connections are encrypted over the wire (ssl is enforced by default; X509 can be enforced by following pertinent comments in following scripts: 2 places in openemr/deployment.yaml, 1 place in redis/configmap-main.yaml, 1 place in redis/healthcheck-haproxy.yaml, 1 place in redis/statefulset-redis.yaml, 2 places in redis/statefulset-sentinel.yaml). + - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset and 3 sentinels for another statefulset. OpenEMR connects directly to Redis with mTLS (mutual TLS / X509 client certificate verification) by default. The primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several places in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in redis/configmap-main.yaml, `admin` in redis/statefulset-sentinel.yaml. The `default` is the typical worker/app/client user. See the **Redis Connection Security** section below for details on the default mTLS configuration and how to downgrade to TLS-only or plain TCP. - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Ports for both http and https. +## Redis Connection Security +By default, Redis connections use **mTLS (mutual TLS)** with X509 client certificate verification. All certificates are managed by cert-manager. To downgrade the connection security: + +### Downgrade to TLS (encrypted, no client certs) +1. `redis/configmap-main.yaml`: Change `tls-auth-clients yes` to `tls-auth-clients no` +2. `redis/statefulset-redis.yaml`: Change `REDISX509=true` to `REDISX509=false` +3. `redis/statefulset-sentinel.yaml`: Change `REDISX509=true` to `REDISX509=false` and change `tls-auth-clients yes` to `tls-auth-clients no` +4. `openemr/deployment.yaml`: Remove the `REDIS_X509` environment variable and remove the `tls.crt` (redis-cert) and `tls.key` (redis-key) items from the `redis-openemr-client-certs` volume + +### Downgrade to TCP (no encryption) +Perform all the TLS downgrade steps above, then additionally: +1. `redis/configmap-main.yaml`: Remove all `tls-*` lines, change `port 0` to `port 6379`, and remove `tls-port 6379` +2. `redis/statefulset-redis.yaml`: Remove the `TLSPARAMETERS` variable and its usage in redis-cli commands, and remove the `redis-certs` volume and volumeMount +3. `redis/statefulset-sentinel.yaml`: Remove the `TLSPARAMETERS` variable and its usage in redis-cli commands, remove the `sentinel-certs` volume and volumeMount, and remove all `tls-*` lines from the sentinel config generation +4. `openemr/deployment.yaml`: Remove the `REDIS_TLS` environment variable and remove the entire `redis-openemr-client-certs` volume and volumeMount +5. `certs/redis.yaml`, `certs/redis-openemr-client.yaml`, `certs/sentinel.yaml`: These cert-manager Certificate resources can be removed entirely +6. `kub-up` and `kub-down` (and `.bat` variants): Remove the redis/sentinel cert references + Would not consider this production quality, but will be a good working, starting point, and hopefully open the door to a myriad of other kubernetes based solutions. Note this is supported by 7.0.0 and higher dockers. If wish to use the most recent development codebase, then can change from openemr/openemr:7.0.3 to openemr/openemr:dev (in the openemr/deployment.yaml script), which is built nightly from the development codebase. If you wish to build dynamically from a branch/tag from a github repo or other git repo, then can change from openemr/openemr:7.0.3 to openemr/openemr:flex (in the openemr/deployment.yaml script) (note this will take much longer to start up (probably at least 10 minutes and up to 90 minutes) and is more cpu intensive since each instance of OpenEMR will download codebase and build separately). # Use @@ -49,8 +67,6 @@ Would not consider this production quality, but will be a good working, starting pod/redis-0 1/1 Running 0 111s pod/redis-1 1/1 Running 0 77s pod/redis-2 1/1 Running 0 55s - pod/redisproxy-744b7749dc-c6pkw 1/1 Running 0 111s - pod/redisproxy-744b7749dc-k8rzp 1/1 Running 0 111s pod/sentinel-0 1/1 Running 0 111s pod/sentinel-1 1/1 Running 0 34s pod/sentinel-2 1/1 Running 0 30s @@ -61,18 +77,15 @@ Would not consider this production quality, but will be a good working, starting service/openemr LoadBalancer 10.96.6.51 8080:32561/TCP,8090:32468/TCP 111s service/phpmyadmin NodePort 10.96.64.163 8081:32195/TCP,8091:31981/TCP 111s service/redis ClusterIP None 6379/TCP 111s - service/redisproxy ClusterIP None 6379/TCP 111s service/sentinel ClusterIP None 5000/TCP 111s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/openemr 3/3 3 3 111s deployment.apps/phpmyadmin 1/1 1 1 111s - deployment.apps/redisproxy 2/2 2 2 111s NAME DESIRED CURRENT READY AGE replicaset.apps/openemr-7889cf48d8 3 3 3 111s replicaset.apps/phpmyadmin-f4d9bfc69 1 1 1 111s - replicaset.apps/redisproxy-744b7749dc 2 2 2 111s NAME READY AGE statefulset.apps/mysql-sts 2/2 111s @@ -117,7 +130,6 @@ Would not consider this production quality, but will be a good working, starting NAME DESIRED CURRENT READY AGE openemr-7889cf48d8 3 3 3 9m22s phpmyadmin-f4d9bfc69 1 1 1 9m22s - redisproxy-744b7749dc 2 2 2 9m22s ``` - Second, lets increase OpenEMR's replicas from 3 to 10 (ie. pretend in an environment where a huge number of OpenEMR users are using the system at the same time) ```bash diff --git a/kubernetes/certs/sentinel.yaml b/kubernetes/certs/sentinel.yaml index 54578ef7..e3a589e3 100644 --- a/kubernetes/certs/sentinel.yaml +++ b/kubernetes/certs/sentinel.yaml @@ -20,6 +20,13 @@ spec: organizations: - sentinel commonName: sentinel + dnsNames: + - sentinel-0.sentinel + - sentinel-1.sentinel + - sentinel-2.sentinel + - sentinel-0.sentinel.default.svc.cluster.local + - sentinel-1.sentinel.default.svc.cluster.local + - sentinel-2.sentinel.default.svc.cluster.local issuerRef: name: ca-issuer kind: Issuer diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 696ab4f6..dfbfb669 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -52,9 +52,11 @@ spec: value: "defaultpassword" - name: REDIS_TLS value: "yes" - # uncomment below if using redis x509 - name: REDIS_X509 value: "yes" + # To downgrade redis connection security: + # tls (no client certs): remove REDIS_X509 above and remove redis cert/key volume mounts below + # tcp (no encryption): also remove REDIS_TLS above and remove redis ca volume mount below - name: SWARM_MODE value: "yes" - name: FORCE_DATABASE_SSL_CONNECT @@ -107,7 +109,6 @@ spec: items: - key: ca.crt path: redis-ca - # uncomment below if using redis x509 - key: tls.crt path: redis-cert - key: tls.key diff --git a/kubernetes/redis/configmap-main.yaml b/kubernetes/redis/configmap-main.yaml index ba80d508..33d3025d 100644 --- a/kubernetes/redis/configmap-main.yaml +++ b/kubernetes/redis/configmap-main.yaml @@ -31,13 +31,15 @@ data: tls-cert-file /certs/tls.crt tls-key-file /certs/tls.key tls-ca-cert-file /certs/ca.crt - tls-auth-clients no - # uncomment below (and comment line above) if using redis x509 - #tls-auth-clients yes + tls-auth-clients yes tls-replication yes - + # port, each redis nodes will be used (only use tls) port 0 tls-port 6379 + + # To downgrade redis connection security: + # tls (no client certs): change "tls-auth-clients yes" to "tls-auth-clients no" + # tcp (no encryption): remove all tls lines above and change "port 0" to "port 6379" and remove "tls-port 6379" # More configurations are optional, if not provided, redis will consider default values ------ # ------ More details on configuration : https://redis.io/docs/manual/config/ ------ \ No newline at end of file diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 426fce66..31fe8a85 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -20,7 +20,7 @@ spec: command: [ "sh", "-c" ] args: - | - # Set below to true if using redis x509 + # To downgrade to tls (no client certs): change to false REDISX509=true TLSPARAMETERS="--tls --cacert /certs/ca.crt" if $REDISX509; then diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index 47149087..32649cb7 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -21,7 +21,7 @@ spec: args: - | REDIS_PASSWORD=adminpassword - # Set below to true if using redis x509 + # To downgrade to tls (no client certs): change to false REDISX509=true nodes=redis-0.redis,redis-1.redis,redis-2.redis TLSPARAMETERS="--tls --cacert /certs/ca.crt" @@ -56,8 +56,6 @@ spec: tls-cert-file /certs/tls.crt tls-key-file /certs/tls.key tls-ca-cert-file /certs/ca.crt - tls-auth-clients no - # uncomment below (and comment line above) if using redis x509 tls-auth-clients yes tls-replication yes sentinel monitor mymaster $MASTER 6379 2 From 2195a70bcfd0f4cbeb4dc693a1fb14540f415779 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Mon, 20 Apr 2026 23:20:33 -0700 Subject: [PATCH 09/21] default mariadb to mtls akin to redis Generated-By: Claude --- kubernetes/README.md | 20 +++++++++++++++++++- kubernetes/mysql/configmap.yaml | 11 +++++------ kubernetes/openemr/deployment.yaml | 17 +++++++++-------- kubernetes/phpmyadmin/configmap.yaml | 6 +++--- kubernetes/phpmyadmin/deployment.yaml | 10 +++++----- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index b65c8a16..6a1ff111 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -1,10 +1,28 @@ # Overview OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections are encrypted over the wire (ssl is enforced by default; X509 can be enforced by following pertinent comments in following scripts: 2 places in mysql/configmap.yaml, 2 places in openemr/deployment.yaml, 1 place in phpmyadmin/configmap.yaml, 1 place in phpmyadmin/deployment.yaml). + - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections use mTLS (mutual TLS / X509 client certificate verification) by default, including replication traffic. See the **MariaDB Connection Security** section below for details and how to downgrade to TLS-only or plain TCP. - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset and 3 sentinels for another statefulset. OpenEMR connects directly to Redis with mTLS (mutual TLS / X509 client certificate verification) by default. The primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several places in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in redis/configmap-main.yaml, `admin` in redis/statefulset-sentinel.yaml. The `default` is the typical worker/app/client user. See the **Redis Connection Security** section below for details on the default mTLS configuration and how to downgrade to TLS-only or plain TCP. - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Ports for both http and https. +## MariaDB Connection Security +By default, MariaDB connections use **mTLS (mutual TLS)** with X509 client certificate verification for all connections (OpenEMR, phpMyAdmin, and replication). All certificates are managed by cert-manager. To downgrade the connection security: + +### Downgrade to TLS (encrypted, no client certs) +1. `mysql/configmap.yaml`: In primary.sql, change `REQUIRE X509` to `REQUIRE SSL`. In secondary.sql, remove the `MASTER_SSL_CERT` and `MASTER_SSL_KEY` lines +2. `openemr/deployment.yaml`: Change `FORCE_DATABASE_X509_CONNECT` to `FORCE_DATABASE_SSL_CONNECT` and remove the `tls.crt` (mysql-cert) and `tls.key` (mysql-key) items from the `mysql-openemr-client-certs` volume +3. `phpmyadmin/configmap.yaml`: Comment out or remove the `ssl_cert` and `ssl_key` lines +4. `phpmyadmin/deployment.yaml`: Remove the `tls.crt` and `tls.key` items from the `mysql-phpmyadmin-client-certs` volume + +### Downgrade to TCP (no encryption) +Perform all the TLS downgrade steps above, then additionally: +1. `mysql/configmap.yaml`: Remove `ssl_ca`, `ssl_cert`, `ssl_key` lines from both primary.cnf and replica.cnf. In primary.sql, change `REQUIRE SSL` to nothing. In secondary.sql, remove `MASTER_SSL_CA`, `MASTER_SSL`, and `MASTER_SSL_VERIFY_SERVER_CERT` lines +2. `openemr/deployment.yaml`: Remove the `FORCE_DATABASE_SSL_CONNECT` environment variable and remove the entire `mysql-openemr-client-certs` volume and volumeMount +3. `phpmyadmin/configmap.yaml`: Set `ssl` to `false`, remove `ssl_ca`, and remove `ssl_verify` +4. `phpmyadmin/deployment.yaml`: Remove the entire `mysql-phpmyadmin-client-certs` volume and volumeMount +5. `certs/mysql.yaml`, `certs/mysql-replication.yaml`, `certs/mysql-openemr-client.yaml`, `certs/mysql-phpmyadmin-client.yaml`: These cert-manager Certificate resources can be removed entirely +6. `kub-up` and `kub-down` (and `.bat` variants): Remove the mysql cert references + ## Redis Connection Security By default, Redis connections use **mTLS (mutual TLS)** with X509 client certificate verification. All certificates are managed by cert-manager. To downgrade the connection security: diff --git a/kubernetes/mysql/configmap.yaml b/kubernetes/mysql/configmap.yaml index 254a0a64..a671b997 100644 --- a/kubernetes/mysql/configmap.yaml +++ b/kubernetes/mysql/configmap.yaml @@ -22,9 +22,8 @@ data: primary.sql: | CREATE USER 'repluser'@'%' IDENTIFIED BY 'replsecret'; - GRANT REPLICATION REPLICA ON *.* TO 'repluser'@'%' REQUIRE SSL; - # uncomment below line (and comment above line) if forcing mysql x509 - # GRANT REPLICATION REPLICA ON *.* TO 'repluser'@'%' REQUIRE X509; + GRANT REPLICATION REPLICA ON *.* TO 'repluser'@'%' REQUIRE X509; + # To downgrade to tls (no client certs): change "REQUIRE X509" to "REQUIRE SSL" CREATE DATABASE primary_db; secondary.sql: | @@ -38,8 +37,8 @@ data: MASTER_PASSWORD='replsecret', MASTER_CONNECT_RETRY=10, MASTER_SSL_CA='/etc/mysql/ssl/replication/ca.crt', - # uncomment below 2 lines if forcing mysql x509 - # MASTER_SSL_CERT = '/etc/mysql/ssl/replication/tls.crt', - # MASTER_SSL_KEY = '/etc/mysql/ssl/replication/tls.key', + MASTER_SSL_CERT='/etc/mysql/ssl/replication/tls.crt', + MASTER_SSL_KEY='/etc/mysql/ssl/replication/tls.key', MASTER_SSL=1, MASTER_SSL_VERIFY_SERVER_CERT=1; + # To downgrade to tls (no client certs): remove MASTER_SSL_CERT and MASTER_SSL_KEY lines above diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index dfbfb669..7fac1de5 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -59,10 +59,12 @@ spec: # tcp (no encryption): also remove REDIS_TLS above and remove redis ca volume mount below - name: SWARM_MODE value: "yes" - - name: FORCE_DATABASE_SSL_CONNECT - # uncomment below line (and comment above line) if forcing mysql x509 - #- name: FORCE_DATABASE_X509_CONNECT + - name: FORCE_DATABASE_X509_CONNECT value: "1" + # To downgrade mysql connection security: + # tls (no client certs): change FORCE_DATABASE_X509_CONNECT to FORCE_DATABASE_SSL_CONNECT + # and remove mysql cert/key volume mounts below + # tcp (no encryption): also remove the env var above and remove mysql ca volume mount below image: openemr/openemr:7.0.3 name: openemr ports: @@ -98,11 +100,10 @@ spec: items: - key: ca.crt path: mysql-ca - # uncomment below if forcing mysql x509 - #- key: tls.crt - # path: mysql-cert - #- key: tls.key - # path: mysql-key + - key: tls.crt + path: mysql-cert + - key: tls.key + path: mysql-key - name: redis-openemr-client-certs secret: secretName: redis-openemr-client-certs diff --git a/kubernetes/phpmyadmin/configmap.yaml b/kubernetes/phpmyadmin/configmap.yaml index fb5028dd..fc95e55c 100644 --- a/kubernetes/phpmyadmin/configmap.yaml +++ b/kubernetes/phpmyadmin/configmap.yaml @@ -13,9 +13,9 @@ data: $cfg['Servers'][$i]['ssl_ca'] = '/etc/mysql/ca.pem'; // Enable SSL verification $cfg['Servers'][$i]['ssl_verify'] = true; - // uncomment below if forcing mysql x509 - // $cfg['Servers'][$i]['ssl_cert'] = '/etc/mysql/tls.crt'; - // $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/tls.key'; + $cfg['Servers'][$i]['ssl_cert'] = '/etc/mysql/tls.crt'; + $cfg['Servers'][$i]['ssl_key'] = '/etc/mysql/tls.key'; + // To downgrade to tls (no client certs): comment out ssl_cert and ssl_key lines above 000-default.conf: | ServerAdmin webmaster@localhost diff --git a/kubernetes/phpmyadmin/deployment.yaml b/kubernetes/phpmyadmin/deployment.yaml index 1edd5a8b..a6bca41f 100644 --- a/kubernetes/phpmyadmin/deployment.yaml +++ b/kubernetes/phpmyadmin/deployment.yaml @@ -62,11 +62,11 @@ spec: items: - key: ca.crt path: ca.pem - # uncomment below if forcing mysql x509 - #- key: tls.crt - # path: tls.crt - #- key: tls.key - # path: tls.key + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + # To downgrade to tls (no client certs): remove tls.crt and tls.key items above - name: phpmyadmin-config-map configMap: name: phpmyadmin-configmap From a8f3ed5fa14628ade1e7cad9c4ed3ca69accb14c Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Mon, 20 Apr 2026 23:31:16 -0700 Subject: [PATCH 10/21] updates Generated-By: Claude --- kubernetes/kub-down | 2 +- kubernetes/kub-down.bat | 2 +- kubernetes/kub-up | 2 +- kubernetes/kub-up.bat | 2 +- kubernetes/mysql/statefulset.yaml | 16 ++++++++++++++++ kubernetes/openemr/deployment.yaml | 9 +++++++-- kubernetes/phpmyadmin/deployment.yaml | 10 ++++++++++ kubernetes/redis/statefulset-redis.yaml | 10 ++++++++++ kubernetes/redis/statefulset-sentinel.yaml | 10 ++++++++++ 9 files changed, 57 insertions(+), 6 deletions(-) diff --git a/kubernetes/kub-down b/kubernetes/kub-down index 0b46eaa9..1f6db2c2 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -13,7 +13,7 @@ kubectl delete \ -f certs/redis-openemr-client.yaml \ -f certs/sentinel.yaml -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml kubectl delete \ -f mysql/configmap.yaml \ diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index 48b12ada..913f715c 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -13,7 +13,7 @@ kubectl delete ^ -f certs/redis-openemr-client.yaml ^ -f certs/sentinel.yaml -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml kubectl delete ^ -f mysql/configmap.yaml ^ diff --git a/kubernetes/kub-up b/kubernetes/kub-up index b42d705b..b243e6ac 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -1,7 +1,7 @@ #!/bin/sh # first bring in cert-manager and allow enough time for it to set up -kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml echo "...waiting a minute to allow cert-manager to set up..." sleep 60 diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 5e1134e4..377f0828 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -1,6 +1,6 @@ @echo off -kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml timeout 60 kubectl apply ^ diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 88649c3a..1d114638 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -65,6 +65,22 @@ spec: ports: - containerPort: 3306 name: mysql-port + livenessProbe: + exec: + command: + - bash + - -c + - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + initialDelaySeconds: 30 + periodSeconds: 20 + readinessProbe: + exec: + command: + - bash + - -c + - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + initialDelaySeconds: 15 + periodSeconds: 10 env: # Using Secrets - name: MYSQL_ROOT_PASSWORD diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 7fac1de5..e53122f5 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -18,10 +18,10 @@ spec: spec: initContainers: - name: init-mysql-wait - image: busybox:1.28 + image: busybox:1.37 command: ['sh', '-c', "until nslookup mysql.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mysql; sleep 2; done"] - name: init-redis-wait - image: busybox:1.28 + image: busybox:1.37 command: ['sh', '-c', "until nslookup redis.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for redis; sleep 2; done"] containers: - env: @@ -81,6 +81,11 @@ spec: - /root/instance-swarm-ready initialDelaySeconds: 5 periodSeconds: 30 + livenessProbe: + tcpSocket: + port: 443 + initialDelaySeconds: 60 + periodSeconds: 30 volumeMounts: - mountPath: /root/certs/mysql/server name: mysql-openemr-client-certs diff --git a/kubernetes/phpmyadmin/deployment.yaml b/kubernetes/phpmyadmin/deployment.yaml index a6bca41f..7e81fb42 100644 --- a/kubernetes/phpmyadmin/deployment.yaml +++ b/kubernetes/phpmyadmin/deployment.yaml @@ -38,6 +38,16 @@ spec: ports: - containerPort: 80 - containerPort: 443 + livenessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 resources: requests: cpu: 100m diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 31fe8a85..15e2c477 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -58,6 +58,16 @@ spec: ports: - containerPort: 6379 name: redis + livenessProbe: + tcpSocket: + port: redis + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + tcpSocket: + port: redis + initialDelaySeconds: 5 + periodSeconds: 10 volumeMounts: - name: redis-certs mountPath: /certs/ diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index 32649cb7..aadedcdd 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -81,6 +81,16 @@ spec: ports: - containerPort: 5000 name: sentinel + livenessProbe: + tcpSocket: + port: sentinel + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + tcpSocket: + port: sentinel + initialDelaySeconds: 5 + periodSeconds: 10 volumeMounts: - name: sentinel-certs mountPath: /certs/ From 08719050982ef0149184bfddf7b077cc08b9ba53 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Mon, 20 Apr 2026 23:42:06 -0700 Subject: [PATCH 11/21] more updates Generated-By: Claude --- kubernetes/README.md | 2 +- kubernetes/kub-down | 2 ++ kubernetes/kub-down.bat | 2 ++ kubernetes/kub-up | 2 ++ kubernetes/kub-up.bat | 2 ++ kubernetes/mysql/configmap.yaml | 4 +-- kubernetes/mysql/replication-secret.yaml | 6 ++++ kubernetes/mysql/statefulset.yaml | 12 ++++++-- kubernetes/openemr/deployment.yaml | 5 ++- kubernetes/redis/configmap-acl.yaml | 6 ++-- kubernetes/redis/configmap-main.yaml | 2 +- kubernetes/redis/secret.yaml | 8 +++++ kubernetes/redis/statefulset-redis.yaml | 36 +++++++++++++++++++--- kubernetes/redis/statefulset-sentinel.yaml | 7 ++++- 14 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 kubernetes/mysql/replication-secret.yaml create mode 100644 kubernetes/redis/secret.yaml diff --git a/kubernetes/README.md b/kubernetes/README.md index 6a1ff111..d250ea5a 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -2,7 +2,7 @@ OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections use mTLS (mutual TLS / X509 client certificate verification) by default, including replication traffic. See the **MariaDB Connection Security** section below for details and how to downgrade to TLS-only or plain TCP. - - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset and 3 sentinels for another statefulset. OpenEMR connects directly to Redis with mTLS (mutual TLS / X509 client certificate verification) by default. The primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several places in the scripts. There are 3 users/passwords (`default` (defaultpassword), `replication` (replicationpassword), `admin` (adminpassword)) used in this redis scheme, and the passwords should be set to something else if use this scheme in production. The main place the passwords are set is in redis/configmap-acl.yaml script. Other places where passwords are used include the following: `replication` in redis/configmap-main.yaml, `admin` in redis/statefulset-sentinel.yaml. The `default` is the typical worker/app/client user. See the **Redis Connection Security** section below for details on the default mTLS configuration and how to downgrade to TLS-only or plain TCP. + - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset and 3 sentinels for another statefulset. OpenEMR connects directly to Redis with mTLS (mutual TLS / X509 client certificate verification) by default. The primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several places in the scripts. There are 3 users/passwords (`default`, `replication`, `admin`) used in this redis scheme. All passwords are stored in the `redis-credentials` Kubernetes Secret (redis/secret.yaml) and should be changed for production use. The `default` is the typical worker/app/client user. See the **Redis Connection Security** section below for details on the default mTLS configuration and how to downgrade to TLS-only or plain TCP. - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Ports for both http and https. ## MariaDB Connection Security diff --git a/kubernetes/kub-down b/kubernetes/kub-down index 1f6db2c2..597d5df4 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -18,8 +18,10 @@ kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download kubectl delete \ -f mysql/configmap.yaml \ -f mysql/secret.yaml \ + -f mysql/replication-secret.yaml \ -f mysql/service.yaml \ -f mysql/statefulset.yaml \ + -f redis/secret.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ -f redis/statefulset-redis.yaml \ diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index 913f715c..ac7e8d0b 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -18,8 +18,10 @@ kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download kubectl delete ^ -f mysql/configmap.yaml ^ -f mysql/secret.yaml ^ + -f mysql/replication-secret.yaml ^ -f mysql/service.yaml ^ -f mysql/statefulset.yaml ^ + -f redis/secret.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ -f redis/statefulset-redis.yaml ^ diff --git a/kubernetes/kub-up b/kubernetes/kub-up index b243e6ac..ab38bb9c 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -23,8 +23,10 @@ sleep 15 kubectl apply \ -f mysql/configmap.yaml \ -f mysql/secret.yaml \ + -f mysql/replication-secret.yaml \ -f mysql/service.yaml \ -f mysql/statefulset.yaml \ + -f redis/secret.yaml \ -f redis/configmap-main.yaml \ -f redis/configmap-acl.yaml \ -f redis/statefulset-redis.yaml \ diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 377f0828..36e58785 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -20,8 +20,10 @@ timeout 15 kubectl apply ^ -f mysql/configmap.yaml ^ -f mysql/secret.yaml ^ + -f mysql/replication-secret.yaml ^ -f mysql/service.yaml ^ -f mysql/statefulset.yaml ^ + -f redis/secret.yaml ^ -f redis/configmap-main.yaml ^ -f redis/configmap-acl.yaml ^ -f redis/statefulset-redis.yaml ^ diff --git a/kubernetes/mysql/configmap.yaml b/kubernetes/mysql/configmap.yaml index a671b997..6e586f1c 100644 --- a/kubernetes/mysql/configmap.yaml +++ b/kubernetes/mysql/configmap.yaml @@ -21,7 +21,7 @@ data: ssl_key=/etc/mysql/ssl/replication/tls.key primary.sql: | - CREATE USER 'repluser'@'%' IDENTIFIED BY 'replsecret'; + CREATE USER 'repluser'@'%' IDENTIFIED BY '__REPL_PASSWORD__'; GRANT REPLICATION REPLICA ON *.* TO 'repluser'@'%' REQUIRE X509; # To downgrade to tls (no client certs): change "REQUIRE X509" to "REQUIRE SSL" CREATE DATABASE primary_db; @@ -34,7 +34,7 @@ data: CHANGE MASTER TO MASTER_HOST='mysql-sts-0.mysql.default.svc.cluster.local', MASTER_USER='repluser', - MASTER_PASSWORD='replsecret', + MASTER_PASSWORD='__REPL_PASSWORD__', MASTER_CONNECT_RETRY=10, MASTER_SSL_CA='/etc/mysql/ssl/replication/ca.crt', MASTER_SSL_CERT='/etc/mysql/ssl/replication/tls.crt', diff --git a/kubernetes/mysql/replication-secret.yaml b/kubernetes/mysql/replication-secret.yaml new file mode 100644 index 00000000..92cd4c4f --- /dev/null +++ b/kubernetes/mysql/replication-secret.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mysql-replication-credentials +stringData: + password: replsecret diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 1d114638..18df1861 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -19,19 +19,25 @@ spec: - mysqld - --character-set-server=utf8mb4 image: mariadb:10.6 + env: + - name: MYSQL_REPL_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-replication-credentials + key: password command: - bash - "-c" - | set -ex echo 'Starting init-mariadb'; - # Check config map to directory that already exists + # Check config map to directory that already exists # (but must be used as a volume for main container) ls /mnt/config-map # Statefulset has sticky identity, number should be last [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} - # Copy appropriate conf.d files from config-map to + # Copy appropriate conf.d files from config-map to # mysql-config volume (emptyDir) depending on pod number if [[ $ordinal -eq 0 ]]; then # This file holds SQL for connecting to primary @@ -45,6 +51,8 @@ spec: # On replicas use secondary configuration on initdb volume cp /mnt/config-map/secondary.sql /docker-entrypoint-initdb.d fi + # Inject replication password from secret + sed -i "s|__REPL_PASSWORD__|$MYSQL_REPL_PASSWORD|g" /docker-entrypoint-initdb.d/*.sql # Add an offset to avoid reserved server-id=0 value. echo server-id=$((3000 + $ordinal)) >> etc/mysql/conf.d/server-id.cnf ls /etc/mysql/conf.d/ diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index e53122f5..d227015e 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -49,7 +49,10 @@ spec: - name: REDIS_SERVER value: "redis-0.redis.default.svc.cluster.local" - name: REDIS_PASSWORD - value: "defaultpassword" + valueFrom: + secretKeyRef: + name: redis-credentials + key: default-password - name: REDIS_TLS value: "yes" - name: REDIS_X509 diff --git a/kubernetes/redis/configmap-acl.yaml b/kubernetes/redis/configmap-acl.yaml index 5f004d88..2d2d2803 100644 --- a/kubernetes/redis/configmap-acl.yaml +++ b/kubernetes/redis/configmap-acl.yaml @@ -4,6 +4,6 @@ metadata: name: redis-acl data: users.acl: | - user admin on >adminpassword ~* &* +@all - user replication on >replicationpassword +psync +replconf +ping - user default on >defaultpassword ~* &* +@all -@dangerous \ No newline at end of file + user admin on >__ADMIN_PASSWORD__ ~* &* +@all + user replication on >__REPLICATION_PASSWORD__ +psync +replconf +ping + user default on >__DEFAULT_PASSWORD__ ~* &* +@all -@dangerous \ No newline at end of file diff --git a/kubernetes/redis/configmap-main.yaml b/kubernetes/redis/configmap-main.yaml index 33d3025d..57742aeb 100644 --- a/kubernetes/redis/configmap-main.yaml +++ b/kubernetes/redis/configmap-main.yaml @@ -21,7 +21,7 @@ data: # This is used by the replics nodes to communicate with master to replicate the data. # we are using a user called "replication" for this, and the a strong pwd for the same is given in masterauth - masterauth replicationpassword + masterauth __REPLICATION_PASSWORD__ masteruser replication # this is the second ConfigMap will be mounted to. it has the list of users needed. diff --git a/kubernetes/redis/secret.yaml b/kubernetes/redis/secret.yaml new file mode 100644 index 00000000..2ed92d84 --- /dev/null +++ b/kubernetes/redis/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: redis-credentials +stringData: + admin-password: adminpassword + replication-password: replicationpassword + default-password: defaultpassword diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 15e2c477..62a19066 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -18,6 +18,22 @@ spec: - name: config image: redis:alpine command: [ "sh", "-c" ] + env: + - name: REDIS_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: admin-password + - name: REDIS_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: replication-password + - name: REDIS_DEFAULT_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: default-password args: - | # To downgrade to tls (no client certs): change to false @@ -26,8 +42,14 @@ spec: if $REDISX509; then TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" fi - echo "Copying configuration file" - cp /tmp/redis/redis.conf /etc/redis/redis.conf + echo "Copying configuration file and injecting passwords" + cp /tmp/redis/redis.conf /etc/redis/redis.conf + sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /etc/redis/redis.conf + echo "Generating ACL file from template" + cp /tmp/acl/users.acl /conf/acl/users.acl + sed -i "s|__ADMIN_PASSWORD__|$REDIS_ADMIN_PASSWORD|g" /conf/acl/users.acl + sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /conf/acl/users.acl + sed -i "s|__DEFAULT_PASSWORD__|$REDIS_DEFAULT_PASSWORD|g" /conf/acl/users.acl if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 5000 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" if [ "$(hostname)" == "redis-0" ]; then @@ -50,6 +72,10 @@ spec: mountPath: /etc/redis/ - name: config mountPath: /tmp/redis/ + - name: config-acl-template + mountPath: /tmp/acl/ + - name: config-acl + mountPath: /conf/acl/ containers: - name: redis image: redis:alpine @@ -91,6 +117,8 @@ spec: - name: config configMap: name: redis-config - - name: config-acl + - name: config-acl-template configMap: - name: redis-acl \ No newline at end of file + name: redis-acl + - name: config-acl + emptyDir: {} \ No newline at end of file diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index aadedcdd..897f8fd4 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -18,9 +18,14 @@ spec: - name: config image: redis:alpine command: [ "sh", "-c" ] + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: admin-password args: - | - REDIS_PASSWORD=adminpassword # To downgrade to tls (no client certs): change to false REDISX509=true nodes=redis-0.redis,redis-1.redis,redis-2.redis From ba5ca27db7ce0d8c401e6158e8339eefa3f62dfe Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 13:38:43 -0700 Subject: [PATCH 12/21] some more updates Generated-By: Claude --- kubernetes/README.md | 8 ++--- kubernetes/openemr/deployment.yaml | 38 ++++++++++++++++------ kubernetes/redis/service-sentinel.yaml | 4 +-- kubernetes/redis/statefulset-redis.yaml | 4 +-- kubernetes/redis/statefulset-sentinel.yaml | 12 +++++-- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index d250ea5a..581109a0 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -24,24 +24,24 @@ Perform all the TLS downgrade steps above, then additionally: 6. `kub-up` and `kub-down` (and `.bat` variants): Remove the mysql cert references ## Redis Connection Security -By default, Redis connections use **mTLS (mutual TLS)** with X509 client certificate verification. All certificates are managed by cert-manager. To downgrade the connection security: +By default, Redis connections use **mTLS (mutual TLS)** with X509 client certificate verification. OpenEMR uses phpredis with Sentinel discovery for automatic failover (`SESSION_STORAGE_MODE=predis-sentinel`). All certificates are managed by cert-manager. To downgrade the connection security: ### Downgrade to TLS (encrypted, no client certs) 1. `redis/configmap-main.yaml`: Change `tls-auth-clients yes` to `tls-auth-clients no` 2. `redis/statefulset-redis.yaml`: Change `REDISX509=true` to `REDISX509=false` 3. `redis/statefulset-sentinel.yaml`: Change `REDISX509=true` to `REDISX509=false` and change `tls-auth-clients yes` to `tls-auth-clients no` -4. `openemr/deployment.yaml`: Remove the `REDIS_X509` environment variable and remove the `tls.crt` (redis-cert) and `tls.key` (redis-key) items from the `redis-openemr-client-certs` volume +4. `openemr/deployment.yaml`: Remove the `REDIS_X509` environment variable and remove the client cert/key items (`redis-master-cert`, `redis-master-key`, `redis-sentinel-cert`, `redis-sentinel-key`) from the `redis-openemr-client-certs` volume ### Downgrade to TCP (no encryption) Perform all the TLS downgrade steps above, then additionally: 1. `redis/configmap-main.yaml`: Remove all `tls-*` lines, change `port 0` to `port 6379`, and remove `tls-port 6379` 2. `redis/statefulset-redis.yaml`: Remove the `TLSPARAMETERS` variable and its usage in redis-cli commands, and remove the `redis-certs` volume and volumeMount 3. `redis/statefulset-sentinel.yaml`: Remove the `TLSPARAMETERS` variable and its usage in redis-cli commands, remove the `sentinel-certs` volume and volumeMount, and remove all `tls-*` lines from the sentinel config generation -4. `openemr/deployment.yaml`: Remove the `REDIS_TLS` environment variable and remove the entire `redis-openemr-client-certs` volume and volumeMount +4. `openemr/deployment.yaml`: Remove the `REDIS_TLS`, `REDIS_X509`, and `REDIS_TLS_CERT_KEY_PATH` environment variables and remove the entire `redis-openemr-client-certs` volume and volumeMount 5. `certs/redis.yaml`, `certs/redis-openemr-client.yaml`, `certs/sentinel.yaml`: These cert-manager Certificate resources can be removed entirely 6. `kub-up` and `kub-down` (and `.bat` variants): Remove the redis/sentinel cert references -Would not consider this production quality, but will be a good working, starting point, and hopefully open the door to a myriad of other kubernetes based solutions. Note this is supported by 7.0.0 and higher dockers. If wish to use the most recent development codebase, then can change from openemr/openemr:7.0.3 to openemr/openemr:dev (in the openemr/deployment.yaml script), which is built nightly from the development codebase. If you wish to build dynamically from a branch/tag from a github repo or other git repo, then can change from openemr/openemr:7.0.3 to openemr/openemr:flex (in the openemr/deployment.yaml script) (note this will take much longer to start up (probably at least 10 minutes and up to 90 minutes) and is more cpu intensive since each instance of OpenEMR will download codebase and build separately). +Would not consider this production quality, but will be a good working, starting point, and hopefully open the door to a myriad of other kubernetes based solutions. Note this is supported by 7.0.0 and higher dockers. If wish to use the most recent development codebase, then can change from openemr/openemr:8.1.1 to openemr/openemr:dev (in the openemr/deployment.yaml script), which is built nightly from the development codebase. If you wish to build dynamically from a branch/tag from a github repo or other git repo, then can change from openemr/openemr:8.1.1 to openemr/openemr:flex (in the openemr/deployment.yaml script) (note this will take much longer to start up (probably at least 10 minutes and up to 90 minutes) and is more cpu intensive since each instance of OpenEMR will download codebase and build separately). # Use 1. Install (and then start) Kubernetes with Minikube or Kind or other. diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index d227015e..e44fb87f 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -46,9 +46,18 @@ spec: key: admin-pass - name: OE_USER value: "admin" - - name: REDIS_SERVER - value: "redis-0.redis.default.svc.cluster.local" - - name: REDIS_PASSWORD + - name: SESSION_STORAGE_MODE + value: "predis-sentinel" + - name: REDIS_SENTINELS + value: "sentinel-0.sentinel|||sentinel-1.sentinel|||sentinel-2.sentinel" + - name: REDIS_MASTER + value: "mymaster" + - name: REDIS_SENTINELS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: default-password + - name: REDIS_MASTER_PASSWORD valueFrom: secretKeyRef: name: redis-credentials @@ -57,9 +66,12 @@ spec: value: "yes" - name: REDIS_X509 value: "yes" + - name: REDIS_TLS_CERT_KEY_PATH + value: "/redis-certs" # To downgrade redis connection security: - # tls (no client certs): remove REDIS_X509 above and remove redis cert/key volume mounts below - # tcp (no encryption): also remove REDIS_TLS above and remove redis ca volume mount below + # tls (no client certs): remove REDIS_X509 above and remove redis client cert/key volume items below + # tcp (no encryption): also remove REDIS_TLS and REDIS_TLS_CERT_KEY_PATH above and remove + # the entire redis-openemr-client-certs volume and volumeMount below - name: SWARM_MODE value: "yes" - name: FORCE_DATABASE_X509_CONNECT @@ -68,7 +80,7 @@ spec: # tls (no client certs): change FORCE_DATABASE_X509_CONNECT to FORCE_DATABASE_SSL_CONNECT # and remove mysql cert/key volume mounts below # tcp (no encryption): also remove the env var above and remove mysql ca volume mount below - image: openemr/openemr:7.0.3 + image: openemr/openemr:8.1.1 name: openemr ports: - containerPort: 80 @@ -92,7 +104,7 @@ spec: volumeMounts: - mountPath: /root/certs/mysql/server name: mysql-openemr-client-certs - - mountPath: /root/certs/redis + - mountPath: /redis-certs name: redis-openemr-client-certs - mountPath: /var/www/localhost/htdocs/openemr/sites name: websitevolume @@ -117,11 +129,17 @@ spec: secretName: redis-openemr-client-certs items: - key: ca.crt - path: redis-ca + path: redis-master-ca + - key: tls.crt + path: redis-master-cert + - key: tls.key + path: redis-master-key + - key: ca.crt + path: redis-sentinel-ca - key: tls.crt - path: redis-cert + path: redis-sentinel-cert - key: tls.key - path: redis-key + path: redis-sentinel-key - name: websitevolume persistentVolumeClaim: claimName: websitevolume diff --git a/kubernetes/redis/service-sentinel.yaml b/kubernetes/redis/service-sentinel.yaml index 62b556b2..d1ca13da 100644 --- a/kubernetes/redis/service-sentinel.yaml +++ b/kubernetes/redis/service-sentinel.yaml @@ -5,8 +5,8 @@ metadata: spec: clusterIP: None ports: - - port: 5000 - targetPort: 5000 + - port: 26379 + targetPort: 26379 name: sentinel selector: app: sentinel \ No newline at end of file diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 62a19066..f3efdf36 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -50,7 +50,7 @@ spec: sed -i "s|__ADMIN_PASSWORD__|$REDIS_ADMIN_PASSWORD|g" /conf/acl/users.acl sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /conf/acl/users.acl sed -i "s|__DEFAULT_PASSWORD__|$REDIS_DEFAULT_PASSWORD|g" /conf/acl/users.acl - if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 5000 ping)" != "PONG" ]; then + if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" if [ "$(hostname)" == "redis-0" ]; then echo "This is redis-0, No need to update config." @@ -61,7 +61,7 @@ spec: fi else echo "Sentinel found, finding master" - MASTER="$(redis-cli $TLSPARAMETERS -h sentinel -p 5000 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" + MASTER="$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" echo "Master got: $MASTER, updating this in redis.conf" echo "REPLICAOF $MASTER 6379" >> /etc/redis/redis.conf fi diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index 897f8fd4..60c8ae85 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -24,6 +24,11 @@ spec: secretKeyRef: name: redis-credentials key: admin-password + - name: REDIS_DEFAULT_PASSWORD + valueFrom: + secretKeyRef: + name: redis-credentials + key: default-password args: - | # To downgrade to tls (no client certs): change to false @@ -56,13 +61,14 @@ spec: fi done echo "Creating Sentinel configuration file" - echo "tls-port 5000 - port 0 + echo "tls-port 26379 + port 0 tls-cert-file /certs/tls.crt tls-key-file /certs/tls.key tls-ca-cert-file /certs/ca.crt tls-auth-clients yes tls-replication yes + requirepass $REDIS_DEFAULT_PASSWORD sentinel monitor mymaster $MASTER 6379 2 sentinel resolve-hostnames yes sentinel announce-hostnames yes @@ -84,7 +90,7 @@ spec: command: ["redis-sentinel"] args: ["/etc/redis/sentinel.conf"] ports: - - containerPort: 5000 + - containerPort: 26379 name: sentinel livenessProbe: tcpSocket: From b3af547e32818ce66b1adedfd4babe012ab823e9 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 15:22:49 -0700 Subject: [PATCH 13/21] more updates Generated-By: Claude --- kubernetes/README.md | 6 +- kubernetes/kind-config-4-nodes.yaml | 21 +----- kubernetes/kind-pvc-hostpath.yaml | 113 ---------------------------- kubernetes/kub-down | 6 ++ kubernetes/kub-down.bat | 6 ++ kubernetes/kub-up | 11 ++- kubernetes/kub-up.bat | 7 ++ kubernetes/nfs/deployment.yaml | 86 +++++++++++++++++++++ kubernetes/nfs/rbac.yaml | 60 +++++++++++++++ kubernetes/nfs/service.yaml | 38 ++++++++++ kubernetes/nfs/storageclass.yaml | 7 ++ kubernetes/volumes/letsencrypt.yaml | 3 +- kubernetes/volumes/ssl.yaml | 3 +- kubernetes/volumes/website.yaml | 3 +- 14 files changed, 229 insertions(+), 141 deletions(-) delete mode 100644 kubernetes/kind-pvc-hostpath.yaml create mode 100644 kubernetes/nfs/deployment.yaml create mode 100644 kubernetes/nfs/rbac.yaml create mode 100644 kubernetes/nfs/service.yaml create mode 100644 kubernetes/nfs/storageclass.yaml diff --git a/kubernetes/README.md b/kubernetes/README.md index 581109a0..6cdbb9eb 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -52,7 +52,7 @@ Would not consider this production quality, but will be a good working, starting kind create cluster kubectl cluster-info --context kind-kind ``` - - 4 nodes (1 control-plane node and 3 worker nodes), which will store shared volumes in host at /tmp/hostpath-provisioner to allow nodes to share volumes (you will need to remove the contents of the /tmp/hostpath-provisioner when tearing this down to prevent the shared volumes causing issues when rebuild it): + - 4 nodes (1 control-plane node and 3 worker nodes). Shared volumes use an in-cluster NFS provisioner (deployed by kub-up) so pods on different nodes can share ReadWriteMany volumes: ```bash kind create cluster --config kind-config-4-nodes.yaml kubectl cluster-info --context kind-kind @@ -185,7 +185,3 @@ Would not consider this production quality, but will be a good working, starting ````bash kind delete cluster ```` - - Additionally, if using Kind with 4 nodes, then also need to delete the shared volume at /tmp/hostpath-provisioner - ````bash - sudo rm -fr /tmp/hostpath-provisioner - ```` diff --git a/kubernetes/kind-config-4-nodes.yaml b/kubernetes/kind-config-4-nodes.yaml index 9d6eddc0..aca095a8 100644 --- a/kubernetes/kind-config-4-nodes.yaml +++ b/kubernetes/kind-config-4-nodes.yaml @@ -1,25 +1,8 @@ # four node (three workers) cluster config -# Supports shared volumes over different nodes, however, to support this, -# have hard-coded hostpath to use /tmp/hostpath-provisioner in this script -# and in the kind-pvc-hostpath.yaml (custom storage class) script. kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - extraMounts: - - hostPath: ./kind-pvc-hostpath.yaml - containerPath: /kind/manifests/default-storage.yaml - - hostPath: /tmp/hostpath-provisioner - containerPath: /tmp/hostpath-provisioner -- role: worker - extraMounts: - - hostPath: /tmp/hostpath-provisioner - containerPath: /tmp/hostpath-provisioner -- role: worker - extraMounts: - - hostPath: /tmp/hostpath-provisioner - containerPath: /tmp/hostpath-provisioner - role: worker - extraMounts: - - hostPath: /tmp/hostpath-provisioner - containerPath: /tmp/hostpath-provisioner \ No newline at end of file +- role: worker +- role: worker diff --git a/kubernetes/kind-pvc-hostpath.yaml b/kubernetes/kind-pvc-hostpath.yaml deleted file mode 100644 index 6db9f218..00000000 --- a/kubernetes/kind-pvc-hostpath.yaml +++ /dev/null @@ -1,113 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: local-storage ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: hostpath-provisioner - namespace: local-storage ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: hostpath-provisioner -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: hostpath-provisioner -subjects: - - kind: ServiceAccount - name: hostpath-provisioner - namespace: local-storage -roleRef: - kind: ClusterRole - name: hostpath-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: leader-locking-hostpath-provisioner - namespace: local-storage -rules: - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["get", "update", "patch"] - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["list", "watch", "create"] ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: leader-locking-hostpath-provisioner - namespace: local-storage -subjects: - - kind: ServiceAccount - name: hostpath-provisioner - namespace: local-storage -roleRef: - kind: Role - name: leader-locking-hostpath-provisioner - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: hostpath-provisioner - namespace: local-storage - labels: - app: hostpath-provisioner -spec: - replicas: 3 - selector: - matchLabels: - app: hostpath-provisioner - template: - metadata: - labels: - app: hostpath-provisioner - spec: - containers: - - name: hostpath-provisioner - image: mauilion/hostpath-provisioner:dev - imagePullPolicy: "IfNotPresent" - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: pv-volume - mountPath: /tmp/hostpath-provisioner - serviceAccountName: hostpath-provisioner - volumes: - - name: pv-volume - hostPath: - path: /tmp/hostpath-provisioner ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: standard - annotations: - storageclass.kubernetes.io/is-default-class: "true" -reclaimPolicy: Retain -provisioner: example.com/hostpath \ No newline at end of file diff --git a/kubernetes/kub-down b/kubernetes/kub-down index 597d5df4..fba0df09 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -37,3 +37,9 @@ kubectl delete \ -f openemr/secret.yaml \ -f openemr/deployment.yaml \ -f openemr/service.yaml + +kubectl delete \ + -f nfs/rbac.yaml \ + -f nfs/storageclass.yaml \ + -f nfs/service.yaml \ + -f nfs/deployment.yaml diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index ac7e8d0b..74cf67f7 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -37,3 +37,9 @@ kubectl delete ^ -f openemr/secret.yaml ^ -f openemr/deployment.yaml ^ -f openemr/service.yaml + +kubectl delete ^ + -f nfs/rbac.yaml ^ + -f nfs/storageclass.yaml ^ + -f nfs/service.yaml ^ + -f nfs/deployment.yaml diff --git a/kubernetes/kub-up b/kubernetes/kub-up index ab38bb9c..fb913f61 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -1,6 +1,15 @@ #!/bin/sh -# first bring in cert-manager and allow enough time for it to set up +# first bring in NFS provisioner for shared volumes (ReadWriteMany) +kubectl apply \ + -f nfs/rbac.yaml \ + -f nfs/storageclass.yaml \ + -f nfs/service.yaml \ + -f nfs/deployment.yaml +echo "...waiting 30 seconds for NFS provisioner to start..." +sleep 30 + +# bring in cert-manager and allow enough time for it to set up kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml echo "...waiting a minute to allow cert-manager to set up..." sleep 60 diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 36e58785..40e380c3 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -1,5 +1,12 @@ @echo off +kubectl apply ^ + -f nfs/rbac.yaml ^ + -f nfs/storageclass.yaml ^ + -f nfs/service.yaml ^ + -f nfs/deployment.yaml +timeout 30 + kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml timeout 60 diff --git a/kubernetes/nfs/deployment.yaml b/kubernetes/nfs/deployment.yaml new file mode 100644 index 00000000..1174c4b6 --- /dev/null +++ b/kubernetes/nfs/deployment.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nfs-provisioner +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: nfs-provisioner + template: + metadata: + labels: + app: nfs-provisioner + spec: + serviceAccountName: nfs-provisioner + containers: + - name: nfs-provisioner + image: registry.k8s.io/sig-storage/nfs-provisioner:v4.0.8 + ports: + - name: nfs + containerPort: 2049 + - name: nfs-udp + containerPort: 2049 + protocol: UDP + - name: nlockmgr + containerPort: 32803 + - name: nlockmgr-udp + containerPort: 32803 + protocol: UDP + - name: mountd + containerPort: 20048 + - name: mountd-udp + containerPort: 20048 + protocol: UDP + - name: rquotad + containerPort: 875 + - name: rquotad-udp + containerPort: 875 + protocol: UDP + - name: rpcbind + containerPort: 111 + - name: rpcbind-udp + containerPort: 111 + protocol: UDP + - name: statd + containerPort: 662 + - name: statd-udp + containerPort: 662 + protocol: UDP + securityContext: + capabilities: + add: + - DAC_READ_SEARCH + - SYS_RESOURCE + args: + - "-provisioner=openemr.org/nfs" + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: SERVICE_NAME + value: nfs-provisioner + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: export-volume + mountPath: /export + livenessProbe: + tcpSocket: + port: nfs + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + tcpSocket: + port: nfs + initialDelaySeconds: 5 + periodSeconds: 10 + volumes: + - name: export-volume + hostPath: + path: /tmp/nfs-provisioner diff --git a/kubernetes/nfs/rbac.yaml b/kubernetes/nfs/rbac.yaml new file mode 100644 index 00000000..56ebaf00 --- /dev/null +++ b/kubernetes/nfs/rbac.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nfs-provisioner +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-provisioner +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "update", "patch"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-provisioner +subjects: + - kind: ServiceAccount + name: nfs-provisioner + namespace: default +roleRef: + kind: ClusterRole + name: nfs-provisioner + apiGroup: rbac.authorization.k8s.io +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-provisioner-leader-locking +rules: + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch", "create", "update", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-provisioner-leader-locking +subjects: + - kind: ServiceAccount + name: nfs-provisioner + namespace: default +roleRef: + kind: Role + name: nfs-provisioner-leader-locking + apiGroup: rbac.authorization.k8s.io diff --git a/kubernetes/nfs/service.yaml b/kubernetes/nfs/service.yaml new file mode 100644 index 00000000..f2795337 --- /dev/null +++ b/kubernetes/nfs/service.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Service +metadata: + name: nfs-provisioner +spec: + ports: + - name: nfs + port: 2049 + - name: nfs-udp + port: 2049 + protocol: UDP + - name: nlockmgr + port: 32803 + - name: nlockmgr-udp + port: 32803 + protocol: UDP + - name: mountd + port: 20048 + - name: mountd-udp + port: 20048 + protocol: UDP + - name: rquotad + port: 875 + - name: rquotad-udp + port: 875 + protocol: UDP + - name: rpcbind + port: 111 + - name: rpcbind-udp + port: 111 + protocol: UDP + - name: statd + port: 662 + - name: statd-udp + port: 662 + protocol: UDP + selector: + app: nfs-provisioner diff --git a/kubernetes/nfs/storageclass.yaml b/kubernetes/nfs/storageclass.yaml new file mode 100644 index 00000000..503978d5 --- /dev/null +++ b/kubernetes/nfs/storageclass.yaml @@ -0,0 +1,7 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: nfs +provisioner: openemr.org/nfs +mountOptions: + - vers=4.1 diff --git a/kubernetes/volumes/letsencrypt.yaml b/kubernetes/volumes/letsencrypt.yaml index cae392c4..602f627e 100644 --- a/kubernetes/volumes/letsencrypt.yaml +++ b/kubernetes/volumes/letsencrypt.yaml @@ -6,8 +6,9 @@ metadata: io.kompose.service: letsencryptvolume name: letsencryptvolume spec: + storageClassName: nfs accessModes: - - ReadWriteOnce + - ReadWriteMany resources: requests: storage: 1Gi diff --git a/kubernetes/volumes/ssl.yaml b/kubernetes/volumes/ssl.yaml index 53a9f977..bf9be3f8 100644 --- a/kubernetes/volumes/ssl.yaml +++ b/kubernetes/volumes/ssl.yaml @@ -3,8 +3,9 @@ kind: PersistentVolumeClaim metadata: name: sslvolume spec: + storageClassName: nfs accessModes: - - ReadWriteOnce + - ReadWriteMany resources: requests: storage: 1Gi diff --git a/kubernetes/volumes/website.yaml b/kubernetes/volumes/website.yaml index 85a63f82..f99f482c 100644 --- a/kubernetes/volumes/website.yaml +++ b/kubernetes/volumes/website.yaml @@ -3,8 +3,9 @@ kind: PersistentVolumeClaim metadata: name: websitevolume spec: + storageClassName: nfs accessModes: - - ReadWriteOnce + - ReadWriteMany resources: requests: storage: 10Gi \ No newline at end of file From 6eb994741db22026d9e5ad9346a49d543a4665a6 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 16:27:09 -0700 Subject: [PATCH 14/21] more updates Generated-By: Claude --- kubernetes/kub-up | 8 +++++--- kubernetes/kub-up.bat | 3 ++- kubernetes/nfs/rbac.yaml | 3 +++ kubernetes/openemr/deployment.yaml | 2 +- kubernetes/redis/statefulset-redis.yaml | 2 ++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/kubernetes/kub-up b/kubernetes/kub-up index fb913f61..da296e25 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -9,10 +9,12 @@ kubectl apply \ echo "...waiting 30 seconds for NFS provisioner to start..." sleep 30 -# bring in cert-manager and allow enough time for it to set up +# bring in cert-manager and wait for its webhook to be ready kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml -echo "...waiting a minute to allow cert-manager to set up..." -sleep 60 +echo "...waiting for cert-manager webhook to be ready..." +kubectl wait --for=condition=Available --timeout=120s -n cert-manager deployment/cert-manager-webhook +echo "...waiting 15 seconds for webhook to stabilize..." +sleep 15 kubectl apply \ -f certs/selfsigned-issuer.yaml \ diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 40e380c3..94693e7e 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -8,7 +8,8 @@ kubectl apply ^ timeout 30 kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml -timeout 60 +kubectl wait --for=condition=Available --timeout=120s -n cert-manager deployment/cert-manager-webhook +timeout 15 kubectl apply ^ -f certs/selfsigned-issuer.yaml ^ diff --git a/kubernetes/nfs/rbac.yaml b/kubernetes/nfs/rbac.yaml index 56ebaf00..5379344b 100644 --- a/kubernetes/nfs/rbac.yaml +++ b/kubernetes/nfs/rbac.yaml @@ -17,6 +17,9 @@ rules: - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["services"] + verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index e44fb87f..5cd8197f 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -80,7 +80,7 @@ spec: # tls (no client certs): change FORCE_DATABASE_X509_CONNECT to FORCE_DATABASE_SSL_CONNECT # and remove mysql cert/key volume mounts below # tcp (no encryption): also remove the env var above and remove mysql ca volume mount below - image: openemr/openemr:8.1.1 + image: openemr/openemr:dev name: openemr ports: - containerPort: 80 diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index f3efdf36..0331bab7 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -45,6 +45,8 @@ spec: echo "Copying configuration file and injecting passwords" cp /tmp/redis/redis.conf /etc/redis/redis.conf sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /etc/redis/redis.conf + MY_FQDN="$(hostname).redis" + echo "replica-announce-ip $MY_FQDN" >> /etc/redis/redis.conf echo "Generating ACL file from template" cp /tmp/acl/users.acl /conf/acl/users.acl sed -i "s|__ADMIN_PASSWORD__|$REDIS_ADMIN_PASSWORD|g" /conf/acl/users.acl From e75160d922a3ca1fd6ad7df61183f1f6ebcd521c Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 18:23:11 -0700 Subject: [PATCH 15/21] more updates Generated-By: Claude --- kubernetes/README.md | 6 ------ kubernetes/kub-up | 4 ++++ kubernetes/kub-up.bat | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index 6cdbb9eb..addfebb4 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -50,17 +50,11 @@ Would not consider this production quality, but will be a good working, starting - 1 node: ```bash kind create cluster - kubectl cluster-info --context kind-kind ``` - 4 nodes (1 control-plane node and 3 worker nodes). Shared volumes use an in-cluster NFS provisioner (deployed by kub-up) so pods on different nodes can share ReadWriteMany volumes: ```bash kind create cluster --config kind-config-4-nodes.yaml - kubectl cluster-info --context kind-kind ``` - - Use following command to ensure all the nodes are ready before proceeding to next step - ```bash - kubectl get nodes - ``` - After you run the kub-up command below, here is a neat command to show which nodes the pods are in ```bash kubectl get pod -o wide diff --git a/kubernetes/kub-up b/kubernetes/kub-up index da296e25..8e6fb941 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -1,5 +1,9 @@ #!/bin/sh +# wait for all nodes to be ready before proceeding +echo "...waiting for all nodes to be ready..." +kubectl wait --for=condition=Ready nodes --all --timeout=120s + # first bring in NFS provisioner for shared volumes (ReadWriteMany) kubectl apply \ -f nfs/rbac.yaml \ diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index 94693e7e..d9329cff 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -1,5 +1,7 @@ @echo off +kubectl wait --for=condition=Ready nodes --all --timeout=120s + kubectl apply ^ -f nfs/rbac.yaml ^ -f nfs/storageclass.yaml ^ From 6225d370e8cd40aeb6bee4f3b2eddcba82fd10e5 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 23:59:20 -0700 Subject: [PATCH 16/21] more updates Generated-By: Claude --- README.md | 2 +- kubernetes/README.md | 60 +++++++++++----------- kubernetes/kind-config-4-nodes.yaml | 5 ++ kubernetes/kub-down | 3 +- kubernetes/kub-down.bat | 3 +- kubernetes/kub-up | 3 +- kubernetes/kub-up.bat | 3 +- kubernetes/mysql/statefulset.yaml | 1 + kubernetes/openemr/deployment.yaml | 5 +- kubernetes/openemr/service.yaml | 4 +- kubernetes/phpmyadmin/deployment.yaml | 1 + kubernetes/phpmyadmin/service.yaml | 6 +-- kubernetes/redis/statefulset-redis.yaml | 1 + kubernetes/redis/statefulset-sentinel.yaml | 1 + 14 files changed, 58 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 5603a223..29d68282 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ OpenEMR administration and deployment tooling ### Deployment Options * [Ubuntu Installer](packages/appliance): Launch OpenEMR on any Ubuntu 24.04 instance -* [Kubernetes](kubernetes): Two-worker OpenEMR Kubernetes orchestration on Minikube +* [Kubernetes](kubernetes): OpenEMR Kubernetes orchestration with mTLS, Redis Sentinel failover, and multi-node support * [Raspberry Pi](raspberrypi): Install OpenEMR Docker on Raspberry Pi (supports ARMv8 infrastructure) ### Installations for Amazon Web Services diff --git a/kubernetes/README.md b/kubernetes/README.md index addfebb4..16cb0b71 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -1,4 +1,6 @@ # Overview +This solution requires OpenEMR Docker 8.1.0 or higher. While not a fully hardened production deployment, this provides a solid working foundation with mTLS encryption, Redis Sentinel failover, and multi-node support, and should open the door to a myriad of other Kubernetes-based solutions. + OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections use mTLS (mutual TLS / X509 client certificate verification) by default, including replication traffic. See the **MariaDB Connection Security** section below for details and how to downgrade to TLS-only or plain TCP. @@ -41,15 +43,13 @@ Perform all the TLS downgrade steps above, then additionally: 5. `certs/redis.yaml`, `certs/redis-openemr-client.yaml`, `certs/sentinel.yaml`: These cert-manager Certificate resources can be removed entirely 6. `kub-up` and `kub-down` (and `.bat` variants): Remove the redis/sentinel cert references -Would not consider this production quality, but will be a good working, starting point, and hopefully open the door to a myriad of other kubernetes based solutions. Note this is supported by 7.0.0 and higher dockers. If wish to use the most recent development codebase, then can change from openemr/openemr:8.1.1 to openemr/openemr:dev (in the openemr/deployment.yaml script), which is built nightly from the development codebase. If you wish to build dynamically from a branch/tag from a github repo or other git repo, then can change from openemr/openemr:8.1.1 to openemr/openemr:flex (in the openemr/deployment.yaml script) (note this will take much longer to start up (probably at least 10 minutes and up to 90 minutes) and is more cpu intensive since each instance of OpenEMR will download codebase and build separately). - # Use 1. Install (and then start) Kubernetes with Minikube or Kind or other. - For Minikube or other, can find online documentation. - For Kind, see below for instructions sets with 1 node or 4 nodes. - 1 node: ```bash - kind create cluster + kind create cluster --config kind-config-1-node.yaml ``` - 4 nodes (1 control-plane node and 3 worker nodes). Shared volumes use an in-cluster NFS provisioner (deployed by kub-up) so pods on different nodes can share ReadWriteMany volumes: ```bash @@ -86,10 +86,10 @@ Would not consider this production quality, but will be a good working, starting NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 443/TCP 3m40s service/mysql ClusterIP None 3306/TCP 111s - service/openemr LoadBalancer 10.96.6.51 8080:32561/TCP,8090:32468/TCP 111s - service/phpmyadmin NodePort 10.96.64.163 8081:32195/TCP,8091:31981/TCP 111s + service/openemr NodePort 10.96.6.51 8080:30080/TCP,8090:30443/TCP 111s + service/phpmyadmin ClusterIP 10.96.64.163 8081/TCP,8091/TCP 111s service/redis ClusterIP None 6379/TCP 111s - service/sentinel ClusterIP None 5000/TCP 111s + service/sentinel ClusterIP None 26379/TCP 111s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/openemr 3/3 3 3 111s @@ -105,33 +105,17 @@ Would not consider this production quality, but will be a good working, starting statefulset.apps/sentinel 3/3 111s ``` 4. Getting the url link to OpenEMR: - - If using minikube, can get the link to go to OpenEMR with following command (use the top link for http and bottom link for https): + - If using kind with the provided config files, OpenEMR is mapped to localhost: `http://localhost:8800` or `https://localhost:9800` + - If using minikube: ```bash minikube service openemr --url ``` - - It will look something like this: - ```console - http://192.168.99.100:31314 - http://192.168.99.100:30613 - ``` - - If using kind, then can use the 3***** port(s) (1st is http, 2nd is https) shown in step 3 (at `service/openemr`) above with the ip address obtained from following command: - ```bash - docker inspect kind-control-plane | grep "IPAddress" - ``` -5. Getting the url link to phpMyAdmin: - - If using minikube, can get the link to go to phpMyAdmin with following command: - ```bash - minikube service phpmyadmin --url - ``` - - It will look something like this: - ```console - http://192.168.99.100:30571 - http://192.168.99.100:30578 - ``` - - If using kind, then can use the 3***** port(s) (1st is http, 2nd is https) shown in step 3 (at `service/phpmyadmin`) above with the ip address obtained from following command: +5. Accessing phpMyAdmin: + - phpMyAdmin is not exposed externally for security. Access it via port-forward: ```bash - docker inspect kind-control-plane | grep "IPAddress" + kubectl port-forward service/phpmyadmin 8081:8081 ``` + Then navigate to `http://localhost:8081`. Press `Ctrl+C` to stop the port-forward when done. 6. Some cool replicas stuff with OpenEMR. The OpenEMR docker pods are run as a replica set (since it is set to 3 replicas in this OpenEMR deployment script). Gonna cover how to view the replica set and how to change the number of replicas on the fly in this step. - First. lets list the replica set like this: ```bash @@ -171,7 +155,25 @@ Would not consider this production quality, but will be a good working, starting ```bash kubectl scale sts mysql-sts --replicas=3 ``` -8. To stop and remove OpenEMR orchestration (this will delete everything): +8. Testing Redis Sentinel failover. Redis is configured with automatic failover via Sentinel. To test it: + - First, check which Redis pod is the current master: + ```bash + kubectl exec redis-0 -- redis-cli --tls --cacert /certs/ca.crt --cert /certs/tls.crt --key /certs/tls.key --user admin -a adminpassword info replication | grep role + ``` + - Delete the master pod to simulate a failure: + ```bash + kubectl delete pod redis-0 + ``` + - Watch the sentinel logs to see the failover happen (~1 second): + ```bash + kubectl logs sentinel-0 | grep failover + ``` + - Verify a new master was promoted: + ```bash + kubectl exec redis-1 -- redis-cli --tls --cacert /certs/ca.crt --cert /certs/tls.crt --key /certs/tls.key --user admin -a adminpassword info replication | grep role + ``` + - OpenEMR continues working throughout the failover — the Sentinel-based session handler automatically discovers the new master. +9. To stop and remove OpenEMR orchestration (this will delete everything): ```bash bash kub-down ``` diff --git a/kubernetes/kind-config-4-nodes.yaml b/kubernetes/kind-config-4-nodes.yaml index aca095a8..c09f469a 100644 --- a/kubernetes/kind-config-4-nodes.yaml +++ b/kubernetes/kind-config-4-nodes.yaml @@ -3,6 +3,11 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 8800 + - containerPort: 30443 + hostPort: 9800 - role: worker - role: worker - role: worker diff --git a/kubernetes/kub-down b/kubernetes/kub-down index fba0df09..14ad4d62 100644 --- a/kubernetes/kub-down +++ b/kubernetes/kub-down @@ -36,7 +36,8 @@ kubectl delete \ -f volumes/website.yaml \ -f openemr/secret.yaml \ -f openemr/deployment.yaml \ - -f openemr/service.yaml + -f openemr/service.yaml \ + -f network/policies.yaml kubectl delete \ -f nfs/rbac.yaml \ diff --git a/kubernetes/kub-down.bat b/kubernetes/kub-down.bat index 74cf67f7..1300887f 100644 --- a/kubernetes/kub-down.bat +++ b/kubernetes/kub-down.bat @@ -36,7 +36,8 @@ kubectl delete ^ -f volumes/website.yaml ^ -f openemr/secret.yaml ^ -f openemr/deployment.yaml ^ - -f openemr/service.yaml + -f openemr/service.yaml ^ + -f network/policies.yaml kubectl delete ^ -f nfs/rbac.yaml ^ diff --git a/kubernetes/kub-up b/kubernetes/kub-up index 8e6fb941..1bef3fb4 100644 --- a/kubernetes/kub-up +++ b/kubernetes/kub-up @@ -56,4 +56,5 @@ kubectl apply \ -f volumes/website.yaml \ -f openemr/secret.yaml \ -f openemr/deployment.yaml \ - -f openemr/service.yaml + -f openemr/service.yaml \ + -f network/policies.yaml diff --git a/kubernetes/kub-up.bat b/kubernetes/kub-up.bat index d9329cff..badcd5ac 100644 --- a/kubernetes/kub-up.bat +++ b/kubernetes/kub-up.bat @@ -48,4 +48,5 @@ kubectl apply ^ -f volumes/website.yaml ^ -f openemr/secret.yaml ^ -f openemr/deployment.yaml ^ - -f openemr/service.yaml + -f openemr/service.yaml ^ + -f network/policies.yaml diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 18df1861..37c1db4a 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -13,6 +13,7 @@ spec: labels: app: mysql spec: + automountServiceAccountToken: false initContainers: - name: init-mysql args: diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 5cd8197f..70e58492 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -16,13 +16,14 @@ spec: labels: name: openemr spec: + automountServiceAccountToken: false initContainers: - name: init-mysql-wait image: busybox:1.37 - command: ['sh', '-c', "until nslookup mysql.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mysql; sleep 2; done"] + command: ['sh', '-c', "until nslookup mysql.default.svc.cluster.local; do echo waiting for mysql; sleep 2; done"] - name: init-redis-wait image: busybox:1.37 - command: ['sh', '-c', "until nslookup redis.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for redis; sleep 2; done"] + command: ['sh', '-c', "until nslookup redis.default.svc.cluster.local; do echo waiting for redis; sleep 2; done"] containers: - env: - name: MYSQL_HOST diff --git a/kubernetes/openemr/service.yaml b/kubernetes/openemr/service.yaml index f929fb9b..783a9572 100644 --- a/kubernetes/openemr/service.yaml +++ b/kubernetes/openemr/service.yaml @@ -5,13 +5,15 @@ metadata: name: openemr name: openemr spec: - type: LoadBalancer + type: NodePort ports: - name: "http" port: 8080 targetPort: 80 + nodePort: 30080 - name: "https" port: 8090 targetPort: 443 + nodePort: 30443 selector: name: openemr diff --git a/kubernetes/phpmyadmin/deployment.yaml b/kubernetes/phpmyadmin/deployment.yaml index 7e81fb42..aafd0ba3 100644 --- a/kubernetes/phpmyadmin/deployment.yaml +++ b/kubernetes/phpmyadmin/deployment.yaml @@ -14,6 +14,7 @@ spec: labels: name: phpmyadmin spec: + automountServiceAccountToken: false containers: - env: - name: PMA_HOSTS diff --git a/kubernetes/phpmyadmin/service.yaml b/kubernetes/phpmyadmin/service.yaml index 426fe766..742de5fb 100644 --- a/kubernetes/phpmyadmin/service.yaml +++ b/kubernetes/phpmyadmin/service.yaml @@ -3,12 +3,12 @@ kind: Service metadata: name: phpmyadmin spec: - type: NodePort + type: ClusterIP ports: - - name: "8081" + - name: "http" port: 8081 targetPort: 80 - - name: "8091" + - name: "https" port: 8091 targetPort: 443 selector: diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 0331bab7..50c2ae7d 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -14,6 +14,7 @@ spec: labels: app: redis spec: + automountServiceAccountToken: false initContainers: - name: config image: redis:alpine diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index 60c8ae85..a390f458 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -14,6 +14,7 @@ spec: labels: app: sentinel spec: + automountServiceAccountToken: false initContainers: - name: config image: redis:alpine From b3d8d2cb65352e62abe28e1dd690ab2d9b3a0d52 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 21 Apr 2026 23:59:38 -0700 Subject: [PATCH 17/21] more updates Generated-By: Claude --- kubernetes/kind-config-1-node.yaml | 10 ++ kubernetes/network/policies.yaml | 142 +++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 kubernetes/kind-config-1-node.yaml create mode 100644 kubernetes/network/policies.yaml diff --git a/kubernetes/kind-config-1-node.yaml b/kubernetes/kind-config-1-node.yaml new file mode 100644 index 00000000..ba0a1882 --- /dev/null +++ b/kubernetes/kind-config-1-node.yaml @@ -0,0 +1,10 @@ +# single node cluster config +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 8800 + - containerPort: 30443 + hostPort: 9800 diff --git a/kubernetes/network/policies.yaml b/kubernetes/network/policies.yaml new file mode 100644 index 00000000..6866daf5 --- /dev/null +++ b/kubernetes/network/policies.yaml @@ -0,0 +1,142 @@ +# Default deny all ingress traffic in the default namespace +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress +spec: + podSelector: {} + policyTypes: + - Ingress +--- +# OpenEMR: allow ingress from anywhere on http/https (user-facing) +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-openemr-ingress +spec: + podSelector: + matchLabels: + name: openemr + policyTypes: + - Ingress + ingress: + - ports: + - port: 80 + - port: 443 +--- +# MySQL: allow ingress from OpenEMR and phpMyAdmin on 3306, +# and from other MySQL pods for replication +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-mysql-ingress +spec: + podSelector: + matchLabels: + app: mysql + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + name: openemr + - podSelector: + matchLabels: + name: phpmyadmin + - podSelector: + matchLabels: + app: mysql + ports: + - port: 3306 +--- +# Redis: allow ingress from OpenEMR on 6379, +# from sentinel on 6379, and from other Redis pods for replication +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis-ingress +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + name: openemr + - podSelector: + matchLabels: + app: sentinel + - podSelector: + matchLabels: + app: redis + ports: + - port: 6379 +--- +# Sentinel: allow ingress from OpenEMR on 26379, +# from other sentinels, and from Redis pods +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-sentinel-ingress +spec: + podSelector: + matchLabels: + app: sentinel + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + name: openemr + - podSelector: + matchLabels: + app: sentinel + - podSelector: + matchLabels: + app: redis + ports: + - port: 26379 +--- +# phpMyAdmin: allow ingress only from localhost/port-forward (no external) +# ClusterIP service already restricts this, but this policy ensures +# no other pod can reach phpMyAdmin either +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-phpmyadmin-ingress +spec: + podSelector: + matchLabels: + name: phpmyadmin + policyTypes: + - Ingress + ingress: + - ports: + - port: 80 + - port: 443 +--- +# NFS provisioner: allow ingress from all pods that need shared volumes +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-nfs-ingress +spec: + podSelector: + matchLabels: + app: nfs-provisioner + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + name: openemr + ports: + - port: 2049 + - port: 111 + - port: 20048 From cf75ae7bcefa6f8d256de7b84f1a68355b4d182c Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Sat, 25 Apr 2026 22:41:27 -0700 Subject: [PATCH 18/21] address copilot review --- kubernetes/mysql/statefulset.yaml | 6 ++++-- kubernetes/network/policies.yaml | 11 +++-------- kubernetes/openemr/deployment.yaml | 4 ++-- kubernetes/redis/statefulset-redis.yaml | 13 +++++++------ kubernetes/redis/statefulset-sentinel.yaml | 14 ++++++++++---- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 37c1db4a..32914257 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -52,8 +52,10 @@ spec: # On replicas use secondary configuration on initdb volume cp /mnt/config-map/secondary.sql /docker-entrypoint-initdb.d fi - # Inject replication password from secret - sed -i "s|__REPL_PASSWORD__|$MYSQL_REPL_PASSWORD|g" /docker-entrypoint-initdb.d/*.sql + # Inject replication password from secret (awk for safe handling of special chars) + for f in /docker-entrypoint-initdb.d/*.sql; do + awk -v val="$MYSQL_REPL_PASSWORD" '{gsub(/__REPL_PASSWORD__/, val); print}' "$f" > "$f.tmp" && mv "$f.tmp" "$f" + done # Add an offset to avoid reserved server-id=0 value. echo server-id=$((3000 + $ordinal)) >> etc/mysql/conf.d/server-id.cnf ls /etc/mysql/conf.d/ diff --git a/kubernetes/network/policies.yaml b/kubernetes/network/policies.yaml index 6866daf5..1db49e35 100644 --- a/kubernetes/network/policies.yaml +++ b/kubernetes/network/policies.yaml @@ -102,23 +102,18 @@ spec: ports: - port: 26379 --- -# phpMyAdmin: allow ingress only from localhost/port-forward (no external) -# ClusterIP service already restricts this, but this policy ensures -# no other pod can reach phpMyAdmin either +# phpMyAdmin: deny all pod-to-pod ingress. +# Access is only via kubectl port-forward, which bypasses NetworkPolicy. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: allow-phpmyadmin-ingress + name: deny-phpmyadmin-ingress spec: podSelector: matchLabels: name: phpmyadmin policyTypes: - Ingress - ingress: - - ports: - - port: 80 - - port: 443 --- # NFS provisioner: allow ingress from all pods that need shared volumes apiVersion: networking.k8s.io/v1 diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index 70e58492..df10ec73 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -20,10 +20,10 @@ spec: initContainers: - name: init-mysql-wait image: busybox:1.37 - command: ['sh', '-c', "until nslookup mysql.default.svc.cluster.local; do echo waiting for mysql; sleep 2; done"] + command: ['sh', '-c', "until nslookup mysql; do echo waiting for mysql; sleep 2; done"] - name: init-redis-wait image: busybox:1.37 - command: ['sh', '-c', "until nslookup redis.default.svc.cluster.local; do echo waiting for redis; sleep 2; done"] + command: ['sh', '-c', "until nslookup redis; do echo waiting for redis; sleep 2; done"] containers: - env: - name: MYSQL_HOST diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 50c2ae7d..49e1d442 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -45,15 +45,16 @@ spec: fi echo "Copying configuration file and injecting passwords" cp /tmp/redis/redis.conf /etc/redis/redis.conf - sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /etc/redis/redis.conf + awk -v val="$REDIS_REPLICATION_PASSWORD" '{gsub(/__REPLICATION_PASSWORD__/, val); print}' /etc/redis/redis.conf > /etc/redis/redis.conf.tmp && mv /etc/redis/redis.conf.tmp /etc/redis/redis.conf MY_FQDN="$(hostname).redis" echo "replica-announce-ip $MY_FQDN" >> /etc/redis/redis.conf echo "Generating ACL file from template" cp /tmp/acl/users.acl /conf/acl/users.acl - sed -i "s|__ADMIN_PASSWORD__|$REDIS_ADMIN_PASSWORD|g" /conf/acl/users.acl - sed -i "s|__REPLICATION_PASSWORD__|$REDIS_REPLICATION_PASSWORD|g" /conf/acl/users.acl - sed -i "s|__DEFAULT_PASSWORD__|$REDIS_DEFAULT_PASSWORD|g" /conf/acl/users.acl - if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 ping)" != "PONG" ]; then + awk -v val="$REDIS_ADMIN_PASSWORD" '{gsub(/__ADMIN_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl + awk -v val="$REDIS_REPLICATION_PASSWORD" '{gsub(/__REPLICATION_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl + awk -v val="$REDIS_DEFAULT_PASSWORD" '{gsub(/__DEFAULT_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl + SENTINELAUTH="--no-auth-warning -a $REDIS_DEFAULT_PASSWORD" + if [ "$(redis-cli $TLSPARAMETERS $SENTINELAUTH -h sentinel -p 26379 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" if [ "$(hostname)" == "redis-0" ]; then echo "This is redis-0, No need to update config." @@ -64,7 +65,7 @@ spec: fi else echo "Sentinel found, finding master" - MASTER="$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" + MASTER="$(redis-cli $TLSPARAMETERS $SENTINELAUTH -h sentinel -p 26379 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" echo "Master got: $MASTER, updating this in redis.conf" echo "REPLICAOF $MASTER 6379" >> /etc/redis/redis.conf fi diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index a390f458..f1d9d85d 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -62,12 +62,11 @@ spec: fi done echo "Creating Sentinel configuration file" - echo "tls-port 26379 + SENTINEL_CONF="tls-port 26379 port 0 tls-cert-file /certs/tls.crt tls-key-file /certs/tls.key tls-ca-cert-file /certs/ca.crt - tls-auth-clients yes tls-replication yes requirepass $REDIS_DEFAULT_PASSWORD sentinel monitor mymaster $MASTER 6379 2 @@ -77,8 +76,15 @@ spec: sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 sentinel auth-user mymaster admin - sentinel auth-pass mymaster $REDIS_PASSWORD - " > /etc/redis/sentinel.conf + sentinel auth-pass mymaster $REDIS_PASSWORD" + if $REDISX509; then + SENTINEL_CONF="$SENTINEL_CONF + tls-auth-clients yes" + else + SENTINEL_CONF="$SENTINEL_CONF + tls-auth-clients no" + fi + echo "$SENTINEL_CONF" > /etc/redis/sentinel.conf cat /etc/redis/sentinel.conf volumeMounts: - name: sentinel-certs From b8cc690ff66d256b1de1586ec995155044c7a7b9 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Sun, 26 Apr 2026 14:42:47 -0700 Subject: [PATCH 19/21] address second round copilot review --- kubernetes/mysql/statefulset.yaml | 9 +++++++-- kubernetes/network/policies.yaml | 10 ++++------ kubernetes/openemr/deployment.yaml | 2 +- kubernetes/redis/statefulset-redis.yaml | 12 ++++++++---- kubernetes/redis/statefulset-sentinel.yaml | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 32914257..08a9ac70 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -52,10 +52,15 @@ spec: # On replicas use secondary configuration on initdb volume cp /mnt/config-map/secondary.sql /docker-entrypoint-initdb.d fi - # Inject replication password from secret (awk for safe handling of special chars) + # Inject replication password from secret + # Uses bash parameter expansion for literal replacement (safe with &, \, etc.) + # Disable xtrace to prevent password leaking into logs + set +x for f in /docker-entrypoint-initdb.d/*.sql; do - awk -v val="$MYSQL_REPL_PASSWORD" '{gsub(/__REPL_PASSWORD__/, val); print}' "$f" > "$f.tmp" && mv "$f.tmp" "$f" + contents=$(<"$f") + printf '%s\n' "${contents//__REPL_PASSWORD__/$MYSQL_REPL_PASSWORD}" > "$f" done + set -x # Add an offset to avoid reserved server-id=0 value. echo server-id=$((3000 + $ordinal)) >> etc/mysql/conf.d/server-id.cnf ls /etc/mysql/conf.d/ diff --git a/kubernetes/network/policies.yaml b/kubernetes/network/policies.yaml index 1db49e35..50f7bc4d 100644 --- a/kubernetes/network/policies.yaml +++ b/kubernetes/network/policies.yaml @@ -115,7 +115,9 @@ spec: policyTypes: - Ingress --- -# NFS provisioner: allow ingress from all pods that need shared volumes +# NFS provisioner: allow all ingress. +# NFS mounts originate from kubelet on nodes, not from pods, so +# pod-selector-based restrictions would block legitimate mounts. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -127,11 +129,7 @@ spec: policyTypes: - Ingress ingress: - - from: - - podSelector: - matchLabels: - name: openemr - ports: + - ports: - port: 2049 - port: 111 - port: 20048 diff --git a/kubernetes/openemr/deployment.yaml b/kubernetes/openemr/deployment.yaml index df10ec73..34679839 100644 --- a/kubernetes/openemr/deployment.yaml +++ b/kubernetes/openemr/deployment.yaml @@ -81,7 +81,7 @@ spec: # tls (no client certs): change FORCE_DATABASE_X509_CONNECT to FORCE_DATABASE_SSL_CONNECT # and remove mysql cert/key volume mounts below # tcp (no encryption): also remove the env var above and remove mysql ca volume mount below - image: openemr/openemr:dev + image: openemr/openemr:8.1.0 name: openemr ports: - containerPort: 80 diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 49e1d442..6c533061 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -45,14 +45,18 @@ spec: fi echo "Copying configuration file and injecting passwords" cp /tmp/redis/redis.conf /etc/redis/redis.conf - awk -v val="$REDIS_REPLICATION_PASSWORD" '{gsub(/__REPLICATION_PASSWORD__/, val); print}' /etc/redis/redis.conf > /etc/redis/redis.conf.tmp && mv /etc/redis/redis.conf.tmp /etc/redis/redis.conf + # Uses shell parameter expansion for literal replacement (safe with &, \, etc.) + contents=$(cat /etc/redis/redis.conf) + printf '%s\n' "${contents//__REPLICATION_PASSWORD__/$REDIS_REPLICATION_PASSWORD}" > /etc/redis/redis.conf MY_FQDN="$(hostname).redis" echo "replica-announce-ip $MY_FQDN" >> /etc/redis/redis.conf echo "Generating ACL file from template" cp /tmp/acl/users.acl /conf/acl/users.acl - awk -v val="$REDIS_ADMIN_PASSWORD" '{gsub(/__ADMIN_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl - awk -v val="$REDIS_REPLICATION_PASSWORD" '{gsub(/__REPLICATION_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl - awk -v val="$REDIS_DEFAULT_PASSWORD" '{gsub(/__DEFAULT_PASSWORD__/, val); print}' /conf/acl/users.acl > /conf/acl/users.acl.tmp && mv /conf/acl/users.acl.tmp /conf/acl/users.acl + contents=$(cat /conf/acl/users.acl) + contents="${contents//__ADMIN_PASSWORD__/$REDIS_ADMIN_PASSWORD}" + contents="${contents//__REPLICATION_PASSWORD__/$REDIS_REPLICATION_PASSWORD}" + contents="${contents//__DEFAULT_PASSWORD__/$REDIS_DEFAULT_PASSWORD}" + printf '%s\n' "$contents" > /conf/acl/users.acl SENTINELAUTH="--no-auth-warning -a $REDIS_DEFAULT_PASSWORD" if [ "$(redis-cli $TLSPARAMETERS $SENTINELAUTH -h sentinel -p 26379 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index f1d9d85d..d1025e96 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -85,7 +85,7 @@ spec: tls-auth-clients no" fi echo "$SENTINEL_CONF" > /etc/redis/sentinel.conf - cat /etc/redis/sentinel.conf + echo "Sentinel configuration written" volumeMounts: - name: sentinel-certs mountPath: /certs/ From a3071dc7c54c7fccf464921a305a06193861b40f Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Sun, 26 Apr 2026 17:24:51 -0700 Subject: [PATCH 20/21] addressed pertinent issues on aisle ai review --- kubernetes/README.md | 70 ++++++++++++---------- kubernetes/kind-config-1-node.yaml | 2 + kubernetes/kind-config-4-nodes.yaml | 2 + kubernetes/mysql/statefulset.yaml | 13 ++-- kubernetes/redis/statefulset-redis.yaml | 17 ++++-- kubernetes/redis/statefulset-sentinel.yaml | 3 +- 6 files changed, 67 insertions(+), 40 deletions(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index 16cb0b71..e3e250c7 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -5,7 +5,10 @@ OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https. - MariaDB - 2 statefulset replications of MariaDB (1 primary/master with 1 replica/slave) are created. Replications can be increased/decreased which will increase/decrease number of replica/slaves. Connections use mTLS (mutual TLS / X509 client certificate verification) by default, including replication traffic. See the **MariaDB Connection Security** section below for details and how to downgrade to TLS-only or plain TCP. - Redis - Configured to support failover. There is 1 master and 2 slaves (no read access on slaves) for a statefulset and 3 sentinels for another statefulset. OpenEMR connects directly to Redis with mTLS (mutual TLS / X509 client certificate verification) by default. The primary/slaves and sentinels would require script changes if wish to increase/decrease replicates for these since these are hard-coded several places in the scripts. There are 3 users/passwords (`default`, `replication`, `admin`) used in this redis scheme. All passwords are stored in the `redis-credentials` Kubernetes Secret (redis/secret.yaml) and should be changed for production use. The `default` is the typical worker/app/client user. See the **Redis Connection Security** section below for details on the default mTLS configuration and how to downgrade to TLS-only or plain TCP. - - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Ports for both http and https. + - phpMyAdmin - There is 1 deployment instance of phpMyAdmin. Access is via `kubectl port-forward` only (not exposed externally). + +## Secrets Management +All passwords (Redis, MariaDB replication) are stored in Kubernetes Secret resources with default values suitable for development and testing. For production deployments, these Secret YAML files (`redis/secret.yaml`, `mysql/replication-secret.yaml`, `mysql/secret.yaml`, `openemr/secret.yaml`) should be replaced with secrets managed by an external secret manager (e.g., HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager) using an operator like External Secrets Operator. The rest of the deployment (init containers, env var references, volume mounts) references Kubernetes Secrets by name and requires no changes regardless of how the secrets are provisioned. ## MariaDB Connection Security By default, MariaDB connections use **mTLS (mutual TLS)** with X509 client certificate verification for all connections (OpenEMR, phpMyAdmin, and replication). All certificates are managed by cert-manager. To downgrade the connection security: @@ -31,7 +34,7 @@ By default, Redis connections use **mTLS (mutual TLS)** with X509 client certifi ### Downgrade to TLS (encrypted, no client certs) 1. `redis/configmap-main.yaml`: Change `tls-auth-clients yes` to `tls-auth-clients no` 2. `redis/statefulset-redis.yaml`: Change `REDISX509=true` to `REDISX509=false` -3. `redis/statefulset-sentinel.yaml`: Change `REDISX509=true` to `REDISX509=false` and change `tls-auth-clients yes` to `tls-auth-clients no` +3. `redis/statefulset-sentinel.yaml`: Change `REDISX509=true` to `REDISX509=false` (the sentinel config automatically sets `tls-auth-clients` based on this value) 4. `openemr/deployment.yaml`: Remove the `REDIS_X509` environment variable and remove the client cert/key items (`redis-master-cert`, `redis-master-key`, `redis-sentinel-cert`, `redis-sentinel-key`) from the `redis-openemr-client-certs` volume ### Downgrade to TCP (no encryption) @@ -69,35 +72,39 @@ Perform all the TLS downgrade steps above, then additionally: ``` - It will look something like this when completed: ```console - NAME READY STATUS RESTARTS AGE - pod/mysql-sts-0 1/1 Running 0 111s - pod/mysql-sts-1 1/1 Running 0 91s - pod/openemr-7889cf48d8-9jdfl 1/1 Running 0 111s - pod/openemr-7889cf48d8-qphrw 1/1 Running 0 111s - pod/openemr-7889cf48d8-zlx9f 1/1 Running 0 111s - pod/phpmyadmin-f4d9bfc69-rx82d 1/1 Running 0 111s - pod/redis-0 1/1 Running 0 111s - pod/redis-1 1/1 Running 0 77s - pod/redis-2 1/1 Running 0 55s - pod/sentinel-0 1/1 Running 0 111s - pod/sentinel-1 1/1 Running 0 34s - pod/sentinel-2 1/1 Running 0 30s + NAME READY STATUS RESTARTS AGE + pod/mysql-sts-0 1/1 Running 0 111s + pod/mysql-sts-1 1/1 Running 0 91s + pod/nfs-provisioner-77f85859c4-xxxxx 1/1 Running 0 3m + pod/openemr-7889cf48d8-9jdfl 1/1 Running 0 111s + pod/openemr-7889cf48d8-qphrw 1/1 Running 0 111s + pod/openemr-7889cf48d8-zlx9f 1/1 Running 0 111s + pod/phpmyadmin-f4d9bfc69-rx82d 1/1 Running 0 111s + pod/redis-0 1/1 Running 0 111s + pod/redis-1 1/1 Running 0 77s + pod/redis-2 1/1 Running 0 55s + pod/sentinel-0 1/1 Running 0 111s + pod/sentinel-1 1/1 Running 0 34s + pod/sentinel-2 1/1 Running 0 30s - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - service/kubernetes ClusterIP 10.96.0.1 443/TCP 3m40s - service/mysql ClusterIP None 3306/TCP 111s - service/openemr NodePort 10.96.6.51 8080:30080/TCP,8090:30443/TCP 111s - service/phpmyadmin ClusterIP 10.96.64.163 8081/TCP,8091/TCP 111s - service/redis ClusterIP None 6379/TCP 111s - service/sentinel ClusterIP None 26379/TCP 111s + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + service/kubernetes ClusterIP 10.96.0.1 443/TCP 3m40s + service/mysql ClusterIP None 3306/TCP 111s + service/nfs-provisioner ClusterIP 10.96.1.73 2049/TCP,2049/UDP,... 3m + service/openemr NodePort 10.96.6.51 8080:30080/TCP,8090:30443/TCP 111s + service/phpmyadmin ClusterIP 10.96.64.163 8081/TCP,8091/TCP 111s + service/redis ClusterIP None 6379/TCP 111s + service/sentinel ClusterIP None 26379/TCP 111s - NAME READY UP-TO-DATE AVAILABLE AGE - deployment.apps/openemr 3/3 3 3 111s - deployment.apps/phpmyadmin 1/1 1 1 111s + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/nfs-provisioner 1/1 1 1 3m + deployment.apps/openemr 3/3 3 3 111s + deployment.apps/phpmyadmin 1/1 1 1 111s - NAME DESIRED CURRENT READY AGE - replicaset.apps/openemr-7889cf48d8 3 3 3 111s - replicaset.apps/phpmyadmin-f4d9bfc69 1 1 1 111s + NAME DESIRED CURRENT READY AGE + replicaset.apps/nfs-provisioner-77f85859c4 1 1 1 3m + replicaset.apps/openemr-7889cf48d8 3 3 3 111s + replicaset.apps/phpmyadmin-f4d9bfc69 1 1 1 111s NAME READY AGE statefulset.apps/mysql-sts 2/2 111s @@ -123,9 +130,10 @@ Perform all the TLS downgrade steps above, then additionally: ``` - It will look something like this (note OpenEMR has 3 desired and 3 current replicas going): ```console - NAME DESIRED CURRENT READY AGE - openemr-7889cf48d8 3 3 3 9m22s - phpmyadmin-f4d9bfc69 1 1 1 9m22s + NAME DESIRED CURRENT READY AGE + nfs-provisioner-77f85859c4 1 1 1 11m + openemr-7889cf48d8 3 3 3 9m22s + phpmyadmin-f4d9bfc69 1 1 1 9m22s ``` - Second, lets increase OpenEMR's replicas from 3 to 10 (ie. pretend in an environment where a huge number of OpenEMR users are using the system at the same time) ```bash diff --git a/kubernetes/kind-config-1-node.yaml b/kubernetes/kind-config-1-node.yaml index ba0a1882..e97f84a4 100644 --- a/kubernetes/kind-config-1-node.yaml +++ b/kubernetes/kind-config-1-node.yaml @@ -6,5 +6,7 @@ nodes: extraPortMappings: - containerPort: 30080 hostPort: 8800 + listenAddress: "127.0.0.1" - containerPort: 30443 hostPort: 9800 + listenAddress: "127.0.0.1" diff --git a/kubernetes/kind-config-4-nodes.yaml b/kubernetes/kind-config-4-nodes.yaml index c09f469a..b2852761 100644 --- a/kubernetes/kind-config-4-nodes.yaml +++ b/kubernetes/kind-config-4-nodes.yaml @@ -6,8 +6,10 @@ nodes: extraPortMappings: - containerPort: 30080 hostPort: 8800 + listenAddress: "127.0.0.1" - containerPort: 30443 hostPort: 9800 + listenAddress: "127.0.0.1" - role: worker - role: worker - role: worker diff --git a/kubernetes/mysql/statefulset.yaml b/kubernetes/mysql/statefulset.yaml index 08a9ac70..6ea55749 100644 --- a/kubernetes/mysql/statefulset.yaml +++ b/kubernetes/mysql/statefulset.yaml @@ -53,9 +53,14 @@ spec: cp /mnt/config-map/secondary.sql /docker-entrypoint-initdb.d fi # Inject replication password from secret - # Uses bash parameter expansion for literal replacement (safe with &, \, etc.) - # Disable xtrace to prevent password leaking into logs + # Validate password doesn't contain characters that would break SQL literals set +x + case "$MYSQL_REPL_PASSWORD" in + *"'"*|*$'\n'*|*$'\r'*|*$'\t'*|*";"*) + echo "ERROR: MYSQL_REPL_PASSWORD contains unsafe characters for SQL (single quote, newline, semicolon)" >&2 + exit 1 + ;; + esac for f in /docker-entrypoint-initdb.d/*.sql; do contents=$(<"$f") printf '%s\n' "${contents//__REPL_PASSWORD__/$MYSQL_REPL_PASSWORD}" > "$f" @@ -86,7 +91,7 @@ spec: command: - bash - -c - - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" mysqladmin ping -u root initialDelaySeconds: 30 periodSeconds: 20 readinessProbe: @@ -94,7 +99,7 @@ spec: command: - bash - -c - - mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD} + - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" mysqladmin ping -u root initialDelaySeconds: 15 periodSeconds: 10 env: diff --git a/kubernetes/redis/statefulset-redis.yaml b/kubernetes/redis/statefulset-redis.yaml index 6c533061..19564485 100644 --- a/kubernetes/redis/statefulset-redis.yaml +++ b/kubernetes/redis/statefulset-redis.yaml @@ -43,9 +43,18 @@ spec: if $REDISX509; then TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" fi + # Validate passwords don't contain characters that would break Redis config/ACL format + for pw_name in REDIS_ADMIN_PASSWORD REDIS_REPLICATION_PASSWORD REDIS_DEFAULT_PASSWORD; do + eval pw_val=\$$pw_name + case "$pw_val" in + *" "*|*$'\t'*|*$'\n'*|*$'\r'*|*">"*) + echo "ERROR: $pw_name contains unsafe characters for Redis ACL (whitespace, >)" >&2 + exit 1 + ;; + esac + done echo "Copying configuration file and injecting passwords" cp /tmp/redis/redis.conf /etc/redis/redis.conf - # Uses shell parameter expansion for literal replacement (safe with &, \, etc.) contents=$(cat /etc/redis/redis.conf) printf '%s\n' "${contents//__REPLICATION_PASSWORD__/$REDIS_REPLICATION_PASSWORD}" > /etc/redis/redis.conf MY_FQDN="$(hostname).redis" @@ -57,8 +66,8 @@ spec: contents="${contents//__REPLICATION_PASSWORD__/$REDIS_REPLICATION_PASSWORD}" contents="${contents//__DEFAULT_PASSWORD__/$REDIS_DEFAULT_PASSWORD}" printf '%s\n' "$contents" > /conf/acl/users.acl - SENTINELAUTH="--no-auth-warning -a $REDIS_DEFAULT_PASSWORD" - if [ "$(redis-cli $TLSPARAMETERS $SENTINELAUTH -h sentinel -p 26379 ping)" != "PONG" ]; then + export REDISCLI_AUTH="$REDIS_DEFAULT_PASSWORD" + if [ "$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 ping)" != "PONG" ]; then echo "Sentinel not found to get the master info, defaulting to redis-0" if [ "$(hostname)" == "redis-0" ]; then echo "This is redis-0, No need to update config." @@ -69,7 +78,7 @@ spec: fi else echo "Sentinel found, finding master" - MASTER="$(redis-cli $TLSPARAMETERS $SENTINELAUTH -h sentinel -p 26379 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" + MASTER="$(redis-cli $TLSPARAMETERS -h sentinel -p 26379 sentinel get-master-addr-by-name mymaster | grep -E '(^redis-\d{1,})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})')" echo "Master got: $MASTER, updating this in redis.conf" echo "REPLICAOF $MASTER 6379" >> /etc/redis/redis.conf fi diff --git a/kubernetes/redis/statefulset-sentinel.yaml b/kubernetes/redis/statefulset-sentinel.yaml index d1025e96..0119b7fb 100644 --- a/kubernetes/redis/statefulset-sentinel.yaml +++ b/kubernetes/redis/statefulset-sentinel.yaml @@ -39,12 +39,13 @@ spec: if $REDISX509; then TLSPARAMETERS="$TLSPARAMETERS --cert /certs/tls.crt --key /certs/tls.key" fi + export REDISCLI_AUTH="$REDIS_PASSWORD" echo "Looping through the redis list to see if Redis Master node is available now" while [ 1 ] do for i in ${nodes//,/ } do - MASTER=$(redis-cli $TLSPARAMETERS --no-auth-warning --raw -h $i --user admin -a $REDIS_PASSWORD info replication | awk '{print $1}' | grep master_host: | cut -d ":" -f2) + MASTER=$(redis-cli $TLSPARAMETERS --raw -h $i --user admin info replication | awk '{print $1}' | grep master_host: | cut -d ":" -f2) if [ "$MASTER" == "" ]; then echo "no master info found in $i" MASTER= From b9f6a234002b84a7bcf2c2192a5671a73489c540 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Tue, 28 Apr 2026 00:03:41 -0700 Subject: [PATCH 21/21] documentation cleanup --- docker/openemr/OVERVIEW.md | 6 +++-- docker/openemr/obsolete/7.0.1/openemr.sh | 26 ++++++++++++++++++++++ docker/openemr/obsolete/7.0.2/openemr.sh | 28 ++++++++++++++++++++++++ kubernetes/README.md | 2 +- 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/docker/openemr/OVERVIEW.md b/docker/openemr/OVERVIEW.md index 4c9d02ff..6d7df126 100644 --- a/docker/openemr/OVERVIEW.md +++ b/docker/openemr/OVERVIEW.md @@ -30,8 +30,10 @@ This [OpenEMR](http://www.open-emr.org) official docker supports automated insta * Port 443 is https/ssl and uses a self-signed certificate by default. If assign the `DOMAIN` and `EMAIL`(optional) environment settings, then it will set up and maintain certificates via letsencrypt. * OpenEMR global settings can be set via the `OPENEMR_SETTING_*` settings. For example, can turn on the REST API in OpenEMR by setting `OPENEMR_SETTING_rest_api` to `1`. There are hundreds of potential settings, which are listed [here](https://www.open-emr.org/wiki/index.php/Administration_Globals). * Can override auto installation and force manual installation by setting `MANUAL_SETUP` environment setting to 'yes'. -* Can set up connection to a redis server for PHP sessions with the `REDIS_SERVER` environment setting (also supports optional settings of `REDIS_USERNAME` and `REDIS_PASSWORD`). This only works for the `5.0.2+` and `flex*` images at this time. -* Can turn on kubernetes/orchestration/swarm/replication support with the `SWARM_MODE` (set it to 'yes'). This only works for the `5.0.2+` and `flex*` images on Docker Swarm and can see [docker-compose.yml](https://gist.github.com/bradymiller/980086836af187285bf28b8db9eecabc) for an example of use in Docker Swarm. This only works for the `6.0.0+` and `flex*` images on Kubernetes and can see [README.md](https://github.com/openemr/openemr-devops/tree/master/kubernetes/minikube/README.md) for examples of use in Kubernetes. Note the shared volumes /var/www/localhost/htdocs/openemr/sites, /etc/ssl and /etc/letsencrypt are needed in this mode. +* Redis session storage is supported in two modes: + * **Redis Native Mode** — Direct connection to a single Redis instance. Sessions are configured via php.ini. Set `REDIS_SERVER` to the Redis host (or host:port). Optional settings: `REDIS_USERNAME`, `REDIS_PASSWORD`. This mode is supported in `5.0.2+` and `flex*` images. + * **Predis Sentinel Mode** — Connects via Redis Sentinel for automatic failover with distributed session locking. Sessions are configured at the PHP application layer (not php.ini). Set `SESSION_STORAGE_MODE` to `predis-sentinel` along with: `REDIS_SENTINELS` (sentinel hosts delimited by `|||`), `REDIS_MASTER` (sentinel master name), `REDIS_MASTER_PASSWORD` (Redis password). Optional: `REDIS_SENTINELS_PASSWORD` (sentinel password). For TLS, set `REDIS_TLS` to `yes` and `REDIS_TLS_CERT_KEY_PATH` to the directory containing cert files (`redis-master-ca`, `redis-master-cert`, `redis-master-key`, `redis-sentinel-ca`, `redis-sentinel-cert`, `redis-sentinel-key`). For mTLS, also set `REDIS_X509` to `yes`. Optional tuning: `REDIS_SESSION_LOCK_TTL` (default 60s), `REDIS_SESSION_LOCK_MAX_WAIT` (default 60s). This mode is supported in `8.1.0+` and `flex*` images and is the recommended mode for Kubernetes deployments (see [Kubernetes README](https://github.com/openemr/openemr-devops/tree/master/kubernetes)). +* Can turn on kubernetes/orchestration/swarm/replication support with the `SWARM_MODE` (set it to 'yes'). This only works for the `5.0.2+` and `flex*` images on Docker Swarm and can see [docker-compose.yml](https://gist.github.com/bradymiller/980086836af187285bf28b8db9eecabc) for an example of use in Docker Swarm. This only works for the `6.0.0+` and `flex*` images on Kubernetes and can see [README.md](https://github.com/openemr/openemr-devops/tree/master/kubernetes) for examples of use in Kubernetes. Note the shared volumes /var/www/localhost/htdocs/openemr/sites, /etc/ssl and /etc/letsencrypt are needed in this mode. * Can force database SSL or X509 connection by setting one of the following parameters to "1", `FORCE_DATABASE_SSL_CONNECT` or `FORCE_DATABASE_X509_CONNECT`. (ensure you have the SSL or X509 properly configured in the database). This is supported in `7.0.1`+ and flex (`3.15-8`, `edge`, `3.17`+) series. * The `flex*` images (`flex-3.22` is Alpine 3.22, `flex-edge` is Alpine Edge, etc.) are for testers and developers and allows use of a OpenEMR version from the specified git repository. Required environment settings for these images are `FLEX_REPOSITORY` and (`FLEX_REPOSITORY_BRANCH` or `FLEX_REPOSITORY_TAG`). `FLEX_REPOSITORY` is the public git repository holding the openemr version that will be used. And `FLEX_REPOSITORY_BRANCH` or `FLEX_REPOSITORY_TAG` represent the branch or tag to use in this git repository, respectively. An exception to the above required settings for these images is if the user sets `EMPTY` environment setting to 'yes'; then these images will not install any openemr in it (this gives a developer flexibility to set up shared volume(s) from host). Another special flag in the `flex*` docker series is `FORCE_NO_BUILD_MODE`, which will force the docker to not build openemr dependent packages/assets via composer/npm if it is set to 'yes'. Two other special flags in the `flex*` docker series is `EASY_DEV_MODE` and `EASY_DEV_MODE_NEW`, which allows use of the "easy development environment" if they are set to 'yes'. Another special flag in the `flex*` docker series is `INSANE_DEV_MODE`, which allows use of devtools in the "insane development environment" if it is set to 'yes'. Another special flag in the `flex*` docker series is `DEVELOPER_TOOLS`, which installs developer/testing tools/packages. Yet another special flag in the `flex*` docker series is `GITHUB_COMPOSER_TOKEN`, which is used to place a github auth token. Another set of special flags in the `flex*` docker series are `XDEBUG_ON`, which will turn on support for xdebug when set to 1 and `XDEBUG_PROFILER_ON`, which will turn on support for xdebug profiling when set to `1` (optional setting for xdebug support are `XDEBUG_IDE_KEY`, `XDEBUG_CLIENT_HOST` and `XDEBUG_CLIENT_PORT`). Another special setting in the `flex*` docker series is `DEMO_MODE`, which when set to `standard`, will load in standard demo data. Another special setting in the `flex*` docker series is `SQL_DATA_DRIVE`, which can be set to a path in the docker; all of the *.sql in this path will be loaded into the OpenEMR database. * Automatic upgrading of OpenEMR is supported when upgrade from `5.0.1` to most recent production image tag (for example `8.0.0`) (note this is not supported in the `flex*` series). In your docker-compose.yml file, you can basically change the image version tag (such as `5.0.1`) to the most recent production image version tag (for example `8.0.0`) and then do a `docker-compose up`. Note it will not work for patch version upgrades (ie. `7.0.3` to `7.0.3.4` will not work). Also noted this will only work if you have set a shared volume for the `/var/www/localhost/htdocs/openemr/sites` directory (it will not work if either you didn't set a shared volume or you set the shared volume to be the entire `/var/www/localhost/htdocs/openemr` directory). Before you do this, recommend backing everything up. diff --git a/docker/openemr/obsolete/7.0.1/openemr.sh b/docker/openemr/obsolete/7.0.1/openemr.sh index 7eccdc35..df5f9d02 100644 --- a/docker/openemr/obsolete/7.0.1/openemr.sh +++ b/docker/openemr/obsolete/7.0.1/openemr.sh @@ -242,6 +242,32 @@ if [ "$REDIS_SERVER" != "" ] && # and in swarm mode the docker will be functional after this redis section (ie. if do the below config section first # then the breakage time of the pod will be markedly less). + # Support phpredis build + # This will allow building phpredis towards either most recent development version "develop", + # or a specific sha1 commit id, such as "e571a81f8d3009aab38cbb88dde865edeb0607ac". + # This allows support for tls (ie. encrypted connections) since not available in production + # version 5.3.7 . + if [ "$PHPREDIS_BUILD" != "" ]; then + apk update + apk del --no-cache php81-redis + apk add --no-cache git php81-dev php81-pecl-igbinary gcc make g++ + mkdir /tmpredis + cd /tmpredis + git clone https://github.com/phpredis/phpredis.git + cd /tmpredis/phpredis + if [ "$PHPREDIS_BUILD" != "develop" ]; then + git reset --hard "$PHPREDIS_BUILD" + fi + phpize + ./configure --enable-redis-igbinary + make + make install + echo "extension=redis" > /etc/php81/conf.d/20_redis.ini + rm -fr /tmpredis/phpredis + apk del --no-cache git php81-dev gcc make g++ + cd /var/www/localhost/htdocs/openemr + fi + # Support the following redis auth: # No username and No password set (using redis default user with nopass set) # Both username and password set (using the redis user and pertinent password) diff --git a/docker/openemr/obsolete/7.0.2/openemr.sh b/docker/openemr/obsolete/7.0.2/openemr.sh index 7cb96dc3..ffaf6371 100644 --- a/docker/openemr/obsolete/7.0.2/openemr.sh +++ b/docker/openemr/obsolete/7.0.2/openemr.sh @@ -262,6 +262,34 @@ if [ "$REDIS_SERVER" != "" ] && # and in swarm mode the docker will be functional after this redis section (ie. if do the below config section first # then the breakage time of the pod will be markedly less). + # Support phpredis build + # This will allow building phpredis towards either most recent development version "develop", + # or a specific sha1 commit id, such as "e571a81f8d3009aab38cbb88dde865edeb0607ac". + # This allows support for tls (ie. encrypted connections) since not available in production + # version 5.3.7 . + if [ "$PHPREDIS_BUILD" != "" ]; then + apk update + apk del --no-cache php82-redis + apk add --no-cache git php82-dev php82-pecl-igbinary gcc make g++ + mkdir /tmpredis + cd /tmpredis + git clone https://github.com/phpredis/phpredis.git + cd /tmpredis/phpredis + if [ "$PHPREDIS_BUILD" != "develop" ]; then + git reset --hard "$PHPREDIS_BUILD" + fi + # note for php 8.2, needed to change from 'phpize' to: + phpize82 + # note for php 8.2, needed to change from './configure --enable-redis-igbinary' to: + ./configure --with-php-config=/usr/bin/php-config82 --enable-redis-igbinary + make -j $(nproc --all) + make install + echo "extension=redis" > /etc/php82/conf.d/20_redis.ini + rm -fr /tmpredis/phpredis + apk del --no-cache git php82-dev gcc make g++ + cd /var/www/localhost/htdocs/openemr + fi + # Support the following redis auth: # No username and No password set (using redis default user with nopass set) # Both username and password set (using the redis user and pertinent password) diff --git a/kubernetes/README.md b/kubernetes/README.md index e3e250c7..439b54b9 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -1,5 +1,5 @@ # Overview -This solution requires OpenEMR Docker 8.1.0 or higher. While not a fully hardened production deployment, this provides a solid working foundation with mTLS encryption, Redis Sentinel failover, and multi-node support, and should open the door to a myriad of other Kubernetes-based solutions. +This solution requires OpenEMR Docker 8.1.0 or higher. The flex Docker series is also supported for development purposes (change to `openemr/openemr:flex` in `openemr/deployment.yaml`, though startup will be significantly slower as each instance builds from source). While not a fully hardened production deployment, this provides a solid working foundation with mTLS encryption, Redis Sentinel failover, and multi-node support, and should open the door to a myriad of other Kubernetes-based solutions. OpenEMR Kubernetes orchestration. Orchestration included OpenEMR, MariaDB, Redis, and phpMyAdmin. - OpenEMR - 3 deployment replications of OpenEMR are created. Replications can be increased/decreased. Ports for both http and https.