Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 0 additions & 26 deletions docker/openemr/obsolete/7.0.1/openemr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 0 additions & 28 deletions docker/openemr/obsolete/7.0.2/openemr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
116 changes: 69 additions & 47 deletions kubernetes/README.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions kubernetes/certs/redis-openemr-client.yaml
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions kubernetes/certs/redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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
Comment on lines +7 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6. 🟡 Overly long-lived Redis/Sentinel mTLS certificates and overly-broad EKU usages

Property Value
Severity Medium
CWE CWE-16

Description

The newly added cert-manager Certificate resources for Redis and Sentinel issue end-entity certificates with a 10-year validity and include both server and client authentication usages.

Implications:

  • duration: 87660h # 10y greatly increases the blast radius of key compromise and reduces the effectiveness of rotation/revocation in typical Kubernetes environments.
  • usages includes both server auth and client auth, which allows the same certificate/keypair to potentially be accepted as a client certificate where mTLS is enforced, undermining identity separation between servers and clients.

Vulnerable configuration:

spec:
  duration: 87660h # 10y
  usages:
    - server auth
    - client auth

Recommendation

Use shorter-lived leaf certificates and restrict EKUs to intended roles.

  • Reduce leaf certificate lifetime (commonly 30-90 days for workload certs; at most 1 year in many orgs).
  • For Redis and Sentinel server certificates, remove client auth.
  • For any client certificates, use a separate Certificate resource with only client auth.

Example (Redis server cert):

spec:
  duration: 2160h # 90d
  renewBefore: 360h # 15d
  usages:
    - digital signature
    - key encipherment
    - server auth

Example (Redis client cert):

spec:
  duration: 2160h # 90d
  renewBefore: 360h
  usages:
    - digital signature
    - key encipherment
    - client auth

Also consider switching to encoding: PKCS8 unless a consuming component explicitly requires PKCS1.

Last updated on: 2026-04-26T21:46:48Z

subject:
organizations:
- redis
commonName: redis
dnsNames:
- 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
group: cert-manager.io
33 changes: 33 additions & 0 deletions kubernetes/certs/sentinel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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
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
group: cert-manager.io
10 changes: 10 additions & 0 deletions kubernetes/kind-config-1-node.yaml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +5 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4. 🟠 Kind extraPortMappings expose OpenEMR NodePort on all host interfaces

Property Value
Severity High
CWE CWE-284

Description

OpenEMR is exposed via a fixed NodePort and Kind extraPortMappings without a listenAddress, which causes the mapped ports to bind on 0.0.0.0 by default in Kind.

  • kubernetes/openemr/service.yaml sets OpenEMR to type: NodePort with fixed nodePorts 30080/30443.
  • kubernetes/kind-config-1-node.yaml (and similarly the 4-node config) maps those nodePorts to host ports 8800/9800.
  • Because listenAddress is not set, these host ports may be reachable from other machines on the network (not just localhost), contradicting the README guidance and potentially exposing a sensitive medical application unintentionally.
  • kubernetes/network/policies.yaml explicitly allows ingress to OpenEMR pods from anywhere on ports 80/443, so once traffic reaches the cluster, it is permitted.

Vulnerable configuration:

extraPortMappings:
- containerPort: 30080
  hostPort: 8800
- containerPort: 30443
  hostPort: 9800

Recommendation

Bind the Kind port mappings to localhost (or remove host port mappings entirely) to avoid unintended exposure.

Option A (recommended for local dev): set listenAddress: "127.0.0.1" for each mapping:

extraPortMappings:
- containerPort: 30080
  hostPort: 8800
  listenAddress: "127.0.0.1"
- containerPort: 30443
  hostPort: 9800
  listenAddress: "127.0.0.1"

Option B: avoid NodePort exposure:

  • Keep OpenEMR Service as ClusterIP and access via kubectl port-forward, or
  • Use an Ingress controller with proper TLS and IP allowlisting.

Also consider tightening allow-openemr-ingress to only allow from the ingress controller / specific CIDRs rather than from all sources.

Last updated on: 2026-04-26T21:46:46Z

26 changes: 7 additions & 19 deletions kubernetes/kind-config-4-nodes.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
# 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
extraPortMappings:
- containerPort: 30080
hostPort: 8800
- containerPort: 30443
hostPort: 9800
- role: worker
- role: worker
- role: worker
extraMounts:
- hostPath: /tmp/hostpath-provisioner
containerPath: /tmp/hostpath-provisioner
113 changes: 0 additions & 113 deletions kubernetes/kind-pvc-hostpath.yaml

This file was deleted.

21 changes: 15 additions & 6 deletions kubernetes/kub-down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ 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

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 \
-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/configmap-pipy.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 \
Expand All @@ -34,4 +36,11 @@ 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 \
-f nfs/storageclass.yaml \
-f nfs/service.yaml \
-f nfs/deployment.yaml
Loading