Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support virtual TPM device #91

Merged
merged 11 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,24 @@ process (by sending a signal or Control-C).
$ placemat [OPTIONS] YAML [YAML ...]

Options:
-graphic
run QEMU with graphical console
-enable-virtfs
enable VirtFS to share files between the guest and the host OS.
-shared-dir
shared directory between the guest and the host OS (default "/mnt/placemat")
-run-dir string
run directory (default "/tmp")
-cache-dir string
directory for cache data.
directory for cache data
-data-dir string
directory to store data (default "/var/scratch/placemat")
-listen-addr string
listen address of API endpoint to control placemat. (default "127.0.0.1:10808")
-debug
show QEMU's and Pod's stdout and stderr
show QEMU's and Pod's stdout and stderr
-enable-virtfs
enable VirtFS to share files between guest and host OS.
-force
force run with removal of garbage
-graphic
run QEMU with graphical console
-listen-addr string
listen address (default "127.0.0.1:10808")
-run-dir string
run directory (default "/tmp")
-shared-dir string
shared directory (default "/mnt/placemat")
```

If `-cache-dir` is not specified, the default will be `/home/${SUDO_USER}/placemat_data`
Expand All @@ -104,6 +104,7 @@ Getting started
- [picocom](https://github.com/npat-efault/picocom) for `pmctl`.
- [rkt][] for `Pod` resource.
- [socat](http://www.dest-unreach.org/socat/) for `pmctl`.
- *(Optional)* [swtpm](https://github.com/stefanberger/swtpm) for providing TPM of `Node` resource.

For Ubuntu or Debian, you can install them as follows:

Expand Down
6 changes: 6 additions & 0 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ func (c *Cluster) Start(ctx context.Context, r *Runtime) error {
env := well.NewEnvironment(ctx)
for _, n := range c.Nodes {
n := n
if n.TPM {
err := n.StartSWTPM(ctx, r)
if err != nil {
return err
}
}
env.Go(func(ctx2 context.Context) error {
// reference the original context because ctx2 will soon be cancelled.
vm, err := n.Start(ctx, r, nodeCh)
Expand Down
4 changes: 4 additions & 0 deletions docs/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ smbios:
product: mk2
serial: 1234abcd
uefi: false
tpm: true
```

