diff --git a/ansible/roles/nova-cell/defaults/main.yml b/ansible/roles/nova-cell/defaults/main.yml
index f08302bd18..bcb50f2063 100644
--- a/ansible/roles/nova-cell/defaults/main.yml
+++ b/ansible/roles/nova-cell/defaults/main.yml
@@ -529,6 +529,14 @@ migration_hostname: "{{ ansible_facts.nodename }}"
 # It does not change that often (in fact, most likely never ever).
 qemu_user_gid: 42427
 
+# Whether to enable libvirt SASL authentication.
+libvirt_enable_sasl: true
+# Username for libvirt SASL.
+libvirt_sasl_authname: "nova"
+# List of enabled libvirt SASL authentication mechanisms.
+libvirt_sasl_mech_list:
+  - "{{ 'SCRAM-SHA-256' if libvirt_tls | bool else 'DIGEST-MD5' }}"
+
 ####################
 # Kolla
 ####################
diff --git a/ansible/roles/nova-cell/handlers/main.yml b/ansible/roles/nova-cell/handlers/main.yml
index 79aae420f0..6ff3e3b497 100644
--- a/ansible/roles/nova-cell/handlers/main.yml
+++ b/ansible/roles/nova-cell/handlers/main.yml
@@ -93,6 +93,7 @@
   vars:
     service_name: "nova-libvirt"
     service: "{{ nova_cell_services[service_name] }}"
+    nova_libvirt_notify: "{{ ['Create libvirt SASL user'] if libvirt_enable_sasl | bool else [] }}"
   become: true
   kolla_docker:
     action: "recreate_or_restart_container"
@@ -112,6 +113,20 @@
   until: restart_nova_libvirt is success
   when:
     - kolla_action != "config"
+  notify: "{{ nova_libvirt_notify }}"
+
+# The SASL user needs to exist in order for nova-compute to start successfully.
+- name: Create libvirt SASL user
+  become: true
+  shell:
+    cmd: >
+      set -o pipefail &&
+      echo {{ libvirt_sasl_password }} |
+      docker exec -i nova_libvirt
+      saslpasswd2 -c -p -a libvirt {{ libvirt_sasl_authname }}
+    executable: /bin/bash
+  changed_when: true
+  no_log: true
 
 - name: Restart nova-compute container
   vars:
diff --git a/ansible/roles/nova-cell/tasks/config.yml b/ansible/roles/nova-cell/tasks/config.yml
index 8e4a2c29b7..59dff485a5 100644
--- a/ansible/roles/nova-cell/tasks/config.yml
+++ b/ansible/roles/nova-cell/tasks/config.yml
@@ -97,6 +97,26 @@
     - libvirt_tls | bool
     - libvirt_tls_manage_certs | bool
 
+- name: Copying over libvirt SASL configuration
+  become: true
+  vars:
+    service_name: "{{ item.service }}"
+    service: "{{ nova_cell_services[service_name] }}"
+  template:
+    src: "{{ item.src }}"
+    dest: "{{ node_config_directory }}/{{ service_name }}/{{ item.dest }}"
+    mode: "0660"
+  when:
+    - libvirt_enable_sasl | bool
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+  with_items:
+    - { src: "auth.conf.j2", dest: "auth.conf", service: "nova-compute" }
+    - { src: "auth.conf.j2", dest: "auth.conf", service: "nova-libvirt" }
+    - { src: "sasl.conf.j2", dest: "sasl.conf", service: "nova-libvirt" }
+  notify:
+    - Restart {{ service_name }} container
+
 - name: Copying files for nova-ssh
   become: true
   vars:
