Recently I have been working on customizing my OpenStack lab deployment to allow for some testing use cases.

Some of these use cases require a TPM be available in the test system. I wanted to utilize virtual machines in order to iterate faster and have more flexibility.

In OpenStack Victoria, they have added the capability to create virtual TPM devices for instances.

https://docs.openstack.org/nova/latest/admin/emulated-tpm.html

I am deploying my lab with TripleO and it appeared that the developers had implemented the ability to enable this vTPM feature. You'll see that I still had some work to do to get vTPM working in my lab.

Modify Deployment Templates

Modifying the tripleo-heat-templates to enable vTPM in Nova is simple. You need to add one parameter and rerun your deployment. I have a catch-all environment file named node-info.yaml where I place all my parameters that aren't in a matching file that comes from /usr/share/openstack-tripleo-heat-templates on the undercloud.

parameter_defaults:
  ...
  NovaEnableVTPM: true

It would be awesome if adding the parameter above made the deployment work with all the pieces needed for vTPM functionality. However, that was not my experience. I believe the end goal is for that to be the case, but not everything is done yet (unless I missed something).

TripleO Nova Containers

There are two containers that matter for vTPM to work (openstack-nova-compute and openstack-nova-libvirt).

The Victoria version of openstack-nova-libvirt has swtpm and trousers installed. However, there is still one piece missing, swtpm-tools. swtpm-tools provides the swtpm_setup binary.

The Victoria version of openstack-nova-compute does not have any of these three packages installed.

For the time being, I needed to modify these two containers with the correct packages to get my deployment working with vTPM.

I figured this stuff out by many days of trying things, seeing errors like swtpm not on path, no user tss, or swtpm_setup not on path errors trying to launch instances.

I accomplished modifying the container images by using the ansible-role-tripleo-modify-image Ansible role.

***NOTE: You need to build the new containers on an entitled RHEL system with buildah and podamn installed, because the tripleo container images are based on RHEL UBI.

To use this role, create a working directory.

[cloud-user@rhel8-image-builder ~]$ mkdir openstack-nova-compute
[cloud-user@rhel8-image-builder ~]$ cd openstack-nova-compute
[cloud-user@rhel8-image-builder openstack-nova-compute]$

Clone the Ansible role.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ git clone https://github.com/openstack/ansible-role-tripleo-modify-image.git
Cloning into 'ansible-role-tripleo-modify-image'...
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 622 (delta 6), reused 16 (delta 4), pack-reused 601
Receiving objects: 100% (622/622), 98.60 KiB | 5.19 MiB/s, done.
Resolving deltas: 100% (363/363), done.

Create a simple playbook to build the modified container image.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ vi nova-compute-swtpm.yml 
- hosts: localhost
  tasks:
  - name: include ansible-role-tripleo-modify-image
    import_role:
      name: ansible-role-tripleo-modify-image
      tasks_from: modify_image.yml
    vars:
      source_image: tripleomaster/openstack-nova-compute:current-tripleo
      modify_dir_path: /home/cloud-user/openstack-nova-compute
      container_build_tool: buildah

Now we need a Dockerfile which will express the modifications needed.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ vi Dockerfile 
# This will be replaced in the file Dockerfile.modified
FROM openstack-nova-compute

# switch to root to install packages
USER root

# install packages
RUN curl "https://content.kdjlab.com/public/advanced-virtualization.repo" -o "/etc/yum.repos.d/advanced-virtualization.repo"
RUN dnf install -y swtpm trousers swtpm-tools && dnf clean all && rm -rf /var/cache/dnf

#Add user and group for swtpm
USER tss

# switch the container back to the default user
USER nova

You'll probably notice that I am pulling down a repo file named advanced-virtualization.repo. This is the CentOS 8 location for swtpm and trousers. You'll want to host your own local copy of this file and change the URL in the Dockerfile to match your location.

[advanced-virtualization]
name=Advanced Virtualization mirror
baseurl=http://mirror.centos.org/centos/8/virt/$basearch/advanced-virtualization/
gpgcheck=0
enabled=1
module_hotfixes=1

Run the playbook.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ ansible-playbook nova-compute-swtpm.yml

