From fc6e6afd344db7c1fa7858c10e5a9edcf3ee2f7c Mon Sep 17 00:00:00 2001 From: Zach Wasserman Date: Tue, 2 Mar 2021 17:19:24 -0800 Subject: [PATCH] Add support for RPM packages This was thankfully made easy by NFPM! Refactored the DEB packaging so that the code is almost entirely shared between the two. --- cmd/package/package.go | 4 +- go.sum | 5 + pkg/packaging/deb.go | 232 +-------------------------------- pkg/packaging/linux_shared.go | 233 ++++++++++++++++++++++++++++++++++ pkg/packaging/rpm.go | 7 + 5 files changed, 250 insertions(+), 231 deletions(-) create mode 100644 pkg/packaging/linux_shared.go create mode 100644 pkg/packaging/rpm.go diff --git a/cmd/package/package.go b/cmd/package/package.go index 04994d1e52..0e06c6795e 100644 --- a/cmd/package/package.go +++ b/cmd/package/package.go @@ -107,8 +107,10 @@ func main() { return packaging.BuildPkg(opt) case "deb": return packaging.BuildDeb(opt) + case "rpm": + return packaging.BuildRPM(opt) default: - return errors.New("type must be one of ('pkg', 'deb')") + return errors.New("type must be one of ('pkg', 'deb', 'rpm')") } } diff --git a/go.sum b/go.sum index 2d29b71e48..47e4368da6 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/bombsimon/wsl/v3 v3.1.0 h1:E5SRssoBgtVFPcYWUOFJEcgaySgdtTNYzsSKDOY7ss8= github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -229,6 +230,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk= +github.com/google/rpmpack v0.0.0-20201225075926-0a97c2c4b688 h1:jyGRCFMyDK/gKe6QRZQWci5+wEUBFElvxLHs3iwO3hY= github.com/google/rpmpack v0.0.0-20201225075926-0a97c2c4b688/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= @@ -412,6 +414,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -465,6 +468,7 @@ github.com/ryancurrah/gomodguard v1.2.0/go.mod h1:rNqbC4TOIdUDcVMSIpNNAzTbzXAZa6 github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= @@ -558,6 +562,7 @@ github.com/tommy-muehle/go-mnd v1.3.1-0.20201008215730-16041ac3fe65 h1:Y0bLA422k github.com/tommy-muehle/go-mnd v1.3.1-0.20201008215730-16041ac3fe65/go.mod h1:T22e7iRN4LsFPZGyRLRXeF+DWVXFuV9thsyO7NjbbTI= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= diff --git a/pkg/packaging/deb.go b/pkg/packaging/deb.go index 8ff2847728..b8ba100c1a 100644 --- a/pkg/packaging/deb.go +++ b/pkg/packaging/deb.go @@ -1,235 +1,7 @@ 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/goreleaser/nfpm/v2" - "github.com/goreleaser/nfpm/v2/deb" - "github.com/goreleaser/nfpm/v2/files" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" -) +import "github.com/goreleaser/nfpm/v2/deb" 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, "root") - if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create root dir") - } - orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "orbit") - if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil { - return errors.Wrap(err, "create orbit dir") - } - - // Initialize autoupdate metadata - - updateOpt := update.DefaultOptions - updateOpt.Platform = "linux" - updateOpt.RootDirectory = orbitRoot - updateOpt.OrbitChannel = opt.OrbitChannel - updateOpt.OsqueryChannel = opt.OsqueryChannel - - // TODO these should be configurable - updateOpt.ServerURL = "https://tuf.fleetctl.com" - - if err := initializeUpdates(updateOpt); err != nil { - return errors.Wrap(err, "initialize updates") - } - - // 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") - } - - // Pick up all file contents - - contents := files.Contents{ - &files.Content{ - Source: filepath.Join(filesystemRoot, "**"), - Destination: "/", - }, - &files.Content{ - Source: "orbit", - Destination: "/var/lib/orbit/orbit", - FileInfo: &files.ContentFileInfo{ - Mode: constant.DefaultExecutableMode, - }, - }, - // Symlink current into /var/lib/orbit/bin/orbit/orbit - &files.Content{ - Source: "/var/lib/orbit/bin/orbit/linux/" + opt.OrbitChannel + "/orbit", - Destination: "/var/lib/orbit/bin/orbit/orbit", - Type: "symlink", - FileInfo: &files.ContentFileInfo{ - Mode: constant.DefaultExecutableMode | os.ModeSymlink, - }, - }, - // Symlink current into /usr/local/bin - &files.Content{ - Source: "/var/lib/orbit/bin/orbit/orbit", - Destination: "/usr/local/bin/orbit", - Type: "symlink", - FileInfo: &files.ContentFileInfo{ - 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: opt.Version, - Description: "Orbit osquery -- runtime and autoupdater by Fleet", - Arch: "amd64", - Maintainer: "Fleet Engineers ", - Homepage: "https://github.com/fleetdm/orbit", - Overridables: nfpm.Overridables{ - Contents: contents, - EmptyFolders: []string{ - "/var/log/osquery", - "/var/log/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 -StartLimitIntervalSec=0 - -[Service] -TimeoutStartSec=0 -EnvironmentFile=/etc/default/orbit -ExecStart=/var/lib/orbit/bin/orbit/orbit -Restart=always -RestartSec=1 -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 .FleetCertificate }}ORBIT_FLEET_CERTIFICATE=/var/lib/orbit/fleet.pem{{ 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 restart 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 + return buildNFPM(opt, deb.Default) } diff --git a/pkg/packaging/linux_shared.go b/pkg/packaging/linux_shared.go new file mode 100644 index 0000000000..c1b7db1593 --- /dev/null +++ b/pkg/packaging/linux_shared.go @@ -0,0 +1,233 @@ +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/goreleaser/nfpm/v2" + "github.com/goreleaser/nfpm/v2/files" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +func buildNFPM(opt Options, pkger nfpm.Packager) 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, "root") + if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create root dir") + } + orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "orbit") + if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil { + return errors.Wrap(err, "create orbit dir") + } + + // Initialize autoupdate metadata + + updateOpt := update.DefaultOptions + updateOpt.Platform = "linux" + updateOpt.RootDirectory = orbitRoot + updateOpt.OrbitChannel = opt.OrbitChannel + updateOpt.OsqueryChannel = opt.OsqueryChannel + + // TODO these should be configurable + updateOpt.ServerURL = "https://tuf.fleetctl.com" + + if err := initializeUpdates(updateOpt); err != nil { + return errors.Wrap(err, "initialize updates") + } + + // 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") + } + + // Pick up all file contents + + contents := files.Contents{ + &files.Content{ + Source: filepath.Join(filesystemRoot, "**"), + Destination: "/", + }, + &files.Content{ + Source: "orbit", + Destination: "/var/lib/orbit/orbit", + FileInfo: &files.ContentFileInfo{ + Mode: constant.DefaultExecutableMode, + }, + }, + // Symlink current into /var/lib/orbit/bin/orbit/orbit + &files.Content{ + Source: "/var/lib/orbit/bin/orbit/linux/" + opt.OrbitChannel + "/orbit", + Destination: "/var/lib/orbit/bin/orbit/orbit", + Type: "symlink", + FileInfo: &files.ContentFileInfo{ + Mode: constant.DefaultExecutableMode | os.ModeSymlink, + }, + }, + // Symlink current into /usr/local/bin + &files.Content{ + Source: "/var/lib/orbit/bin/orbit/orbit", + Destination: "/usr/local/bin/orbit", + Type: "symlink", + FileInfo: &files.ContentFileInfo{ + 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: opt.Version, + Description: "Orbit osquery -- runtime and autoupdater by Fleet", + Arch: "amd64", + Maintainer: "Fleet Engineers ", + Homepage: "https://github.com/fleetdm/orbit", + Overridables: nfpm.Overridables{ + Contents: contents, + EmptyFolders: []string{ + "/var/log/osquery", + "/var/log/orbit", + }, + Scripts: nfpm.Scripts{ + PostInstall: postInstallPath, + }, + }, + } + 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 := pkger.Package(info, out); err != nil { + return errors.Wrap(err, "write package") + } + log.Info().Str("path", filename).Msg("wrote 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=Orbit osquery +After=network.service syslog.service +StartLimitIntervalSec=0 + +[Service] +TimeoutStartSec=0 +EnvironmentFile=/etc/default/orbit +ExecStart=/var/lib/orbit/bin/orbit/orbit +Restart=always +RestartSec=1 +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 .FleetCertificate }}ORBIT_FLEET_CERTIFICATE=/var/lib/orbit/fleet.pem{{ 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 restart 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/rpm.go b/pkg/packaging/rpm.go new file mode 100644 index 0000000000..ad62838ee5 --- /dev/null +++ b/pkg/packaging/rpm.go @@ -0,0 +1,7 @@ +package packaging + +import "github.com/goreleaser/nfpm/v2/rpm" + +func BuildRPM(opt Options) error { + return buildNFPM(opt, rpm.Default) +}