diff --git a/ansible/roles/nova-cell/templates/auth.conf.j2 b/ansible/roles/nova-cell/templates/auth.conf.j2
new file mode 100644
index 0000000000..75576757ff
--- /dev/null
+++ b/ansible/roles/nova-cell/templates/auth.conf.j2
@@ -0,0 +1,6 @@
+[credentials-default]
+authname={{ libvirt_sasl_authname }}
+password={{ libvirt_sasl_password }}
+
+[auth-libvirt-default]
+credentials=default
diff --git a/ansible/roles/nova-cell/templates/libvirtd.conf.j2 b/ansible/roles/nova-cell/templates/libvirtd.conf.j2
index 6023456269..aa01f743d8 100644
--- a/ansible/roles/nova-cell/templates/libvirtd.conf.j2
+++ b/ansible/roles/nova-cell/templates/libvirtd.conf.j2
@@ -5,10 +5,11 @@ tls_port = "{{ nova_libvirt_port }}"
 key_file = "/etc/pki/libvirt/private/serverkey.pem"
 cert_file = "/etc/pki/libvirt/servercert.pem"
 ca_file = "/etc/pki/CA/cacert.pem"
+auth_tls = "{{ 'sasl' if libvirt_enable_sasl | bool else 'none' }}"
 {% else %}
 listen_tcp = 1
 listen_tls = 0
-auth_tcp = "none"
+auth_tcp = "{{ 'sasl' if libvirt_enable_sasl | bool else 'none' }}"
 tcp_port = "{{ nova_libvirt_port }}"
 ca_file = ""
 {% endif %}
diff --git a/ansible/roles/nova-cell/templates/nova-compute.json.j2 b/ansible/roles/nova-cell/templates/nova-compute.json.j2
index 2a762ae2b2..93cf50dfeb 100644
--- a/ansible/roles/nova-cell/templates/nova-compute.json.j2
+++ b/ansible/roles/nova-cell/templates/nova-compute.json.j2
@@ -55,7 +55,13 @@
             "owner": "nova",
             "perm": "0600",
             "optional": true
-        }
+        }{% if nova_compute_virt_type in ['kvm', 'qemu'] and libvirt_enable_sasl | bool %},
+        {
+            "source": "{{ container_config_directory }}/auth.conf",
+            "dest": "/var/lib/nova/.config/libvirt/auth.conf",
+            "owner": "nova",
+            "perm": "0600"
+        }{% endif %}
     ],
     "permissions": [
         {
diff --git a/ansible/roles/nova-cell/templates/nova-libvirt.json.j2 b/ansible/roles/nova-cell/templates/nova-libvirt.json.j2
index f4160c6af4..d2ddc9e6f9 100644
--- a/ansible/roles/nova-cell/templates/nova-libvirt.json.j2
+++ b/ansible/roles/nova-cell/templates/nova-libvirt.json.j2
@@ -55,6 +55,18 @@
             "dest": "/etc/ceph/ceph.conf",
             "owner": "nova",
             "perm": "0600"
+        }{% endif %}{% if libvirt_enable_sasl | bool %},
+        {
+            "source": "{{ container_config_directory }}/sasl.conf",
+            "dest": "/etc/sasl2/libvirt.conf",
+            "owner": "root",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/auth.conf",
+            "dest": "/root/.config/libvirt/auth.conf",
+            "owner": "root",
+            "perm": "0600"
         }{% endif %}
     ]
 }
diff --git a/ansible/roles/nova-cell/templates/sasl.conf.j2 b/ansible/roles/nova-cell/templates/sasl.conf.j2
new file mode 100644
index 0000000000..a2c1271c47
--- /dev/null
+++ b/ansible/roles/nova-cell/templates/sasl.conf.j2
@@ -0,0 +1,2 @@
+mech_list: {{ libvirt_sasl_mech_list | join(' ') }}
+sasldb_path: /etc/libvirt/passwd.db
diff --git a/doc/source/reference/compute/libvirt-guide.rst b/doc/source/reference/compute/libvirt-guide.rst
index 3aae36690b..6776c1f98d 100644
--- a/doc/source/reference/compute/libvirt-guide.rst
+++ b/doc/source/reference/compute/libvirt-guide.rst
@@ -1,5 +1,3 @@
-.. libvirt-tls-guide:
-
 ====================================
 Libvirt - Nova Virtualisation Driver
 ====================================
@@ -23,16 +21,39 @@ hardware virtualisation (e.g. Virtualisation Technology (VT) BIOS configuration
 on Intel systems), ``qemu`` may be used to provide less performant
 software-emulated virtualisation.
 