Once this completes, you'll now have a local copy of the modified image tagged with a timestamp. You can see it by running buildah images.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ sudo buildah images
REPOSITORY                                       TAG                                       IMAGE ID       CREATED        SIZE
localhost/tripleomaster/openstack-nova-compute   current-tripleo-modified-20201028213253   84264f5e20ab   4 days ago     2.32 GB
docker.io/tripleomaster/openstack-nova-compute   current-tripleo                           99f558f0e9e9   9 days ago     2.22 GB

Lastly we need to push this out to a registry. I used Docker Hub. Create a repository to hold your image.

Authenticate to Docker Hub, then push.

[cloud-user@rhel8-image-builder openstack-nova-compute]$ sudo buildah login docker.io
... #enter username and password
[cloud-user@rhel8-image-builder openstack-nova-compute]$ sudo buildah push --debug localhost/tripleomaster/openstack-nova-compute:current-tripleo-modified-20201028213253 docker://docker.io/kjw3/openstack-nova-compute:victoria-swtpm

https://hub.docker.com/repository/docker/kjw3/openstack-nova-compute

If you examine the commands that made the latest tag of my container, you'll see:

USER root
/bin/sh -c curl "https://content.kdjlab.com/public/advanced-virtualization.repo" -o "/etc/yum.repos.d/advanced-virtualization.repo"
/bin/sh -c dnf install -y swtpm trousers swtpm-tools && dnf clean all && rm -rf /var/cache/dnf
USER tss
USER nova

This matches my Dockerfile.

You need to repeat this process for the openstack-nova-libvirt container image. Change occurrences of openstack-nova-compute for openstack-nova-libvirt in the steps above. There is one minor difference in the Dockerfile. openstack-nova-libvirt runs as root. The Dockerfile I used for it is below. Notice I only installed swtpm-tools too because swtpm and trousers was already installed in this container.

[cloud-user@rhel8-image-builder openstack-nova-libvirt]$ cat Dockerfile
# This will be replaced in the file Dockerfile.modified
FROM openstack-nova-libvirt

# switch to root to install packages
USER root

# install packages
RUN curl "https://content.kdjlab.com/public/advanced-virtualization.repo" -o "/etc/yum.repos.d/advanced-virtualization.repo"
RUN dnf install -y swtpm-tools && dnf clean all && rm -rf /var/cache/dnf

#Add user and group for swtpm
USER tss

# switch the container back to the default user
USER root

Modify Deployment Templates to Use the New Containers

We now need to add two parameters to tell the overcloud deployment what images to user for Nova Compute and Nova Libvirt. I added these to my node-info.yaml environment file.

parameter_defaults:
  ...
  ContainerNovaComputeImage: docker.io/kjw3/openstack-nova-compute:victoria-swtpm
  ContainerNovaLibvirtImage: docker.io/kjw3/openstack-nova-libvirt:victoria-swtpm

Rerun the deployment now.

(undercloud) [stack@tripleo-victoria ~]$ ./deploy.sh
...

On your compute hosts, you will see that nova_compute and nova_libvirt containers are using the new images.

[heat-admin@overcloud-compute-0 ~]$ sudo podman ps | grep nova
20e2be971930  docker.io/kjw3/openstack-nova-compute:victoria-swtpm                                                          kolla_start           15 hours ago  Up 15 hours ago         nova_compute
8e632b9a150f  docker.io/kjw3/openstack-nova-libvirt:victoria-swtpm                                                          kolla_start           15 hours ago  Up 15 hours ago         nova_libvirt
27304d15b145  docker.io/kjw3/openstack-nova-libvirt:victoria-swtpm                                                          kolla_start           15 hours ago  Up 15 hours ago         nova_virtlogd
9f64b5a402cb  docker.io/kjw3/openstack-nova-compute:victoria-swtpm                                                          kolla_start           2 days ago    Up 2 days ago           nova_migration_target

Are we there yet? I wish. I found out that even with the new containers in place, there is one final missing component. SELinux. The work is still in progress. Below is the output from setroubleshoot for the SELinux denials.

(undercloud) [stack@tripleo-victoria ~]$ cat swtpm-selinux-issues 

found 4 alerts in /var/log/audit/audit.log
--------------------------------------------------------------------------------

