Files
ubicloud/rhizome/host/spec/cert_server_setup_spec.rb
Benjamin Satzger af0d3ed67b Fix race with cert server leaving vm stuck in prep
When creating a VM attached to a load balancer, cert server puts a
certificate into the folder /vm/inhost_name/cert by executing
`sudo host/bin/setup-cert-server put-certificate inhost_name`

During that call, before writing the certs, it first creates the cert
folder using `mkdir_p` (as the root user). If  /vm/inhost_name
didn't exist yet at that point, the folder /vm/inhost_name was
created like this:

```
ll inhost_name
total 12
drwxr-xr-x 3 root root 4096 May  9 12:52 ./
drwxr-xr-x 7 root root 4096 May  9 12:52 ../
drwxr-xr-x 2 root root 4096 May  9 12:52 cert/
```

This is what subsequently happens to VM provisioning in that case:

* create_unix_user: succeeds but gives this warning
```
adduser: Warning: The home directory `/vm/inhost_name'
does not belong to the user you are currently creating.
```

* prep: Fails with `tee: /vm/inhost_name/prep.json: Permission denied`

As a fix, we use `mkdir` instead of `mkdir_p` and just ignore the
error indicating that the folder already exists. That way, it won't
create the /vm/inhost_name folder. Putting the certificate should
succeed after `create_unix_user` has created /vm/inhost_name.
2025-05-12 09:01:21 +02:00

145 lines
6.5 KiB
Ruby

# frozen_string_literal: true
require_relative "../lib/cert_server_setup"
require_relative "../../common/lib/util"
RSpec.describe CertServerSetup do
subject(:cert_server_setup) { described_class.new(vm_name) }
let(:vm_name) { "test-vm" }
describe "#setup" do
it "copies the server, creates the service, enables and starts the service" do
expect(cert_server_setup).to receive(:copy_server)
expect(cert_server_setup).to receive(:create_service)
expect(cert_server_setup).to receive(:enable_and_start_service)
expect { cert_server_setup.setup }.not_to raise_error
end
end
describe "#stop_and_remove" do
it "stops and removes the service, removes the paths" do
expect(cert_server_setup).to receive(:stop_and_remove_service)
expect(cert_server_setup).to receive(:remove_paths)
expect { cert_server_setup.stop_and_remove }.not_to raise_error
end
end
describe "#copy_server" do
it "downloads the server if it doesn't exist, copies the server, and sets the owner" do
expect(File).to receive(:exist?).with("/opt/metadata-endpoint-0.1.5").and_return(false)
expect(cert_server_setup).to receive(:download_server)
expect(cert_server_setup).to receive(:r).with("cp /opt/metadata-endpoint-0.1.5/metadata-endpoint /vm/test-vm/cert/metadata-endpoint-0.1.5")
expect(cert_server_setup).to receive(:r).with("sudo chown test-vm:test-vm /vm/test-vm/cert/metadata-endpoint-0.1.5")
expect { cert_server_setup.copy_server }.not_to raise_error
end
it "doesn't download the server if it already exists" do
expect(File).to receive(:exist?).with("/opt/metadata-endpoint-0.1.5").and_return(true)
expect(cert_server_setup).not_to receive(:download_server)
expect(cert_server_setup).to receive(:r).with("cp /opt/metadata-endpoint-0.1.5/metadata-endpoint /vm/test-vm/cert/metadata-endpoint-0.1.5")
expect(cert_server_setup).to receive(:r).with("sudo chown test-vm:test-vm /vm/test-vm/cert/metadata-endpoint-0.1.5")
expect { cert_server_setup.copy_server }.not_to raise_error
end
end
describe "#download_server" do
it "downloads the server, extracts it, and removes the tarball" do
expect(Arch).to receive(:render).with(x64: "x86_64", arm64: "arm64").and_return("arm64")
expect(cert_server_setup).to receive(:r).with("curl -L3 -o /tmp/metadata-endpoint-0.1.5.tar.gz https://github.com/ubicloud/metadata-endpoint/releases/download/v0.1.5/metadata-endpoint_Linux_arm64.tar.gz")
expect(FileUtils).to receive(:mkdir_p).with("/opt/metadata-endpoint-0.1.5")
expect(FileUtils).to receive(:cd).with("/opt/metadata-endpoint-0.1.5")
expect(FileUtils).to receive(:rm_f).with("/tmp/metadata-endpoint-0.1.5.tar.gz")
expect { cert_server_setup.download_server }.not_to raise_error
end
it "downloads the server for x64" do
expect(Arch).to receive(:render).with(x64: "x86_64", arm64: "arm64").and_return("x86_64")
expect(cert_server_setup).to receive(:r).with("curl -L3 -o /tmp/metadata-endpoint-0.1.5.tar.gz https://github.com/ubicloud/metadata-endpoint/releases/download/v0.1.5/metadata-endpoint_Linux_x86_64.tar.gz")
expect(FileUtils).to receive(:mkdir_p).with("/opt/metadata-endpoint-0.1.5")
expect(FileUtils).to receive(:cd).with("/opt/metadata-endpoint-0.1.5")
expect(FileUtils).to receive(:rm_f).with("/tmp/metadata-endpoint-0.1.5.tar.gz")
expect { cert_server_setup.download_server }.not_to raise_error
end
end
describe "#create_service" do
it "creates the service file" do
expect(File).to receive(:write).with("/etc/systemd/system/test-vm-metadata-endpoint.service", <<~SERVICE)
[Unit]
Description=Certificate Server
After=network.target
[Service]
NetworkNamespacePath=/var/run/netns/test-vm
ExecStart=/vm/test-vm/cert/metadata-endpoint-0.1.5
Restart=always
RestartSec=15
Type=simple
ProtectSystem=strict
PrivateDevices=yes
PrivateTmp=yes
ProtectHome=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectControlGroups=yes
NoNewPrivileges=yes
ReadOnlyPaths=/vm/test-vm/cert/cert.pem /vm/test-vm/cert/key.pem
User=test-vm
Group=test-vm
Environment=VM_INHOST_NAME=test-vm
Environment=IPV6_ADDRESS="FD00:0B1C:100D:5AFE:CE::"
Environment=GOMEMLIMIT=9MiB
Environment=GOMAXPROCS=1
CPUQuota=50%
MemoryLimit=10M
SERVICE
expect(cert_server_setup).to receive(:r).with("systemctl daemon-reload")
expect { cert_server_setup.create_service }.not_to raise_error
end
end
describe "#enable_and_start_service" do
it "enables and starts the service" do
expect(cert_server_setup).to receive(:r).with("systemctl enable --now test-vm-metadata-endpoint")
cert_server_setup.enable_and_start_service
# expect { cert_server_setup.enable_and_start_service }.not_to raise_error
end
end
describe "#stop_and_remove_service" do
it "stops and removes the service" do
expect(File).to receive(:exist?).with("/etc/systemd/system/test-vm-metadata-endpoint.service").and_return(true)
expect(cert_server_setup).to receive(:r).with("systemctl disable --now test-vm-metadata-endpoint")
expect(cert_server_setup).to receive(:r).with("systemctl daemon-reload")
expect(FileUtils).to receive(:rm_f).with("/etc/systemd/system/test-vm-metadata-endpoint.service")
expect { cert_server_setup.stop_and_remove_service }.not_to raise_error
end
it "doesn't stop and remove the service if it doesn't exist" do
expect(File).to receive(:exist?).with("/etc/systemd/system/test-vm-metadata-endpoint.service").and_return(false)
expect(cert_server_setup).not_to receive(:r).with("systemctl disable --now test-vm-metadata-endpoint")
expect(cert_server_setup).to receive(:r).with("systemctl daemon-reload")
expect(FileUtils).to receive(:rm_f).with("/etc/systemd/system/test-vm-metadata-endpoint.service")
expect { cert_server_setup.stop_and_remove_service }.not_to raise_error
end
end
describe "#put_certificate" do
it "puts the certificate to the server" do
expect(FileUtils).to receive(:mkdir).with("/vm/test-vm/cert")
expect(cert_server_setup).to receive(:safe_write_to_file).with("/vm/test-vm/cert/cert.pem", "cert")
expect(cert_server_setup).to receive(:safe_write_to_file).with("/vm/test-vm/cert/key.pem", "key")
expect { cert_server_setup.put_certificate("cert", "key") }.not_to raise_error
end
end
describe "#remove_paths" do
it "removes the paths" do
expect(FileUtils).to receive(:rm_rf).with("/vm/test-vm/cert")
expect { cert_server_setup.remove_paths }.not_to raise_error
end
end
end