fleet/orbit/pkg/packaging/macos.go

362 lines
9.8 KiB
Go
Raw Normal View History

2021-02-09 03:23:50 +00:00
package packaging
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
"github.com/fleetdm/fleet/v4/pkg/secure"
2021-02-09 03:23:50 +00:00
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// See helful docs in http://bomutils.dyndns.org/tutorial.html
// BuildPkg builds a macOS .pkg. So far this is tested only on macOS but in theory it works with bomutils on
// Linux.
func BuildPkg(opt Options) (string, error) {
2021-02-09 03:23:50 +00:00
// Initialize directories
tmpDir, err := initializeTempDir()
2021-02-09 03:23:50 +00:00
if err != nil {
return "", err
2021-02-09 03:23:50 +00:00
}
2021-02-17 02:05:18 +00:00
defer os.RemoveAll(tmpDir)
2021-02-09 03:23:50 +00:00
filesystemRoot := filepath.Join(tmpDir, "root")
if err := secure.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
return "", errors.Wrap(err, "create root dir")
2021-02-09 03:23:50 +00:00
}
orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "orbit")
if err := secure.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
return "", errors.Wrap(err, "create orbit dir")
2021-02-09 03:23:50 +00:00
}
// Initialize autoupdate metadata
updateOpt := update.DefaultOptions
updateOpt.Platform = "macos"
2021-02-09 03:23:50 +00:00
updateOpt.RootDirectory = orbitRoot
2021-03-02 19:24:32 +00:00
updateOpt.OrbitChannel = opt.OrbitChannel
updateOpt.OsquerydChannel = opt.OsquerydChannel
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {
updateOpt.RootKeys = opt.UpdateRoots
}
2021-02-09 03:23:50 +00:00
if err := InitializeUpdates(updateOpt); err != nil {
return "", errors.Wrap(err, "initialize updates")
2021-02-09 03:23:50 +00:00
}
2021-02-17 02:05:18 +00:00
// Write files
if err := writePackageInfo(opt, tmpDir); err != nil {
return "", errors.Wrap(err, "write PackageInfo")
2021-02-17 02:05:18 +00:00
}
if err := writeDistribution(opt, tmpDir); err != nil {
return "", errors.Wrap(err, "write Distribution")
2021-02-17 02:05:18 +00:00
}
if err := writeScripts(opt, tmpDir); err != nil {
return "", errors.Wrap(err, "write postinstall")
2021-02-17 02:05:18 +00:00
}
if err := writeSecret(opt, orbitRoot); err != nil {
return "", errors.Wrap(err, "write enroll secret")
}
2021-02-17 02:05:18 +00:00
if opt.StartService {
if err := writeLaunchd(opt, filesystemRoot); err != nil {
return "", errors.Wrap(err, "write launchd")
2021-02-17 02:05:18 +00:00
}
}
if opt.FleetCertificate != "" {
if err := writeCertificate(opt, orbitRoot); err != nil {
return "", errors.Wrap(err, "write fleet certificate")
}
}
2021-03-02 19:24:32 +00:00
// TODO gate behind a flag and allow copying a local orbit
// if err := copyFile(
// "./orbit",
// filepath.Join(orbitRoot, "bin", "orbit", "macos", "current", "orbit"),
// 0755,
// ); err != nil {
// return errors.Wrap(err, "write orbit")
// }
2021-02-17 02:05:18 +00:00
2021-02-09 03:23:50 +00:00
// Build package
2021-03-09 23:22:17 +00:00
2021-02-09 03:23:50 +00:00
if err := xarBom(opt, tmpDir); err != nil {
return "", errors.Wrap(err, "build pkg")
2021-02-09 03:23:50 +00:00
}
generatedPath := filepath.Join(tmpDir, "orbit.pkg")
if len(opt.SignIdentity) != 0 {
log.Info().Str("identity", opt.SignIdentity).Msg("productsign package")
if err := signPkg(generatedPath, opt.SignIdentity); err != nil {
return "", errors.Wrap(err, "productsign")
}
}
if opt.Notarize {
if err := notarizePkg(generatedPath); err != nil {
return "", err
}
}
2021-02-09 03:23:50 +00:00
filename := fmt.Sprintf("orbit-osquery_%s_amd64.pkg", opt.Version)
if err := copyFile(generatedPath, filename, constant.DefaultFileMode); err != nil {
return "", errors.Wrap(err, "rename pkg")
2021-02-09 03:23:50 +00:00
}
log.Info().Str("path", filename).Msg("wrote pkg package")
return filename, nil
2021-02-09 03:23:50 +00:00
}
func writePackageInfo(opt Options, rootPath string) error {
2021-02-17 02:05:18 +00:00
// PackageInfo is metadata for the pkg
2021-02-09 03:23:50 +00:00
path := filepath.Join(rootPath, "flat", "base.pkg", "PackageInfo")
if err := secure.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
2021-02-09 03:23:50 +00:00
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosPackageInfoTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
2021-02-17 02:05:18 +00:00
func writeScripts(opt Options, rootPath string) error {
// Postinstall script
path := filepath.Join(rootPath, "scripts", "postinstall")
if err := secure.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
2021-02-17 02:05:18 +00:00
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosPostinstallTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0744); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
2021-02-17 02:05:18 +00:00
func writeLaunchd(opt Options, rootPath string) error {
// launchd is the service mechanism on macOS
path := filepath.Join(rootPath, "Library", "LaunchDaemons", "com.fleetdm.orbit.plist")
if err := secure.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
2021-02-17 02:05:18 +00:00
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosLaunchdTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0644); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
2021-02-09 03:23:50 +00:00
func writeDistribution(opt Options, rootPath string) error {
2021-02-17 02:05:18 +00:00
// Distribution file is metadata for the pkg
2021-02-09 03:23:50 +00:00
path := filepath.Join(rootPath, "flat", "Distribution")
if err := secure.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
2021-02-09 03:23:50 +00:00
return errors.Wrap(err, "mkdir")
}
var contents bytes.Buffer
if err := macosDistributionTemplate.Execute(&contents, opt); err != nil {
return errors.Wrap(err, "execute template")
}
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
return errors.Wrap(err, "write file")
}
return nil
}
func writeCertificate(opt Options, orbitRoot string) error {
// Fleet TLS certificate
dstPath := filepath.Join(orbitRoot, "fleet.pem")
if err := copyFile(opt.FleetCertificate, dstPath, 0644); err != nil {
return errors.Wrap(err, "write orbit")
}
return nil
}
2021-02-09 03:23:50 +00:00
// xarBom creates the actual .pkg format. It's a xar archive with a BOM (Bill of
// materials?). See http://bomutils.dyndns.org/tutorial.html.
func xarBom(opt Options, rootPath string) error {
// Adapted from BSD licensed
// https://github.com/go-flutter-desktop/hover/blob/v0.46.2/cmd/packaging/darwin-pkg.go
2021-02-17 02:05:18 +00:00
// Copy payload/scripts
if err := cpio(
filepath.Join(rootPath, "root"),
filepath.Join(rootPath, "flat", "base.pkg", "Payload"),
); err != nil {
return errors.Wrap(err, "cpio Payload")
2021-02-09 03:23:50 +00:00
}
2021-02-17 02:05:18 +00:00
if err := cpio(
filepath.Join(rootPath, "scripts"),
filepath.Join(rootPath, "flat", "base.pkg", "Scripts"),
); err != nil {
return errors.Wrap(err, "cpio Scripts")
2021-02-09 03:23:50 +00:00
}
// Make bom
var cmdMkbom *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmdMkbom = exec.Command("mkbom", filepath.Join(rootPath, "root"), filepath.Join("flat", "base.pkg", "Bom"))
cmdMkbom.Dir = rootPath
default:
cmdMkbom = exec.Command(
"docker", "run", "--rm", "-v", rootPath+":/root", "fleetdm/bomutils",
"mkbom", "-u", "0", "-g", "80",
// Use / instead of filepath.Join because these will always be paths within the Docker
// container (so Linux file paths)
"/root/root", "/root/flat/base.pkg/Bom",
)
2021-02-09 03:23:50 +00:00
}
cmdMkbom.Stdout = os.Stdout
cmdMkbom.Stderr = os.Stderr
if err := cmdMkbom.Run(); err != nil {
return errors.Wrap(err, "mkbom")
}
// List files for xar
var files []string
2021-02-17 21:39:25 +00:00
err := filepath.Walk(
filepath.Join(rootPath, "flat"),
func(path string, info os.FileInfo, _ error) error {
relativePath, err := filepath.Rel(filepath.Join(rootPath, "flat"), path)
if err != nil {
return err
}
files = append(files, relativePath)
return nil
},
)
if err != nil {
2021-02-09 03:23:50 +00:00
return errors.Wrap(err, "iterate files")
}
// Make xar
var cmdXar *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmdXar = exec.Command("xar", append([]string{"--compression", "none", "-cf", filepath.Join("..", "orbit.pkg")}, files...)...)
cmdXar.Dir = filepath.Join(rootPath, "flat")
default:
cmdXar = exec.Command(
"docker", "run", "--rm", "-v", rootPath+":/root", "-w", "/root/flat", "fleetdm/bomutils",
"xar",
)
cmdXar.Args = append(cmdXar.Args, append([]string{"--compression", "none", "-cf", "/root/orbit.pkg"}, files...)...)
}
2021-02-09 03:23:50 +00:00
cmdXar.Stdout = os.Stdout
cmdXar.Stderr = os.Stderr
if err := cmdXar.Run(); err != nil {
return errors.Wrap(err, "run xar")
}
return nil
}
2021-02-17 02:05:18 +00:00
func cpio(srcPath, dstPath string) error {
// This is the compression routine that is expected for pkg files.
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0755)
2021-02-17 02:05:18 +00:00
if err != nil {
return errors.Wrap(err, "open dst")
}
defer dst.Close()
cmdFind := exec.Command("find", ".")
cmdFind.Dir = srcPath
cmdCpio := exec.Command("cpio", "-o", "--format", "odc", "-R", "0:80")
cmdCpio.Dir = srcPath
cmdGzip := exec.Command("gzip", "-c")
// Pipes like this: find | cpio | gzip > dstPath
cmdCpio.Stdin, err = cmdFind.StdoutPipe()
if err != nil {
return errors.Wrap(err, "pipe cpio")
}
cmdGzip.Stdin, err = cmdCpio.StdoutPipe()
if err != nil {
return errors.Wrap(err, "pipe gzip")
}
cmdGzip.Stdout = dst
err = cmdGzip.Start()
if err != nil {
return errors.Wrap(err, "start gzip")
}
err = cmdCpio.Start()
if err != nil {
return errors.Wrap(err, "start cpio")
}
err = cmdFind.Run()
if err != nil {
return errors.Wrap(err, "run find")
}
err = cmdCpio.Wait()
if err != nil {
return errors.Wrap(err, "wait cpio")
}
err = cmdGzip.Wait()
if err != nil {
return errors.Wrap(err, "wait gzip")
}
err = dst.Sync()
2021-02-17 02:05:18 +00:00
if err != nil {
return errors.Wrap(err, "sync dst")
2021-02-17 02:05:18 +00:00
}
return nil
}
func signPkg(pkgPath, identity string) error {
var outBuf bytes.Buffer
cmdProductsign := exec.Command(
"productsign",
"--sign", identity,
pkgPath,
pkgPath+".signed",
)
cmdProductsign.Stdout = &outBuf
cmdProductsign.Stderr = &outBuf
if err := cmdProductsign.Run(); err != nil {
fmt.Println(outBuf.String())
return errors.Wrap(err, "productsign")
}
if err := os.Rename(pkgPath+".signed", pkgPath); err != nil {
return errors.Wrap(err, "rename signed")
}
return nil
}