SELinux is preventing swtpm from write access on the directory swtpm.

*****  Plugin qemu_file_image (98.8 confidence) suggests   *******************

If swtpm is a virtualization target
Then you need to change the label on swtpm'
Do
# semanage fcontext -a -t virt_image_t 'swtpm'
# restorecon -v 'swtpm'

*****  Plugin catchall (2.13 confidence) suggests   **************************

If you believe that swtpm should be allowed write access on the swtpm directory by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'swtpm' --raw | audit2allow -M my-swtpm
# semodule -X 300 -i my-swtpm.pp


Additional Information:
Source Context                system_u:system_r:svirt_t:s0:c38,c260
Target Context                system_u:object_r:container_file_t:s0
Target Objects                swtpm [ dir ]
Source                        swtpm
Source Path                   swtpm
Port                          <Unknown>
Host                          <Unknown>
Source RPM Packages           
Target RPM Packages           
Policy RPM                    selinux-policy-3.14.3-41.el8_2.6.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Permissive
Host Name                     overcloud-controller-1
Platform                      Linux overcloud-controller-1
                              4.18.0-193.19.1.el8_2.x86_64 #1 SMP Mon Sep 14
                              14:37:00 UTC 2020 x86_64 x86_64
Alert Count                   1
First Seen                    2020-11-01 06:25:41 EST
Last Seen                     2020-11-01 06:25:41 EST
Local ID                      b088198d-ac2b-45a0-ac8a-354bcc3c51ac

Raw Audit Messages
type=AVC msg=audit(1604229941.636:2391008): avc:  denied  { write } for  pid=452433 comm="swtpm" name="swtpm" dev="tmpfs" ino=510589704 scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=dir permissive=1


type=AVC msg=audit(1604229941.636:2391008): avc:  denied  { add_name } for  pid=452433 comm="swtpm" name="1-instance-0000005a-swtpm.sock" scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=dir permissive=1


type=AVC msg=audit(1604229941.636:2391008): avc:  denied  { create } for  pid=452433 comm="swtpm" name="1-instance-0000005a-swtpm.sock" scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=sock_file permissive=1


Hash: swtpm,svirt_t,container_file_t,dir,write

--------------------------------------------------------------------------------

SELinux is preventing swtpm from setattr access on the sock_file 1-instance-0000005a-swtpm.sock.

*****  Plugin catchall (100. confidence) suggests   **************************

If you believe that swtpm should be allowed setattr access on the 1-instance-0000005a-swtpm.sock sock_file by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'swtpm' --raw | audit2allow -M my-swtpm
# semodule -X 300 -i my-swtpm.pp


Additional Information:
Source Context                system_u:system_r:svirt_t:s0:c38,c260
Target Context                system_u:object_r:container_file_t:s0
Target Objects                1-instance-0000005a-swtpm.sock [ sock_file ]
Source                        swtpm
Source Path                   swtpm
Port                          <Unknown>
Host                          <Unknown>
Source RPM Packages           
Target RPM Packages           
Policy RPM                    selinux-policy-3.14.3-41.el8_2.6.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Permissive
Host Name                     overcloud-controller-1
Platform                      Linux overcloud-controller-1
                              4.18.0-193.19.1.el8_2.x86_64 #1 SMP Mon Sep 14
                              14:37:00 UTC 2020 x86_64 x86_64
Alert Count                   1
First Seen                    2020-11-01 06:25:41 EST
Last Seen                     2020-11-01 06:25:41 EST
Local ID                      6ea3df16-9bf2-463e-bc0c-bd1f892eae4e

Raw Audit Messages
type=AVC msg=audit(1604229941.636:2391009): avc:  denied  { setattr } for  pid=452433 comm="swtpm" name="1-instance-0000005a-swtpm.sock" dev="tmpfs" ino=510590763 scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=sock_file permissive=1


Hash: swtpm,svirt_t,container_file_t,sock_file,setattr

--------------------------------------------------------------------------------

SELinux is preventing swtpm from append access on the file instance-0000005a-swtpm.log.

*****  Plugin qemu_file_image (98.8 confidence) suggests   *******************

