diff --git a/cmd/package/package.go b/cmd/package/package.go index 19777438ca..b1635e1a5a 100644 --- a/cmd/package/package.go +++ b/cmd/package/package.go @@ -1,34 +1,18 @@ package main import ( - "bytes" - "io/ioutil" "os" - "path/filepath" - "text/template" "time" - "github.com/fleetdm/orbit/pkg/constant" - "github.com/fleetdm/orbit/pkg/update" - "github.com/fleetdm/orbit/pkg/update/filestore" - "github.com/goreleaser/nfpm/v2" - "github.com/goreleaser/nfpm/v2/deb" - "github.com/goreleaser/nfpm/v2/files" + "github.com/fleetdm/orbit/pkg/packaging" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" ) -type packageOptions struct { - FleetURL string - EnrollSecret string - StartService bool - Insecure bool -} - func main() { - var opt packageOptions + var opt packaging.Options log.Logger = log.Output( zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano}, ) @@ -74,227 +58,10 @@ func main() { } } - return buildLinux(opt) + return packaging.BuildDeb(opt) } if err := app.Run(os.Args); err != nil { log.Fatal().Err(err).Msg("package failed") } } - -func buildLinux(opt packageOptions) error { - // Initialize directories - - tmpDir, err := ioutil.TempDir("", "orbit-package") - if err != nil { - return errors.Wrap(err, "failed to create temp dir") - } - defer os.RemoveAll(tmpDir) - log.Debug().Str("path", tmpDir).Msg("created temp dir") - - filesystemRoot := filepath.Join(tmpDir, "filesystem") - if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create filesystem dir") - } - orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "fleet", "orbit") - if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create orbit dir") - } - - // Write files - - if err := writeSystemdUnit(opt, filesystemRoot); err != nil { - return errors.Wrap(err, "write systemd unit") - } - - if err := writeEnvFile(opt, filesystemRoot); err != nil { - return errors.Wrap(err, "write env file") - } - - postInstallPath := filepath.Join(tmpDir, "postinstall.sh") - if err := writePostInstall(opt, postInstallPath); err != nil { - return errors.Wrap(err, "write postinstall script") - } - - // Initialize autoupdate metadata - - localStore, err := filestore.New(filepath.Join(orbitRoot, "tuf-metadata.json")) - if err != nil { - return errors.Wrap(err, "failed to create local metadata store") - } - updateOpt := update.DefaultOptions - updateOpt.RootDirectory = orbitRoot - updateOpt.ServerURL = "https://tuf.fleetctl.com" - updateOpt.LocalStore = localStore - updateOpt.Platform = "linux" - - updater, err := update.New(updateOpt) - if err != nil { - return errors.Wrap(err, "failed to init updater") - } - if err := updater.UpdateMetadata(); err != nil { - return errors.Wrap(err, "failed to update metadata") - } - osquerydPath, err := updater.Get("osqueryd", "stable") - if err != nil { - return errors.Wrap(err, "failed to get osqueryd") - } - log.Debug().Str("path", osquerydPath).Msg("got osqueryd") - - // Pick up all file contents - - contents := files.Contents{ - &files.Content{ - Source: filepath.Join(filesystemRoot, "**"), - Destination: "/", - }, - &files.Content{ - Source: "orbit", - Destination: "/var/lib/fleet/orbit/orbit", - FileInfo: &files.ContentFileInfo{ - Mode: constant.DefaultExecutableMode, - }, - }, - &files.Content{ - Source: "/var/lib/fleet/orbit/orbit", - Destination: "/usr/local/bin/orbit", - Type: "symlink", - FileInfo: &files.ContentFileInfo{ - // TODO follow up on nfpm not respecting this - // https://github.com/goreleaser/nfpm/issues/286 - Mode: constant.DefaultExecutableMode | os.ModeSymlink, - }, - }, - } - contents, err = files.ExpandContentGlobs(contents, false) - if err != nil { - return errors.Wrap(err, "glob contents") - } - for _, c := range contents { - log.Debug().Interface("file", c).Msg("added file") - } - - // Build package - - info := &nfpm.Info{ - Name: "orbit-osquery", - Version: "0.0.1", - Description: "Osquery launcher and autoupdater", - Arch: "amd64", - Maintainer: "FleetDM Engineers ", - Homepage: "https://github.com/fleetdm/orbit", - Overridables: nfpm.Overridables{ - Contents: contents, - EmptyFolders: []string{ - "/var/log/osquery", - "/var/log/fleet/orbit", - }, - Scripts: nfpm.Scripts{ - PostInstall: postInstallPath, - }, - }, - } - pkger := deb.Default - filename := pkger.ConventionalFileName(info) - - out, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode) - if err != nil { - return errors.Wrap(err, "open output file") - } - defer out.Close() - - if err := deb.Default.Package(info, out); err != nil { - return errors.Wrap(err, "write deb package") - } - log.Info().Str("path", filename).Msg("wrote deb package") - - return nil -} - -func writeSystemdUnit(optt packageOptions, rootPath string) error { - systemdRoot := filepath.Join(rootPath, "usr", "lib", "systemd", "system") - if err := os.MkdirAll(systemdRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create systemd dir") - } - if err := ioutil.WriteFile( - filepath.Join(systemdRoot, "orbit.service"), - []byte(` -[Unit] -Description=Fleet Orbit osquery -After=network.service syslog.service - -[Service] -TimeoutStartSec=0 -EnvironmentFile=/etc/default/orbit -ExecStart=/usr/local/bin/orbit -Restart=on-failure -KillMode=control-group -KillSignal=SIGTERM -CPUQuota=20% - -[Install] -WantedBy=multi-user.target -`), - constant.DefaultFileMode, - ); err != nil { - return errors.Wrap(err, "write file") - } - - return nil -} - -var envTemplate = template.Must(template.New("env").Parse(` -{{- if .Insecure }}ORBIT_INSECURE=true{{ end }} -{{ if .FleetURL }}ORBIT_FLEET_URL={{.FleetURL}}{{ end }} -{{ if .EnrollSecret }}ORBIT_ENROLL_SECRET={{.EnrollSecret}}{{ end }} -`)) - -func writeEnvFile(opt packageOptions, rootPath string) error { - envRoot := filepath.Join(rootPath, "etc", "default") - if err := os.MkdirAll(envRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create env dir") - } - - var contents bytes.Buffer - if err := envTemplate.Execute(&contents, opt); err != nil { - return errors.Wrap(err, "execute template") - } - - if err := ioutil.WriteFile( - filepath.Join(envRoot, "orbit"), - contents.Bytes(), - constant.DefaultFileMode, - ); err != nil { - return errors.Wrap(err, "write file") - } - - return nil -} - -var postInstallTemplate = template.Must(template.New("postinstall").Parse(` -#!/bin/sh - -# Exit on error -set -e - -# If we have a systemd, daemon-reload away now -if [ -x /bin/systemctl ] && pidof systemd ; then - /bin/systemctl daemon-reload 2>/dev/null 2>&1 -{{ if .StartService -}} - /bin/systemctl start orbit.service 2>&1 -{{- end}} -fi -`)) - -func writePostInstall(opt packageOptions, path string) error { - var contents bytes.Buffer - if err := postInstallTemplate.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 -} diff --git a/pkg/packaging/deb.go b/pkg/packaging/deb.go new file mode 100644 index 0000000000..88a4d25ba3 --- /dev/null +++ b/pkg/packaging/deb.go @@ -0,0 +1,235 @@ +package packaging + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "github.com/fleetdm/orbit/pkg/constant" + "github.com/fleetdm/orbit/pkg/update" + "github.com/fleetdm/orbit/pkg/update/filestore" + "github.com/goreleaser/nfpm/v2" + "github.com/goreleaser/nfpm/v2/deb" + "github.com/goreleaser/nfpm/v2/files" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +func BuildDeb(opt Options) error { + // Initialize directories + + tmpDir, err := ioutil.TempDir("", "orbit-package") + if err != nil { + return errors.Wrap(err, "failed to create temp dir") + } + defer os.RemoveAll(tmpDir) + log.Debug().Str("path", tmpDir).Msg("created temp dir") + + filesystemRoot := filepath.Join(tmpDir, "filesystem") + if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create filesystem dir") + } + orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "fleet", "orbit") + if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create orbit dir") + } + + // Write files + + if err := writeSystemdUnit(opt, filesystemRoot); err != nil { + return errors.Wrap(err, "write systemd unit") + } + + if err := writeEnvFile(opt, filesystemRoot); err != nil { + return errors.Wrap(err, "write env file") + } + + postInstallPath := filepath.Join(tmpDir, "postinstall.sh") + if err := writePostInstall(opt, postInstallPath); err != nil { + return errors.Wrap(err, "write postinstall script") + } + + // Initialize autoupdate metadata + + localStore, err := filestore.New(filepath.Join(orbitRoot, "tuf-metadata.json")) + if err != nil { + return errors.Wrap(err, "failed to create local metadata store") + } + updateOpt := update.DefaultOptions + updateOpt.RootDirectory = orbitRoot + updateOpt.ServerURL = "https://tuf.fleetctl.com" + updateOpt.LocalStore = localStore + updateOpt.Platform = "linux" + + updater, err := update.New(updateOpt) + if err != nil { + return errors.Wrap(err, "failed to init updater") + } + if err := updater.UpdateMetadata(); err != nil { + return errors.Wrap(err, "failed to update metadata") + } + osquerydPath, err := updater.Get("osqueryd", "stable") + if err != nil { + return errors.Wrap(err, "failed to get osqueryd") + } + log.Debug().Str("path", osquerydPath).Msg("got osqueryd") + + // Pick up all file contents + + contents := files.Contents{ + &files.Content{ + Source: filepath.Join(filesystemRoot, "**"), + Destination: "/", + }, + &files.Content{ + Source: "orbit", + Destination: "/var/lib/fleet/orbit/orbit", + FileInfo: &files.ContentFileInfo{ + Mode: constant.DefaultExecutableMode, + }, + }, + &files.Content{ + Source: "/var/lib/fleet/orbit/orbit", + Destination: "/usr/local/bin/orbit", + Type: "symlink", + FileInfo: &files.ContentFileInfo{ + // TODO follow up on nfpm not respecting this + // https://github.com/goreleaser/nfpm/issues/286 + Mode: constant.DefaultExecutableMode | os.ModeSymlink, + }, + }, + } + contents, err = files.ExpandContentGlobs(contents, false) + if err != nil { + return errors.Wrap(err, "glob contents") + } + for _, c := range contents { + log.Debug().Interface("file", c).Msg("added file") + } + + // Build package + + info := &nfpm.Info{ + Name: "orbit-osquery", + Version: "0.0.1", + Description: "Osquery launcher and autoupdater", + Arch: "amd64", + Maintainer: "FleetDM Engineers ", + Homepage: "https://github.com/fleetdm/orbit", + Overridables: nfpm.Overridables{ + Contents: contents, + EmptyFolders: []string{ + "/var/log/osquery", + "/var/log/fleet/orbit", + }, + Scripts: nfpm.Scripts{ + PostInstall: postInstallPath, + }, + }, + } + pkger := deb.Default + filename := pkger.ConventionalFileName(info) + + out, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode) + if err != nil { + return errors.Wrap(err, "open output file") + } + defer out.Close() + + if err := deb.Default.Package(info, out); err != nil { + return errors.Wrap(err, "write deb package") + } + log.Info().Str("path", filename).Msg("wrote deb package") + + return nil +} + +func writeSystemdUnit(opt Options, rootPath string) error { + systemdRoot := filepath.Join(rootPath, "usr", "lib", "systemd", "system") + if err := os.MkdirAll(systemdRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create systemd dir") + } + if err := ioutil.WriteFile( + filepath.Join(systemdRoot, "orbit.service"), + []byte(` +[Unit] +Description=Fleet Orbit osquery +After=network.service syslog.service + +[Service] +TimeoutStartSec=0 +EnvironmentFile=/etc/default/orbit +ExecStart=/usr/local/bin/orbit +Restart=on-failure +KillMode=control-group +KillSignal=SIGTERM +CPUQuota=20% + +[Install] +WantedBy=multi-user.target +`), + constant.DefaultFileMode, + ); err != nil { + return errors.Wrap(err, "write file") + } + + return nil +} + +var envTemplate = template.Must(template.New("env").Parse(` +{{- if .Insecure }}ORBIT_INSECURE=true{{ end }} +{{ if .FleetURL }}ORBIT_FLEET_URL={{.FleetURL}}{{ end }} +{{ if .EnrollSecret }}ORBIT_ENROLL_SECRET={{.EnrollSecret}}{{ end }} +`)) + +func writeEnvFile(opt Options, rootPath string) error { + envRoot := filepath.Join(rootPath, "etc", "default") + if err := os.MkdirAll(envRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create env dir") + } + + var contents bytes.Buffer + if err := envTemplate.Execute(&contents, opt); err != nil { + return errors.Wrap(err, "execute template") + } + + if err := ioutil.WriteFile( + filepath.Join(envRoot, "orbit"), + contents.Bytes(), + constant.DefaultFileMode, + ); err != nil { + return errors.Wrap(err, "write file") + } + + return nil +} + +var postInstallTemplate = template.Must(template.New("postinstall").Parse(` +#!/bin/sh + +# Exit on error +set -e + +# If we have a systemd, daemon-reload away now +if [ -x /bin/systemctl ] && pidof systemd ; then + /bin/systemctl daemon-reload 2>/dev/null 2>&1 +{{ if .StartService -}} + /bin/systemctl start orbit.service 2>&1 +{{- end}} +fi +`)) + +func writePostInstall(opt Options, path string) error { + var contents bytes.Buffer + if err := postInstallTemplate.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 +} diff --git a/pkg/packaging/packaging.go b/pkg/packaging/packaging.go new file mode 100644 index 0000000000..4086f971f2 --- /dev/null +++ b/pkg/packaging/packaging.go @@ -0,0 +1,16 @@ +// package packaging provides tools for buildin Orbit installation packages. +package packaging + +// Options are the configurable options provided for the package. +type Options struct { + // FleetURL is the URL to the Fleet server. + FleetURL string + // EnrollSecret is the enroll secret used to authenticate to the Fleet + // server. + EnrollSecret string + // StartService is a boolean indicating whether to start a system-specific + // background service. + StartService bool + // Insecure enables insecure TLS connections for the generated package. + Insecure bool +}