The properties are:
Expand All @@ -138,6 +139,9 @@ The properties are:
- `uefi`: BIOS mode of the VM.
- If false: The VM will load Qemu's default BIOS (SeaBIO) and enable iPXE boot by a net device.
- If true: The VM loads OVMF as BIOS and disable iPXE boot by a net device.
- `tpm`: Create Trusted Platform Module(TPM) for the VM. This feature requires [swtpm](https://github.com/stefanberger/swtpm).
- If false: Provide no TPM device.
- If true: Provide a TPM device as `/dev/tpm0` on the VM.

### `image` volume

Expand Down
24 changes: 19 additions & 5 deletions mtest/cleanup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mtest

import (
"os"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
Expand All @@ -10,29 +12,41 @@ import (
func TestCleanup() {
It("should remove remaining resources and launch placemat", func() {
var session *gexec.Session
By("launch placemat", func() {
By("running placemat", func() {
session = runPlacemat(clusterYAML, "-force")
err := prepareSSHClients(node1, node2)
Expect(err).To(Succeed())
})

By("kill placemat process", func() {
By("checking that socket files exist on a host", func() {
_, err := os.Stat("/tmp/node1/swtpm.socket")
Expect(err).NotTo(HaveOccurred())
_, err = os.Stat("/tmp/node2/swtpm.socket")
Expect(err).NotTo(HaveOccurred())
})

By("checking that a device file exists on guests", func() {
execSafeAt(node1, "test", "-c", "/dev/tpm0")
execSafeAt(node2, "test", "-c", "/dev/tpm0")
})

By("killing placemat process", func() {
killPlacemat(session)
Eventually(session.Exited).Should(BeClosed())
})

By("run placemat without -force option", func() {
By("running placemat without -force option", func() {
session = runPlacemat(clusterYAML)
Eventually(session.Exited).Should(BeClosed())
})

By("run placemat with -force option", func() {
By("running placemat with -force option", func() {
session = runPlacemat(clusterYAML, "-force")
err := prepareSSHClients(node1, node2)
Expect(err).To(Succeed())
})

By("terminate placemat", func() {
By("terminating placemat", func() {
terminatePlacemat(session)
Eventually(session.Exited).Should(BeClosed())
})
Expand Down
4 changes: 3 additions & 1 deletion mtest/cluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ volumes:
name: seed
user-data: user-data_node1.yml
network-config: network1.yml
tpm: true
---
kind: Node
name: node2
Expand All @@ -40,12 +41,13 @@ volumes:
name: seed
user-data: user-data_node2.yml
network-config: network2.yml
tpm: true
---
kind: Pod
name: pod1
interfaces:
- network: ext-net
addresses:
addresses:
- @POD1@/24
apps:
- name: ubuntu
Expand Down
9 changes: 8 additions & 1 deletion mtest/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mtest
import (
"encoding/json"
"errors"
"os"
"strings"

"github.com/cybozu-go/placemat/web"
Expand Down Expand Up @@ -33,6 +34,12 @@ func TestExample() {
return nil
}).Should(Succeed())
})

By("checking that a socket file does not exist on host", func() {
_, err := os.Stat("/tmp/boot/swtpm.socket")
Expect(err).To(HaveOccurred())
})

By("saving a snapshot", func() {
_, err := pmctl("snapshot", "save", "test")
Expect(err).NotTo(HaveOccurred())
Expand All @@ -52,7 +59,7 @@ func TestExample() {
Expect(node).NotTo(Equal("There is no snapshot available."))
}
})
By("terminate placemat", func() {
By("terminating placemat", func() {
terminatePlacemat(session)
Eventually(session.Exited).Should(BeClosed())
})
Expand Down
72 changes: 70 additions & 2 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type NodeSpec struct {
CPU int `yaml:"cpu,omitempty"`
Memory string `yaml:"memory,omitempty"`
UEFI bool `yaml:"uefi,omitempty"`
TPM bool `yaml:"tpm,omitempty"`
SMBIOS SMBIOSConfig `yaml:"smbios,omitempty"`
}

Expand Down Expand Up @@ -126,17 +127,34 @@ func CleanupNodes(r *Runtime, nodes []*Node) {
r.monitorSocketPath(d.Name),
r.socketPath(d.Name),
}
dirs := []string{
r.swtpmSocketDirPath(d.Name),
}
for _, f := range files {
_, err := os.Stat(f)
if err == nil {
err = os.Remove(f)
if err != nil {
log.Warn("failed to clean file", map[string]interface{}{
"filename": f,
log.Warn("failed to clean", map[string]interface{}{
"filename": f,
log.FnError: err,
})
}
}
}
for _, d := range dirs {
_, err := os.Stat(d)
if err == nil {
err = os.RemoveAll(d)
if err != nil {
log.Warn("failed to clean", map[string]interface{}{
"directory": d,
log.FnError: err,
})
}
}
}

}
}

Expand Down Expand Up @@ -242,6 +260,12 @@ func (n *Node) Start(ctx context.Context, r *Runtime, nodeCh chan<- bmcInfo) (*N
}
}

if n.TPM {
params = append(params, "-chardev", "socket,id=chrtpm,path="+r.swtpmSocketPath(n.Name))
params = append(params, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm")
params = append(params, "-device", "tpm-tis,tpmdev=tpm0")
}

if r.enableVirtFS {
p := path.Join(r.sharedDir, n.Name)
err := os.MkdirAll(p, 0755)
Expand Down Expand Up @@ -309,6 +333,7 @@ func (n *Node) Start(ctx context.Context, r *Runtime, nodeCh chan<- bmcInfo) (*N
os.Remove(guest)
os.Remove(monitor)
os.Remove(r.socketPath(n.Name))
os.RemoveAll(r.swtpmSocketDirPath(n.Name))
}

vm := &NodeVM{
Expand All @@ -334,3 +359,46 @@ func createNVRAM(ctx context.Context, p string) error {
}
return well.CommandContext(ctx, "cp", defaultOVMFVarsPath, p).Run()
}

// StartSWTPM starts swtpm with software TPM socket
func (n *Node) StartSWTPM(ctx context.Context, r *Runtime) error {
err := os.Mkdir(r.swtpmSocketDirPath(n.Name), 0755)
if err != nil {
return err
}

log.Info("Starting swtpm for node", map[string]interface{}{
"name": n.Name,
"socket": r.swtpmSocketPath(n.Name),
})
c := well.CommandContext(ctx, "swtpm", "socket",
"--tpmstate", "dir="+r.swtpmSocketDirPath(n.Name),
"--tpm2",
"--ctrl",
"type=unixio,path="+r.swtpmSocketPath(n.Name),
)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
err = c.Start()
if err != nil {
return err
}

for {
_, err := os.Stat(r.swtpmSocketPath(n.Name))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
break
}

select {
case <-time.After(100 * time.Millisecond):
case <-ctx.Done():
return nil
}
}

return nil
}
8 changes: 8 additions & 0 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,11 @@ func (r *Runtime) guestSocketPath(host string) string {
func (r *Runtime) nvramPath(host string) string {
return filepath.Join(r.dataDir, "nvram", host+".fd")
}

func (r *Runtime) swtpmSocketDirPath(host string) string {
return filepath.Join(r.runDir, host)
}

func (r *Runtime) swtpmSocketPath(host string) string {
return filepath.Join(r.runDir, host, "swtpm.socket")
}