If instance-0000005a-swtpm.log is a virtualization target
Then you need to change the label on instance-0000005a-swtpm.log'
Do
# semanage fcontext -a -t virt_image_t 'instance-0000005a-swtpm.log'
# restorecon -v 'instance-0000005a-swtpm.log'

*****  Plugin catchall (2.13 confidence) suggests   **************************

If you believe that swtpm should be allowed append access on the instance-0000005a-swtpm.log file by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'swtpm' --raw | audit2allow -M my-swtpm
# semodule -X 300 -i my-swtpm.pp


Additional Information:
Source Context                system_u:system_r:svirt_t:s0:c38,c260
Target Context                system_u:object_r:container_ro_file_t:s0
Target Objects                instance-0000005a-swtpm.log [ file ]
Source                        swtpm
Source Path                   swtpm
Port                          <Unknown>
Host                          <Unknown>
Source RPM Packages           
Target RPM Packages           
Policy RPM                    selinux-policy-3.14.3-41.el8_2.6.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Permissive
Host Name                     overcloud-controller-1
Platform                      Linux overcloud-controller-1
                              4.18.0-193.19.1.el8_2.x86_64 #1 SMP Mon Sep 14
                              14:37:00 UTC 2020 x86_64 x86_64
Alert Count                   1
First Seen                    2020-11-01 06:25:41 EST
Last Seen                     2020-11-01 06:25:41 EST
Local ID                      0bbe7b10-0e15-4b86-a019-5bd9ec884b3b

Raw Audit Messages
type=AVC msg=audit(1604229941.636:2391010): avc:  denied  { append } for  pid=452433 comm="swtpm" name="instance-0000005a-swtpm.log" dev="overlay" ino=510589703 scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_ro_file_t:s0 tclass=file permissive=1


Hash: swtpm,svirt_t,container_ro_file_t,file,append

--------------------------------------------------------------------------------

SELinux is preventing swtpm from create access on the file 1-instance-0000005a-swtpm.pid.

*****  Plugin catchall (100. confidence) suggests   **************************

If you believe that swtpm should be allowed create access on the 1-instance-0000005a-swtpm.pid file by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'swtpm' --raw | audit2allow -M my-swtpm
# semodule -X 300 -i my-swtpm.pp


Additional Information:
Source Context                system_u:system_r:svirt_t:s0:c38,c260
Target Context                system_u:object_r:container_file_t:s0
Target Objects                1-instance-0000005a-swtpm.pid [ file ]
Source                        swtpm
Source Path                   swtpm
Port                          <Unknown>
Host                          <Unknown>
Source RPM Packages           
Target RPM Packages           
Policy RPM                    selinux-policy-3.14.3-41.el8_2.6.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Permissive
Host Name                     overcloud-controller-1
Platform                      Linux overcloud-controller-1
                              4.18.0-193.19.1.el8_2.x86_64 #1 SMP Mon Sep 14
                              14:37:00 UTC 2020 x86_64 x86_64
Alert Count                   1
First Seen                    2020-11-01 06:25:41 EST
Last Seen                     2020-11-01 06:25:41 EST
Local ID                      e43e45e0-3b04-4962-8fd5-21993166df9e

Raw Audit Messages
type=AVC msg=audit(1604229941.639:2391011): avc:  denied  { create } for  pid=452434 comm="swtpm" name="1-instance-0000005a-swtpm.pid" scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=file permissive=1


type=AVC msg=audit(1604229941.639:2391011): avc:  denied  { open } for  pid=452434 comm="swtpm" path="/run/libvirt/qemu/swtpm/1-instance-0000005a-swtpm.pid" dev="tmpfs" ino=510592344 scontext=system_u:system_r:svirt_t:s0:c38,c260 tcontext=system_u:object_r:container_file_t:s0 tclass=file permissive=1


Hash: swtpm,svirt_t,container_file_t,file,create