+SASL Authentication
+===================
+
+The default configuration of Kolla Ansible is to run libvirt over TCP,
+authenticated with SASL. This should not be considered as providing a secure,
+encrypted channel, since the username/password SASL mechanisms available for
+TCP are no longer considered cryptographically secure. However, it does at
+least provide some authentication for the libvirt API. For a more secure
+encrypted channel, use :ref`libvirt TLS <libvirt-tls>`.
+
+SASL is enabled according to the ``libvirt_enable_sasl`` flag, which defaults
+to ``true``.
+
+The username is configured via ``libvirt_sasl_authname``, and defaults to
+``kolla``. The password is configured via ``libvirt_sasl_password``, and is
+generated with other passwords using and stored in ``passwords.yml``.
+
+The list of enabled authentication mechanisms is configured via
+``libvirt_sasl_mech_list``, and defaults to ``["SCRAM-SHA-256"]`` if libvirt
+TLS is enabled, or ``["DIGEST-MD5"]`` otherwise.
+
+.. libvirt-tls:
+
 Libvirt TLS
 ===========
 
 The default configuration of Kolla Ansible is to run libvirt over TCP, with
-authentication disabled. As long as one takes steps to protect who can access
-the port this works well. However, in the case where you want live-migration to
-be allowed across hypervisors one may want to either add some level of
-authentication to the connections or make sure VM data is passed between
-hypervisors in a secure manner. To do this we can enable TLS for libvirt and
-make nova use it.
+SASL authentication. As long as one takes steps to protect who can access
+the network this works well. However, in a less trusted environment one may
+want to use encryption when accessing the libvirt API. To do this we can enable
+TLS for libvirt and make nova use it. Mutual TLS is configured, providing
+authentication of clients via certificates. SASL authentication provides a
+further level of security.
 
 Using libvirt TLS
 ~~~~~~~~~~~~~~~~~
diff --git a/etc/kolla/passwords.yml b/etc/kolla/passwords.yml
index b343496b9e..33667cd8d0 100644
--- a/etc/kolla/passwords.yml
+++ b/etc/kolla/passwords.yml
@@ -253,3 +253,8 @@ keystone_federation_openid_crypto_password:
 # Ceph RadosGW options
 ####################
 ceph_rgw_keystone_password:
+
+##################
+# libvirt options
+##################
+libvirt_sasl_password:
diff --git a/releasenotes/notes/libvirt-sasl-404199143610fb75.yaml b/releasenotes/notes/libvirt-sasl-404199143610fb75.yaml
new file mode 100644
index 0000000000..31461b647a
--- /dev/null
+++ b/releasenotes/notes/libvirt-sasl-404199143610fb75.yaml
@@ -0,0 +1,27 @@
+---
+features:
+  - |
+    Adds support for libvirt SASL authentication. It is enabled by default.
+    `LP#1964013 <https://bugs.launchpad.net/kolla-ansible/+bug/1964013>`__
+security:
+  - |
+    Fixes an issue where the default configuration of libvirt did not use
+    authentication for the API exposed over TCP on the internal API network.
+    This allowed anyone with access to the internal API network read-write
+    access to libvirt. While the internal API network is typically trusted,
+    other services on this network generally at least require authentication.
+
+    SASL authentication is now enabled for libvirt by default. Kolla Ansible
+    supports libvirt TLS since the Train release, and this is recommended to
+    provide a higher level of security. `LP#1964013
+    <https://bugs.launchpad.net/kolla-ansible/+bug/1964013>`__
+upgrade:
+  - |
+    The addition of libvirt SASL authentication requires a new password in
+    ``passwords.yml``, ``libvirt_sasl_password``. This may be generated using
+    the existing ``kolla-genpwd`` and ``kolla-mergepwd`` tooling.
+  - |
+    The addition of libvirt SASL authentication requires both the
+    ``nova_libvirt`` and ``nova_compute`` containers to be updated
+    simultaneously, using new images with the necessary Cyrus SASL
+    dependencies, as well as configuration containing the SASL credentials.