Skip to content

Commit

Permalink
Implement the first part of 'trust launch'
Browse files Browse the repository at this point in the history
Trust launch creates a new SUDI keyset, creates a machine, provisions
it for that SUDI keyset, then installs it.

This patchset implements the auto-provisioning, but not yet install.

Signed-off-by: Serge Hallyn <[email protected]>
  • Loading branch information
hallyn committed Sep 27, 2023
1 parent 257d13f commit d471960
Show file tree
Hide file tree
Showing 8 changed files with 479 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ all: mosctl mosb trust $(ZOT) $(ORAS) $(REGCTL)

VERSION_LDFLAGS=-X github.com/project-machine/mos/pkg/mosconfig.Version=$(MAIN_VERSION) \
-X github.com/project-machine/mos/pkg/trust.Version=$(MAIN_VERSION) \
-X github.com/project-machine/mos/pkg/mosconfig.LayerVersion=0.0.1 \
-X github.com/project-machine/mos/pkg/mosconfig.LayerVersion=0.0.2 \
-X github.com/project-machine/mos/pkg/trust.BootkitVersion=$(BOOTKIT_VERSION)

mosctl: .made-gofmt $(GO_SRC)
Expand Down
175 changes: 175 additions & 0 deletions cmd/trust/launch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package main

import (
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/project-machine/mos/pkg/provider"
"github.com/project-machine/mos/pkg/trust"
"github.com/urfave/cli"
)

var launchCmd = cli.Command{
Name: "launch",
Usage: "launch a new machine",
UsageText: `name install-url
name: name to give the VM
install-url: install.json distoci URL to install (e.g. zothub.io/machine/zot/install:1.0.0)
Note that install is not yet supported.`,
Action: doLaunch,
Flags: []cli.Flag{
cli.StringFlag{
Name: "project",
Usage: "keyset:project to which this machine will belong (TRUST_PROJECT)",
},
cli.StringFlag{
Name: "serial, uuid",
Usage: "Serial number UUID to assign to the machine, empty to use a random UUID",
Value: "",
},
cli.BoolFlag{
Name: "skip-provisioning",
Usage: "Skip provisioning the machine",
},
cli.BoolFlag{
Name: "skip-install",
Usage: "Skip running the install ISO",
},
cli.StringFlag{
Name: "type",
Usage: "Type of machine to launch.",
Value: "kvm",
},
},
}

func splitFullProject(full string) (string, string, error) {
s := strings.Split(full, ":")
if len(s) != 2 {
return "", "", errors.Errorf("Bad project name %q, should be keyset:project, e.g. snakeoil:default.", full)
}
return s[0], s[1], nil
}

func doLaunch(ctx *cli.Context) error {
args := ctx.Args()
if len(args) < 1 {
return errors.New("A name for the new machine is required")
}

if !ctx.Bool("skip-install") && len(args) != 2 {
return errors.New("Install manifest URL is required")
}

mtype := ctx.String("type")
var p provider.Provider
var err error
switch mtype {
case "kvm":
p, err = provider.NewKVMProvider()
if err != nil {
return errors.Wrapf(err, "Failed to instantiate machine provider for type %q", mtype)
}
default:
return errors.Errorf("Unknown machine type: %q", mtype)
}

mname := args[0]
if mname == "" {
return errors.New("Please specify machine name")
}

if p.Exists(mname) {
return errors.Errorf("Machine %q already exists", mname)
}

fullProject := ctx.String("project")
keyset, project, err := splitFullProject(fullProject)
if err != nil {
return err
}

trustDir, err := getMosKeyPath()
if err != nil {
return err
}

keysetDir := filepath.Join(trustDir, keyset)
projDir := filepath.Join(keysetDir, "manifest", project)
if !PathExists(projDir) {
return errors.Errorf("Project %s not found", fullProject)
}

uuid := ctx.String("uuid")
sudiDir, err := genSudi(keysetDir, projDir, uuid)
if err != nil {
return errors.Wrapf(err, "Failed generating SUDI")
}
if uuid == "" {
uuid = filepath.Base(sudiDir)
}

defer func() {
if err != nil {
os.RemoveAll(sudiDir)
}
}()

if err := makeSudiVfat(sudiDir); err != nil {
return errors.Wrapf(err, "Failed creating SUDI disk")
}

m, err := p.New(mname, fullProject, uuid)
if err != nil {
return errors.Wrapf(err, "Failed to create new machine")
}

defer func() {
if err != nil {
p.Delete(mname)
}
}()

if err := m.RunProvision(); err != nil {
return errors.Wrapf(err, "Failed to run provisioning ISO")
}

return nil
}