Because of these denials (and because I wanted to see it working), I put selinux in permissive mode on my computes. Close your eyes Dan Walsh :( I submitted some bugs around these extra steps and the denials.

(undercloud) [stack@tripleo-victoria ~]$ for i in {84,78,80,83,71,81,72,75}; do ssh heat-admin@10.100.4.$i sudo setenforce 0 ; done

Modify Flavor or Image to Have vTPM Parameters

You can either set these metadata parameters on flavors or images. I chose to add some t1 class flavors that have the vTPM params

You need to update the metadata for an existing flavor or create a new one with the following metadata parameters. See below.

Launch an Instance with a vTPM

Now launch an instance using one of the flavors with the vTPM params. Once it is up and running, we can log in and validate that the vTPM is present and functional.

If you choose tpm_version 1.2:

[centos@egx-2 ~]$ ls /dev/tpm0 
/dev/tpm0

[centos@egx-2 ~]$ sudo dmesg | grep -i tpm
[    0.823761] tpm_tis 00:05: 1.2 TPM (device-id 0x1, rev-id 1)

[centos@egx-2 ~]$ sudo yum install -y tpm2-tools

[centos@egx-2 ~]$ sudo systemctl enable --now tcsd

[centos@egx-2 ~]$ sudo systemctl status tcsd
● tcsd.service - TCG Core Services Daemon
   Loaded: loaded (/usr/lib/systemd/system/tcsd.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2020-11-02 01:05:53 UTC; 1h 9min ago
  Process: 8737 ExecStart=/sbin/tcsd (code=exited, status=0/SUCCESS)
 Main PID: 8738 (tcsd)
   CGroup: /system.slice/tcsd.service
           └─8738 /sbin/tcsd

Nov 02 01:05:53 egx-2 systemd[1]: Starting TCG Core Services Daemon...
Nov 02 01:05:53 egx-2 tcsd[8737]: TCSD TDDL[8737]: TrouSerS ioctl: (25) Inappropriate ioctl for device
Nov 02 01:05:53 egx-2 tcsd[8737]: TCSD TDDL[8737]: TrouSerS Falling back to Read/Write device support.
Nov 02 01:05:53 egx-2 TCSD[8738]: TrouSerS trousers 0.3.14: TCSD up and running.
Nov 02 01:05:53 egx-2 systemd[1]: Started TCG Core Services Daemon.

[centos@egx-2 ~]$ sudo tpm_version
  TPM 1.2 Version Info:
  Chip Version:        1.2.18.158
  Spec Level:          2
  Errata Revision:     3
  TPM Vendor ID:       IBM
  TPM Version:         01010000
  Manufacturer Info:   49424d00

If you choose tpm_version 2.0:

[centos@tpm-test ~]$ ls /dev/tpm*
/dev/tpm0  /dev/tpmrm0

[centos@tpm-test ~]$ sudo dmesg | grep -i tpm
[    0.000000] ACPI: TPM2 00000000bffd1336 0004C (v04 BOCHS  BXPCTPM2 00000001 BXPC 00000001)
[    0.801741] tpm_tis MSFT0101:00: 2.0 TPM (device-id 0x1, rev-id 1)

[centos@egx-2 ~]$ sudo yum install -y tpm2-tools
...
Installed:
  tpm2-tools.x86_64 0:3.0.4-3.el7

Dependency Installed:
  tpm2-abrmd.x86_64 0:1.1.0-11.el7
  tpm2-tss.x86_64 0:1.4.0-3.el7
Complete!

[centos@tpm-test ~]$ sudo yum install -y trousers
...
Installed:
  trousers.x86_64 0:0.3.14-2.el7
Complete!

[centos@tpm-test ~]$ sudo chown tss /dev/tpm*

[centos@egx-2 ~]$ sudo systemctl enable --now tpm2-abrmd

[centos@tpm-test ~]$ sudo systemctl status tpm2-abrmd
● tpm2-abrmd.service - TPM2 Access Broker and Resource Management Daemon
   Loaded: loaded (/usr/lib/systemd/system/tpm2-abrmd.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2020-11-02 02:27:02 UTC; 17s ago
 Main PID: 9034 (tpm2-abrmd)
   CGroup: /system.slice/tpm2-abrmd.service
           └─9034 /usr/sbin/tpm2-abrmd

Nov 02 02:27:02 tpm-test systemd[1]: Starting TPM2 Access Broker and Resource Management Daemon...
Nov 02 02:27:02 tpm-test systemd[1]: Started TPM2 Access Broker and Resource Management Daemon.

Now we are able to take actions with our vTPM device inside the instance. On to further testing!