// Make a VFAT disk storing the already-generated SUDI cert and privkey
func makeSudiVfat(sudiDir string) error {
cert := filepath.Join(sudiDir, "cert.pem")
key := filepath.Join(sudiDir, "privkey.pem")
disk := filepath.Join(sudiDir, "sudi.vfat")
if !trust.PathExists(cert) || !trust.PathExists(key) {
return errors.Errorf("cert or key does not exist")
}
if trust.PathExists(disk) {
return errors.Errorf("sudi.vfat already exists")
}

f, err := os.OpenFile(disk, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return errors.Wrapf(err, "Failed creating sudi disk")
}
f.Close()

if err := os.Truncate(disk, 20*1024*1024); err != nil {
return errors.Wrapf(err, "Failed truncating sudi disk")
}
if err := trust.RunCommand("mkfs.vfat", "-n", "trust-data", disk); err != nil {
return errors.Wrapf(err, "Failed formatting sudi disk")
}
if err := trust.RunCommand("mcopy", "-i", disk, cert, "::cert.pem"); err != nil {
return errors.Wrapf(err, "Failed copying cert to sudi disk")
}
if err := trust.RunCommand("mcopy", "-i", disk, key, "::privkey.pem"); err != nil {
return errors.Wrapf(err, "Failed copying key to sudi disk")
}

return nil
}
5 changes: 4 additions & 1 deletion cmd/trust/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ func main() {
// keyset
keysetCmd,

// launch
launchCmd,

// project
projectCmd,

// sudo
// sudi
sudiCmd,

// sign
Expand Down
36 changes: 24 additions & 12 deletions cmd/trust/sudi.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ func doGenSudi(ctx *cli.Context) error {
var myUUID string
if len(args) == 3 {
myUUID = args[2]
} else {
myUUID = uuid.NewString()
}

trustDir, err := getMosKeyPath()
Expand All @@ -66,37 +64,51 @@ func doGenSudi(ctx *cli.Context) error {
return fmt.Errorf("Project not found: %s", projName)
}

capath := filepath.Join(keysetPath, "sudi-ca")
snPath := filepath.Join(projPath, "sudi", myUUID)
prodUUID, err := os.ReadFile(filepath.Join(projPath, "uuid"))
if _, err = genSudi(keysetPath, projPath, myUUID); err != nil {
return errors.Wrapf(err, "Failed generating SUDI")
}

return nil
}

// Generate a SUDI key for given uuid. If uuid is "", then generate a
// new UUID. Return the directory path for this new SUDI cert.
func genSudi(keysetPath, projDir, sudiUUID string) (string, error) {
prodUUID, err := os.ReadFile(filepath.Join(projDir, "uuid"))
if err != nil {
return errors.Wrapf(err, "Failed reading project UUID")
return "", errors.Wrapf(err, "Failed reading project UUID")
}

if sudiUUID == "" {
sudiUUID = uuid.NewString()
}

// read the project CA certificate
capath := filepath.Join(keysetPath, "sudi-ca")
caCert, err := readCertificateFromFile(filepath.Join(capath, "cert.pem"))
if err != nil {
return errors.Wrapf(err, "Failed reading SUDI CA certificate")
return "", errors.Wrapf(err, "Failed reading SUDI CA certificate")
}

// read the project CA private key to sign the sudi key with
caKey, err := readPrivKeyFromFile(filepath.Join(capath, "privkey.pem"))
if err != nil {
return errors.Wrapf(err, "Failed reading SUDI CA key")
return "", errors.Wrapf(err, "Failed reading SUDI CA key")
}

certTmpl := newCertTemplate(string(prodUUID), myUUID)
certTmpl := newCertTemplate(string(prodUUID), sudiUUID)

snPath := filepath.Join(projDir, "sudi", sudiUUID)
if err := trust.EnsureDir(snPath); err != nil {
return errors.Wrapf(err, "Failed creating new SUDI directory")
return "", errors.Wrapf(err, "Failed creating new SUDI directory")
}

if err := SignCert(&certTmpl, caCert, caKey, snPath); err != nil {
os.RemoveAll(snPath)
return errors.Wrapf(err, "Failed creating new SUDI keypair")
return "", errors.Wrapf(err, "Failed creating new SUDI keypair")
}

return nil
return snPath, nil
}

func newCertTemplate(productUUID, machineUUID string) x509.Certificate {
Expand Down
3 changes: 2 additions & 1 deletion layers/provision/trust-provision
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ done
[ -z "$missing" ] ||
fail "$devpath was found, but did not contain ${missing# }"

exec mosctl provision --disk /dev/sda --wipe \
mosctl provision --disk /dev/sda --wipe \
"$mp/cert.pem" "$mp/privkey.pem"

if [ $? -eq 0 ]; then
echo "XXX provisioned successfully XXX"
else
echo "XXX FAIL XXX"
exit 1
fi
Loading

0 comments on commit d471960

Please sign in to comment.