Fleet Desktop MVP (#4530)

* WIP

* WIP2

* Fix orbit and fleetctl tests

* Amend macos-app default

* Add some fixes

* Use fleetctl updates roots command

* Add more fixes to Updater

* Fixes to app publishing and downloading

* Add more changes to support fleetctl cross generation

* Amend comment

* Add pkg generation to ease testing

* Make more fixes

* Add changes entry

* Add legacy targets (until our TUF system exposes the new app)

* Fix fleetctl preview

* Fix bool flag

* Fix orbit logic for disabled-updates and dev-mode

* Fix TestPreview

* Remove constant and fix zip-slip attack (codeql)

* Return unknown error

* Fix updater's checkExec

* Add support for executable signing in init_tuf.sh

* Try only signing orbit

* Fix init_tuf.sh targets, macos-app only for osqueryd

* Specify GOARCH to support M1s

* Add workflow to generate osqueryd.app.tar.gz

* Use 5.2.2 on init_tuf.sh

* Add unit test for tar.gz target

* Use artifacts instead of releases

* Remove copy paste residue

* Fleet Desktop Packaging WIP

* Ignore gosec warning

* Trigger on PR too

* Install Go in workflow

* Pass url parameter to desktop app

* Fix fleetctl package

* Final set of changes for v1 of Fleet Desktop

* Add changes

* PR fixes

* Fix CI build

* add larger menu bar icon

* Add transparency item

* Delete host_device_auth entry on host deletion

* Add SetTargetChannel

* Update white logo and add desktop to update runner

* Add fleet-desktop monitoring to orbit

* Define fleet-desktop app exec name

* Fix update runner creation

* Add API test before enabling the My device menu item

Co-authored-by: Zach Wasserman <zach@fleetdm.com>
This commit is contained in:
Lucas Manuel Rodriguez 2022-03-21 14:53:53 -03:00 committed by GitHub
parent e9fd5f46c7
commit ecdfd627b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 901 additions and 149 deletions

View file

@ -0,0 +1,62 @@
name: Generate desktop.app.tar.gz for Orbit
on:
push:
branches:
- main
paths:
# The workflow can be triggered by modifying FLEET_DESKTOP_VERSION env.
- '.github/workflows/generate-desktop-app-tar-gz.yml'
pull_request:
paths:
# The workflow can be triggered by modifying FLEET_DESKTOP_VERSION env.
- '.github/workflows/generate-desktop-app-tar-gz.yml'
workflow_dispatch:
env:
FLEET_DESKTOP_VERSION: 0.0.1
jobs:
release:
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: '^1.17.0'
- name: Checkout
uses: actions/checkout@v2
- name: Import signing keys
env:
APPLE_APPLICATION_CERTIFICATE: ${{ secrets.APPLE_APPLICATION_CERTIFICATE }}
APPLE_APPLICATION_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
echo "$APPLE_APPLICATION_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security import certificate.p12 -k build.keychain -P $APPLE_APPLICATION_CERTIFICATE_PASSWORD -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
security find-identity -vv
rm certificate.p12
- name: Generate desktop.app.tar.gz
env:
AC_USERNAME: ${{ secrets.APPLE_USERNAME }}
AC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
CODESIGN_IDENTITY: 51049B247B25B3119FAE7E9C0CC4375A43E47237
run: |
AC_USERNAME=$AC_USERNAME \
AC_PASSWORD=$AC_PASSWORD \
FLEET_DESKTOP_APPLE_AUTHORITY=$CODESIGN_IDENTITY \
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
make desktop-app-tar-gz
- name: Upload desktop.app.tar.gz
uses: actions/upload-artifact@v2
with:
name: desktop.app.tar.gz
path: desktop.app.tar.gz

3
.gitignore vendored
View file

@ -58,6 +58,7 @@ terraform.tfstate*
# generated installers
fleet-osquery*
desktop.app.tar.gz
# residual files when running the cpe command
cmd/cpe/etagenv
@ -65,4 +66,4 @@ cmd/cpe/cpe*.sqlite
cmd/cpe/cpe*.sqlite.gz
# Testing TUF server
test_tuf
test_tuf

View file

@ -300,3 +300,14 @@ endif
tar xf $(TMP_DIR)/osquery_pkg_expanded/Payload --directory $(TMP_DIR)/osquery_pkg_payload_expanded
tar czf $(out-path)/osqueryd.app.tar.gz -C $(TMP_DIR)/osquery_pkg_payload_expanded/opt/osquery/lib osquery.app
rm -r $(TMP_DIR)
# Build and generate desktop.app.tar.gz bundle.
#
# Usage:
# FLEET_DESKTOP_APPLE_AUTHORITY=foo FLEET_DESKTOP_VERSION=0.0.1 make desktop-app-tar-gz
desktop-app-tar-gz:
ifneq ($(shell uname), Darwin)
@echo "Makefile target desktop-app-tar-gz is only supported on macOS"
@exit 1
endif
go run ./tools/desktop macos

View file

@ -0,0 +1 @@
* (Beta) Introduce `Fleet Desktop` to macOS fleet installer with a tray icon that allows accessing a user device in Fleet.

View file

@ -0,0 +1 @@
* (Beta) Add `--fleet-desktop` flag to fleetctl for `--type=pkg` to generate a Fleet-osquery installer for macOS with "Fleet Desktop" support.

View file

@ -13,7 +13,7 @@ import (
)
const (
defaultFileMode = 0600
defaultFileMode = 0o600
)
func init() {

View file

@ -82,6 +82,12 @@ func packageCommand() *cli.Command {
Value: "stable",
Destination: &opt.OsquerydChannel,
},
&cli.StringFlag{
Name: "desktop-channel",
Usage: "Update channel of desktop to use",
Value: "stable",
Destination: &opt.DesktopChannel,
},
&cli.StringFlag{
Name: "orbit-channel",
Usage: "Update channel of Orbit to use",
@ -118,6 +124,11 @@ func packageCommand() *cli.Command {
Name: "verbose",
Usage: "Log detailed information when building the package",
},
&cli.BoolFlag{
Name: "fleet-desktop",
Usage: "Include the Fleet Desktop Application in the package",
Destination: &opt.Desktop,
},
},
Action: func(c *cli.Context) error {
if opt.FleetURL != "" || opt.EnrollSecret != "" {

View file

@ -22,6 +22,7 @@ import (
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/pkg/open"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/mitchellh/go-ps"
@ -108,10 +109,10 @@ Use the stop and reset subcommands to manage the server and dependencies once st
// Make sure the logs directory is writable, otherwise the Fleet
// server errors on startup. This can be a problem when running on
// Linux with a non-root user inside the container.
if err := os.Chmod(filepath.Join(previewDir, "logs"), 0777); err != nil {
if err := os.Chmod(filepath.Join(previewDir, "logs"), 0o777); err != nil {
return fmt.Errorf("make logs writable: %w", err)
}
if err := os.Chmod(filepath.Join(previewDir, "vulndb"), 0777); err != nil {
if err := os.Chmod(filepath.Join(previewDir, "vulndb"), 0o777); err != nil {
return fmt.Errorf("make vulndb writable: %w", err)
}
@ -269,7 +270,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
return fmt.Errorf("wait for current host: %w", err)
}
if err := openBrowser("http://localhost:1337/previewlogin"); err != nil {
if err := open.Browser("http://localhost:1337/previewlogin"); err != nil {
fmt.Println("Automatic browser open failed. Please navigate to http://localhost:1337/previewlogin.")
}
@ -286,7 +287,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
return errors.New("Failed to run docker-compose")
}
} else {
if err := openBrowser("http://localhost:1337/previewlogin"); err != nil {
if err := open.Browser("http://localhost:1337/previewlogin"); err != nil {
fmt.Println("Automatic browser open failed. Please navigate to http://localhost:1337/previewlogin.")
}
}
@ -582,7 +583,7 @@ func previewResetCommand() *cli.Command {
func storePidFile(destDir string, pid int) error {
pidFilePath := path.Join(destDir, "orbit.pid")
err := os.WriteFile(pidFilePath, []byte(fmt.Sprint(pid)), os.FileMode(0644))
err := os.WriteFile(pidFilePath, []byte(fmt.Sprint(pid)), os.FileMode(0o644))
if err != nil {
return fmt.Errorf("error writing pidfile %s: %s", pidFilePath, err)
}
@ -638,12 +639,8 @@ func downloadOrbitAndStart(destDir, enrollSecret, address, orbitChannel, osquery
}
// Override default channels with the provided values.
orbit := updateOpt.Targets["orbit"]
orbit.Channel = orbitChannel
updateOpt.Targets["orbit"] = orbit
osqueryd := updateOpt.Targets["osqueryd"]
osqueryd.Channel = osquerydChannel
updateOpt.Targets["osqueryd"] = osqueryd
updateOpt.Targets.SetTargetChannel("orbit", orbitChannel)
updateOpt.Targets.SetTargetChannel("osqueryd", osquerydChannel)
updateOpt.RootDirectory = destDir
@ -712,20 +709,3 @@ func killFromPIDFile(destDir string, pidFileName string, expectedExecName string
}
return nil
}
func openBrowser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
case "darwin":
cmd = exec.Command("open", url)
default: // xdg-open is available on most Linux-y systems
cmd = exec.Command("xdg-open", url)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to open in browser: %w", err)
}
return nil
}

View file

@ -267,7 +267,7 @@ func TestUpdatesIntegration(t *testing.T) {
require.NoError(t, err)
other, err := updater.Get("other")
require.NoError(t, err)
require.Equal(t, filepath.Base(other), filepath.Base(testPath))
require.Equal(t, filepath.Base(other.ExecPath), filepath.Base(testPath))
repo, err = openRepo(tmpDir)
require.NoError(t, err)
@ -342,12 +342,12 @@ func TestUpdatesIntegration(t *testing.T) {
// Remove the old other copy first
o, err := updater.Get("other")
require.NoError(t, err)
require.NoError(t, os.RemoveAll(filepath.Join(filepath.Dir(o), "other.app.tar.gz")))
require.NoError(t, os.RemoveAll(filepath.Join(filepath.Dir(o), filepath.Base(testPath))))
require.NoError(t, os.RemoveAll(filepath.Join(filepath.Dir(o.ExecPath), "other.app.tar.gz")))
require.NoError(t, os.RemoveAll(filepath.Join(filepath.Dir(o.ExecPath), filepath.Base(testPath))))
o2, err := updater.Get("other")
require.NoError(t, err)
require.Equal(t, o, o2)
_, err = os.Stat(o2)
_, err = os.Stat(o2.ExecPath)
require.NoError(t, err)
// Update client should be able to initialize with new root

4
go.mod
View file

@ -29,6 +29,7 @@ require (
github.com/facebookincubator/nvdtools v0.1.4
github.com/fatih/color v1.12.0
github.com/fleetdm/goose v0.0.0-20220214194029-91b5e5eb8e77
github.com/getlantern/systray v1.2.0
github.com/getsentry/sentry-go v0.12.0
github.com/ghodss/yaml v1.0.0
github.com/go-kit/kit v0.9.0
@ -37,7 +38,7 @@ require (
github.com/gocolly/colly v1.2.0
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/gomodule/redigo v1.8.5
github.com/google/go-cmp v0.5.6
github.com/google/go-cmp v0.5.7
github.com/google/go-github/v37 v37.0.0
github.com/google/uuid v1.3.0
github.com/goreleaser/goreleaser v1.1.0
@ -77,6 +78,7 @@ require (
github.com/rs/zerolog v1.20.0
github.com/russellhaering/goxmldsig v1.1.0
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/shirou/gopsutil/v3 v3.22.2
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cast v1.3.1
github.com/spf13/cobra v1.2.1

36
go.sum
View file

@ -402,6 +402,20 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.0 h1:MsAdOcmOnm4V+r3HFONDszdZeoj7E3q2dEvsPdsxXtI=
github.com/getlantern/systray v1.2.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -447,6 +461,8 @@ github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
@ -543,8 +559,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM=
github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4=
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
@ -819,6 +836,8 @@ github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/macadmins/osquery-extension v0.0.5 h1:bF0xgFRU5oYU4BF5xwOCnQIi70RxeoJN8vzrDWv5CCU=
github.com/macadmins/osquery-extension v0.0.5/go.mod h1:GJLCEZWld1jM8fwLQJ4xixl8XkvWjWgQugRXFvlc/FA=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -936,6 +955,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
github.com/open-policy-agent/opa v0.24.0 h1:fnGOIux+TTGZsC0du1bRBtV8F+KPN55Hks12uE3Fq3E=
github.com/open-policy-agent/opa v0.24.0/go.mod h1:qEyD/i8j+RQettHGp4f86yjrjvv+ZYia+JHCMv2G7wA=
github.com/opencensus-integrations/ocsql v0.1.1/go.mod h1:ozPYpNVBHZsX33jfoQPO5TlI5lqh0/3R36kirEqJKAM=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/oschwald/geoip2-golang v1.6.1 h1:GKxT3yaWWNXSb7vj6D7eoJBns+lGYgx08QO0UcNm0YY=
github.com/oschwald/geoip2-golang v1.6.1/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
@ -961,6 +982,8 @@ github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUI
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
@ -1029,6 +1052,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@ -1110,6 +1135,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@ -1158,6 +1187,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
@ -1455,6 +1486,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1479,10 +1511,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View file

@ -0,0 +1,107 @@
package main
import (
"crypto/tls"
_ "embed"
"fmt"
"log"
"net/http"
"net/url"
"os"
"time"
"github.com/fleetdm/fleet/v4/pkg/open"
"github.com/getlantern/systray"
)
//go:embed icon_white.png
var icoBytes []byte
func main() {
// Our TUF provided targets must support launching with "--help".
if len(os.Args) > 1 && os.Args[1] == "--help" {
fmt.Println("Fleet Desktop application executable")
return
}
devURL := os.Getenv("FLEET_DESKTOP_DEVICE_URL")
if devURL == "" {
log.Println("missing URL environment FLEET_DESKTOP_DEVICE_URL")
os.Exit(1)
}
deviceURL, err := url.Parse(devURL)
if err != nil {
log.Printf("invalid URL argument: %s\n", err)
os.Exit(1)
}
devTestPath := os.Getenv("FLEET_DESKTOP_DEVICE_API_TEST_PATH")
if devTestPath == "" {
log.Println("missing URL environment FLEET_DESKTOP_DEVICE_API_TEST_PATH")
os.Exit(1)
}
devTestURL := *deviceURL
devTestURL.Path = devTestPath
onReady := func() {
log.Println("ready")
systray.SetIcon(icoBytes)
systray.SetTooltip("Fleet Device Management Menu.")
myDeviceItem := systray.AddMenuItem("Initializing...", "")
myDeviceItem.Disable()
transparencyItem := systray.AddMenuItem("Transparency", "")
// Perform API test call to enable the "My device" item as soon
// as the device auth token is registered by Fleet.
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
tr := http.DefaultTransport.(*http.Transport)
if os.Getenv("FLEET_DESKTOP_INSECURE") != "" {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
client := &http.Client{
Transport: tr,
}
for {
resp, err := client.Get(devTestURL.String())
if err != nil {
// To ease troubleshooting we set the tooltip as the error.
myDeviceItem.SetTooltip(err.Error())
log.Printf("get device URL: %s", err)
} else {
resp.Body.Close()
if resp.StatusCode == http.StatusOK {
myDeviceItem.SetTitle("My device")
myDeviceItem.Enable()
myDeviceItem.SetTooltip("")
return
}
}
<-ticker.C
}
}()
go func() {
for {
select {
case <-myDeviceItem.ClickedCh:
if err := open.Browser(deviceURL.String()); err != nil {
log.Printf("open browser my device: %s", err)
}
case <-transparencyItem.ClickedCh:
if err := open.Browser("https://fleetdm.com/transparency"); err != nil {
log.Printf("open browser transparency: %s", err)
}
}
}
}()
}
onExit := func() {
log.Println("exit")
}
systray.Run(onReady, onExit)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

View file

@ -9,6 +9,7 @@ import (
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"
@ -22,11 +23,13 @@ import (
"github.com/fleetdm/fleet/v4/orbit/pkg/update/filestore"
"github.com/fleetdm/fleet/v4/pkg/certificate"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/open"
"github.com/fleetdm/fleet/v4/pkg/secure"
"github.com/google/uuid"
"github.com/oklog/run"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
gopsutil_process "github.com/shirou/gopsutil/v3/process"
"github.com/urfave/cli/v2"
"gopkg.in/natefinch/lumberjack.v2"
)
@ -96,6 +99,12 @@ func main() {
Value: "stable",
EnvVars: []string{"ORBIT_ORBIT_CHANNEL"},
},
&cli.StringFlag{
Name: "desktop-channel",
Usage: "Update channel of Fleet Desktop to use",
Value: "stable",
EnvVars: []string{"ORBIT_DESKTOP_CHANNEL"},
},
&cli.BoolFlag{
Name: "disable-updates",
Usage: "Disables auto updates",
@ -123,6 +132,11 @@ func main() {
Name: "dev-darwin-legacy-targets",
Usage: "Use darwin legacy target (flag only used on darwin)",
},
&cli.BoolFlag{
Name: "fleet-desktop",
Usage: "Launch Fleet Desktop application (flag currently only used on darwin)",
EnvVars: []string{"ORBIT_FLEET_DESKTOP"},
},
}
app.Action = func(c *cli.Context) error {
if c.Bool("version") {
@ -193,7 +207,7 @@ func main() {
localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json"))
if err != nil {
log.Fatal().Err(err).Msg("failed to create local metadata store")
log.Fatal().Err(err).Msg("create local metadata store")
}
opt := update.DefaultOptions
@ -202,13 +216,15 @@ func main() {
opt.Targets = update.DarwinLegacyTargets
}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
opt.Targets["desktop"] = update.DesktopMacOSTarget
// Override default channel with the provided value.
opt.Targets.SetTargetChannel("desktop", c.String("desktop-channel"))
}
// Override default channels with the provided values.
orbit := opt.Targets["orbit"]
orbit.Channel = c.String("orbit-channel")
opt.Targets["orbit"] = orbit
osqueryd := opt.Targets["osqueryd"]
osqueryd.Channel = c.String("osqueryd-channel")
opt.Targets["osqueryd"] = osqueryd
opt.Targets.SetTargetChannel("orbit", c.String("orbit-channel"))
opt.Targets.SetTargetChannel("osqueryd", c.String("osqueryd-channel"))
opt.RootDirectory = c.String("root-dir")
opt.ServerURL = c.String("update-url")
@ -216,8 +232,9 @@ func main() {
opt.InsecureTransport = c.Bool("insecure")
var (
updater *update.Updater
osquerydPath string
updater *update.Updater
osquerydPath string
desktopAppPath string
)
// NOTE: When running in dev-mode, even if `disable-updates` is set,
@ -225,21 +242,35 @@ func main() {
if !c.Bool("disable-updates") || c.Bool("dev-mode") {
updater, err = update.New(opt)
if err != nil {
return fmt.Errorf("failed to create updater: %w", err)
return fmt.Errorf("create updater: %w", err)
}
if err := updater.UpdateMetadata(); err != nil {
log.Info().Err(err).Msg("failed to update metadata. using saved metadata.")
log.Info().Err(err).Msg("update metadata. using saved metadata.")
}
osquerydPath, err = updater.Get("osqueryd")
osquerydLocalTarget, err := updater.Get("osqueryd")
if err != nil {
return fmt.Errorf("failed to get osqueryd target: %w", err)
return fmt.Errorf("get osqueryd target: %w", err)
}
osquerydPath = osquerydLocalTarget.ExecPath
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
fleetDesktopLocalTarget, err := updater.Get("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
}
desktopAppPath = fleetDesktopLocalTarget.DirPath
}
} else {
log.Info().Msg("running with auto updates disabled")
updater = update.NewDisabled(opt)
osquerydPath, err = updater.ExecutableLocalPath("osqueryd")
if err != nil {
log.Fatal().Err(err).Msg("failed to locate osqueryd")
log.Fatal().Err(err).Msg("locate osqueryd")
}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
desktopAppPath, err = updater.DirLocalPath("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
}
}
}
@ -251,7 +282,7 @@ func main() {
}
if err := os.RemoveAll(path); err != nil {
log.Info().Err(err).Msg("failed to remove .old")
log.Info().Err(err).Msg("remove .old")
return nil
}
log.Debug().Str("path", path).Msg("cleaned up old")
@ -264,9 +295,13 @@ func main() {
var g run.Group
if !c.Bool("disable-updates") {
targets := []string{"orbit", "osqueryd"}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
targets = append(targets, "desktop")
}
updateRunner, err := update.NewRunner(updater, update.RunnerOptions{
CheckInterval: 10 * time.Second,
Targets: []string{"orbit", "osqueryd"},
Targets: targets,
})
if err != nil {
return err
@ -414,7 +449,7 @@ func main() {
// Create an osquery runner with the provided options.
r, err := osquery.NewRunner(osquerydPath, options...)
if err != nil {
return fmt.Errorf("failed to create osquery runner: %w", err)
return fmt.Errorf("create osquery runner: %w", err)
}
g.Add(r.Execute, r.Interrupt)
@ -423,6 +458,11 @@ func main() {
}))
g.Add(ext.Execute, ext.Interrupt)
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
desktopRunner := newDesktopRunner(desktopAppPath, fleetURL, deviceAuthToken, c.Bool("insecure"))
g.Add(desktopRunner.actor())
}
// Install a signal handler
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -440,6 +480,71 @@ func main() {
}
}
type desktopRunner struct {
appPath string
fleetURL string
deviceAuthToken string
insecure bool
done chan struct{}
}
func newDesktopRunner(appPath, fleetURL, deviceAuthToken string, insecure bool) *desktopRunner {
return &desktopRunner{
appPath: appPath,
fleetURL: fleetURL,
deviceAuthToken: deviceAuthToken,
insecure: insecure,
done: make(chan struct{}),
}
}
func (d *desktopRunner) actor() (func() error, func(error)) {
return d.execute, d.interrupt
}
func (d *desktopRunner) execute() error {
log.Info().Str("path", d.appPath).Msg("opening")
url, err := url.Parse(d.fleetURL)
if err != nil {
return fmt.Errorf("invalid fleet-url: %w", err)
}
url.Path = path.Join(url.Path, "device", d.deviceAuthToken)
opts := []open.AppOption{
open.AppWithEnv("FLEET_DESKTOP_DEVICE_URL", url.String()),
open.AppWithEnv("FLEET_DESKTOP_DEVICE_API_TEST_PATH", path.Join("api", "latest", "fleet", "device", d.deviceAuthToken)),
}
if d.insecure {
opts = append(opts, open.AppWithEnv("FLEET_DESKTOP_INSECURE", "1"))
}
if err := open.App(d.appPath, opts...); err != nil {
return fmt.Errorf("open desktop app: %w", err)
}
// Monitor the fleet-desktop application.
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-d.done:
return nil
case <-ticker.C:
if _, err := getProcessByName(constant.DesktopAppExecName); err != nil {
log.Err(err).Msg("desktopRunner.execute exit")
return fmt.Errorf("get desktop process: %w", err)
}
}
}
}
func (d *desktopRunner) interrupt(err error) {
log.Debug().Err(err).Msg("interrupt desktopRunner")
defer close(d.done)
if err := killProcessByName(constant.DesktopAppExecName); err != nil {
log.Error().Err(err).Msg("killProcess")
}
}
func loadOrGenerateToken(rootDir string) (string, error) {
filePath := filepath.Join(rootDir, "identifier")
id, err := ioutil.ReadFile(filePath)
@ -460,6 +565,42 @@ func loadOrGenerateToken(rootDir string) (string, error) {
}
}
func killProcessByName(name string) error {
foundProcess, err := getProcessByName(name)
if err != nil {
return fmt.Errorf("get process: %w", err)
}
if err := foundProcess.Kill(); err != nil {
return fmt.Errorf("kill process %d: %w", foundProcess.Pid, err)
}
return nil
}
var errProcessNotFound = errors.New("process not found")
func getProcessByName(name string) (*gopsutil_process.Process, error) {
processes, err := gopsutil_process.Processes()
if err != nil {
return nil, err
}
var foundProcess *gopsutil_process.Process
for _, process := range processes {
processName, err := process.Name()
if err != nil {
log.Debug().Err(err).Int32("pid", process.Pid).Msg("get process name")
continue
}
if processName == name {
foundProcess = process
break
}
}
if foundProcess == nil {
return nil, errProcessNotFound
}
return foundProcess, nil
}
var versionCommand = &cli.Command{
Name: "version",
Usage: "Get the orbit version",

View file

@ -53,10 +53,8 @@ var shellCommand = &cli.Command{
// Initialize updater and get expected version
opt := update.DefaultOptions
// Override default channels with the provided values.
osqueryd := opt.Targets["osqueryd"]
osqueryd.Channel = c.String("osqueryd-channel")
opt.Targets["osqueryd"] = osqueryd
// Override default channel with the provided value.
opt.Targets.SetTargetChannel("osqueryd", c.String("osqueryd-channel"))
opt.RootDirectory = c.String("root-dir")
opt.ServerURL = c.String("update-url")
@ -70,10 +68,11 @@ var shellCommand = &cli.Command{
if err := updater.UpdateMetadata(); err != nil {
log.Info().Err(err).Msg("failed to update metadata. using saved metadata.")
}
osquerydPath, err := updater.Get("osqueryd")
osquerydLocalTarget, err := updater.Get("osqueryd")
if err != nil {
return err
}
osquerydPath := osquerydLocalTarget.ExecPath
var g run.Group

View file

@ -5,4 +5,6 @@ const (
DefaultDirMode = 0o755
// DefaultFileMode is the default file mode to apply to created files.
DefaultFileMode = 0o600
// DesktopAppExecName is the name of Fleet's Desktop executable.
DesktopAppExecName = "fleet-desktop"
)

View file

@ -34,19 +34,14 @@ func buildNFPM(opt Options, pkger nfpm.Packager) (string, error) {
}
// Initialize autoupdate metadata
updateOpt := update.DefaultOptions
updateOpt.RootDirectory = orbitRoot
updateOpt.Targets = update.LinuxTargets
// Override default channels with the provided values.
orbit := updateOpt.Targets["orbit"]
orbit.Channel = opt.OrbitChannel
updateOpt.Targets["orbit"] = orbit
osqueryd := updateOpt.Targets["osqueryd"]
osqueryd.Channel = opt.OsquerydChannel
updateOpt.Targets["osqueryd"] = osqueryd
updateOpt.Targets.SetTargetChannel("orbit", opt.OrbitChannel)
updateOpt.Targets.SetTargetChannel("osqueryd", opt.OsquerydChannel)
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {

View file

@ -45,13 +45,15 @@ func BuildPkg(opt Options) (string, error) {
updateOpt.ServerURL = opt.UpdateURL
updateOpt.Targets = update.DarwinTargets
if opt.Desktop {
updateOpt.Targets["desktop"] = update.DesktopMacOSTarget
// Override default channel with the provided value.
updateOpt.Targets.SetTargetChannel("desktop", opt.DesktopChannel)
}
// Override default channels with the provided values.
orbit := updateOpt.Targets["orbit"]
orbit.Channel = opt.OrbitChannel
updateOpt.Targets["orbit"] = orbit
osqueryd := updateOpt.Targets["osqueryd"]
osqueryd.Channel = opt.OsquerydChannel
updateOpt.Targets["osqueryd"] = osqueryd
updateOpt.Targets.SetTargetChannel("orbit", opt.OrbitChannel)
updateOpt.Targets.SetTargetChannel("osqueryd", opt.OsquerydChannel)
if opt.UpdateRoots != "" {
updateOpt.RootKeys = opt.UpdateRoots
@ -160,7 +162,7 @@ func writeScripts(opt Options, rootPath string) error {
return fmt.Errorf("execute template: %w", err)
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0744); err != nil {
if err := ioutil.WriteFile(path, contents.Bytes(), 0o744); err != nil {
return fmt.Errorf("write file: %w", err)
}
@ -179,7 +181,7 @@ func writeLaunchd(opt Options, rootPath string) error {
return fmt.Errorf("execute template: %w", err)
}
if err := ioutil.WriteFile(path, contents.Bytes(), 0644); err != nil {
if err := ioutil.WriteFile(path, contents.Bytes(), 0o644); err != nil {
return fmt.Errorf("write file: %w", err)
}
@ -209,7 +211,7 @@ func writeCertificate(opt Options, orbitRoot string) error {
// Fleet TLS certificate
dstPath := filepath.Join(orbitRoot, "fleet.pem")
if err := file.Copy(opt.FleetCertificate, dstPath, 0644); err != nil {
if err := file.Copy(opt.FleetCertificate, dstPath, 0o644); err != nil {
return fmt.Errorf("write orbit: %w", err)
}
@ -299,7 +301,7 @@ func xarBom(opt Options, rootPath string) error {
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)
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0o755)
if err != nil {
return fmt.Errorf("open dst: %w", err)
}

View file

@ -94,6 +94,10 @@ var macosLaunchdTemplate = template.Must(template.New("").Option("missingkey=err
<string>{{ .OsquerydChannel }}</string>
<key>ORBIT_UPDATE_URL</key>
<string>{{ .UpdateURL }}</string>
{{- if .Desktop }}
<key>ORBIT_FLEET_DESKTOP</key>
<string>true</string>
{{- end }}
</dict>
<key>KeepAlive</key>
<true/>

View file

@ -45,6 +45,8 @@ type Options struct {
OrbitChannel string
// OsquerydChannel is the update channel to use for Osquery (osqueryd).
OsquerydChannel string
// DesktopChannel is the update channel to use for the Fleet Desktop application.
DesktopChannel string
// UpdateURL is the base URL of the update server (TUF repository).
UpdateURL string
// UpdateRoots is the root JSON metadata for update server (TUF repository).
@ -53,6 +55,8 @@ type Options struct {
OsqueryFlagfile string
// Debug determines whether to enable debug logging for the agent.
Debug bool
// Desktop determines whether to package the Fleet Desktop application.
Desktop bool
}
func initializeTempDir() (string, error) {
@ -62,7 +66,7 @@ func initializeTempDir() (string, error) {
return "", fmt.Errorf("failed to create temp dir: %w", err)
}
if err := os.Chmod(tmpDir, 0755); err != nil {
if err := os.Chmod(tmpDir, 0o755); err != nil {
_ = os.RemoveAll(tmpDir)
return "", fmt.Errorf("change temp directory permissions: %w", err)
}
@ -77,6 +81,9 @@ type UpdatesData struct {
OsquerydPath string
OsquerydVersion string
DesktopPath string
DesktopVersion string
}
func (u UpdatesData) String() string {
@ -101,10 +108,12 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
if err := updater.UpdateMetadata(); err != nil {
return nil, fmt.Errorf("failed to update metadata: %w", err)
}
osquerydPath, err := updater.Get("osqueryd")
osquerydLocalTarget, err := updater.Get("osqueryd")
if err != nil {
return nil, fmt.Errorf("failed to get osqueryd: %w", err)
}
osquerydPath := osquerydLocalTarget.ExecPath
osquerydMeta, err := updater.Lookup("osqueryd")
if err != nil {
return nil, fmt.Errorf("failed to get osqueryd metadata: %w", err)
@ -116,10 +125,12 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
if err := json.Unmarshal(*osquerydMeta.Custom, &osquerydCustom); err != nil {
return nil, fmt.Errorf("failed to get osqueryd version: %w", err)
}
orbitPath, err := updater.Get("orbit")
orbitLocalTarget, err := updater.Get("orbit")
if err != nil {
return nil, fmt.Errorf("failed to get orbit: %w", err)
}
orbitPath := orbitLocalTarget.ExecPath
orbitMeta, err := updater.Lookup("orbit")
if err != nil {
return nil, fmt.Errorf("failed to get orbit metadata: %w", err)
@ -129,6 +140,25 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
return nil, fmt.Errorf("failed to get orbit version: %w", err)
}
var (
desktopPath string
desktopCustom custom
)
if _, ok := updateOpt.Targets["desktop"]; ok {
desktopLocalTarget, err := updater.Get("desktop")
if err != nil {
return nil, fmt.Errorf("failed to get desktop: %w", err)
}
desktopPath = desktopLocalTarget.ExecPath
desktopMeta, err := updater.Lookup("desktop")
if err != nil {
return nil, fmt.Errorf("failed to get orbit metadata: %w", err)
}
if err := json.Unmarshal(*desktopMeta.Custom, &desktopCustom); err != nil {
return nil, fmt.Errorf("failed to get orbit version: %w", err)
}
}
if devBuildPath := os.Getenv("FLEETCTL_ORBIT_DEV_BUILD_PATH"); devBuildPath != "" {
updater.CopyDevBuild("orbit", devBuildPath)
}
@ -139,6 +169,9 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
OsquerydPath: osquerydPath,
OsquerydVersion: osquerydCustom.Version,
DesktopPath: desktopPath,
DesktopVersion: desktopCustom.Version,
}, nil
}
@ -149,7 +182,7 @@ func writeSecret(opt Options, orbitRoot string) error {
return fmt.Errorf("mkdir: %w", err)
}
if err := ioutil.WriteFile(path, []byte(opt.EnrollSecret), 0600); err != nil {
if err := ioutil.WriteFile(path, []byte(opt.EnrollSecret), 0o600); err != nil {
return fmt.Errorf("write file: %w", err)
}
@ -183,7 +216,7 @@ var osqueryCerts []byte
func writeOsqueryCertPEM(opt Options, orbitRoot string) error {
dstPath := filepath.Join(orbitRoot, "certs.pem")
if err := ioutil.WriteFile(dstPath, osqueryCerts, 0644); err != nil {
if err := ioutil.WriteFile(dstPath, osqueryCerts, 0o644); err != nil {
return fmt.Errorf("write file: %w", err)
}

View file

@ -42,12 +42,8 @@ func BuildMSI(opt Options) (string, error) {
updateOpt.Targets = update.WindowsTargets
// Override default channels with the provided values.
orbit := updateOpt.Targets["orbit"]
orbit.Channel = opt.OrbitChannel
updateOpt.Targets["orbit"] = orbit
osqueryd := updateOpt.Targets["osqueryd"]
osqueryd.Channel = opt.OsquerydChannel
updateOpt.Targets["osqueryd"] = osqueryd
updateOpt.Targets.SetTargetChannel("orbit", opt.OrbitChannel)
updateOpt.Targets.SetTargetChannel("osqueryd", opt.OsquerydChannel)
updateOpt.ServerURL = opt.UpdateURL
if opt.UpdateRoots != "" {

View file

@ -1,5 +1,7 @@
package update
import "github.com/fleetdm/fleet/v4/orbit/pkg/constant"
// DefaultOptions are the default options to use when creating an update
// client.
var DefaultOptions = defaultOptions
@ -57,4 +59,11 @@ var (
TargetFile: "osqueryd.exe",
},
}
DesktopMacOSTarget = TargetInfo{
Platform: "macos",
Channel: "stable",
TargetFile: "desktop.app.tar.gz",
ExtractedExecSubPath: []string{"Fleet Desktop.app", "Contents", "MacOS", constant.DesktopAppExecName},
}
)

View file

@ -54,7 +54,7 @@ func NewRunner(updater *Updater, opt RunnerOptions) (*Runner, error) {
if err != nil {
return nil, fmt.Errorf("failed to get local path for %s: %w", target, err)
}
_, localHash, err := fileHashes(meta, localTarget.path)
_, localHash, err := fileHashes(meta, localTarget.Path)
if err != nil {
return nil, fmt.Errorf("%s file hash: %w", target, err)
}
@ -135,10 +135,11 @@ func (r *Runner) updateAction() (bool, error) {
}
func (r *Runner) updateTarget(target string) error {
path, err := r.updater.Get(target)
localTarget, err := r.updater.Get(target)
if err != nil {
return fmt.Errorf("get binary: %w", err)
}
path := localTarget.ExecPath
if target != "orbit" {
return nil

View file

@ -66,6 +66,13 @@ type Options struct {
// Targets is a map of target name and its tracking information.
type Targets map[string]TargetInfo
// SetTargetChannel sets the channel of a target in the map.
func (ts Targets) SetTargetChannel(target, channel string) {
t := ts[target]
t.Channel = channel
ts[target] = t
}
// TargetInfo holds all the information to track target updates.
type TargetInfo struct {
// Platform is the target's platform string.
@ -157,53 +164,70 @@ func (u *Updater) repoPath(target string) (string, error) {
return path.Join(target, t.Platform, t.Channel, t.TargetFile), nil
}
// ExecutableLocalPath returns the configured local path of a target.
// ExecutableLocalPath returns the configured executable local path of a target.
func (u *Updater) ExecutableLocalPath(target string) (string, error) {
localTarget, err := u.localTarget(target)
if err != nil {
return "", err
}
return localTarget.execPath, nil
return localTarget.ExecPath, nil
}
// localTarget holds local paths of a target.
// DirLocalPath returns the configured root directory local path of a tar.gz target.
//
// Returns empty for a non tar.gz target.
func (u *Updater) DirLocalPath(target string) (string, error) {
localTarget, err := u.localTarget(target)
if err != nil {
return "", err
}
return localTarget.DirPath, nil
}
// LocalTarget holds local paths of a target.
//
// E.g., for a osqueryd target:
//
// localTarget{
// info: TargetInfo{
// LocalTarget{
// Info: TargetInfo{
// Platform: "macos-app",
// Channel: "stable",
// TargetFile: "osqueryd.app.tar.gz",
// ExtractedExecSubPath: []string{"osquery.app", "Contents", "MacOS", "osqueryd"},
// },
// path: "/local/path/to/osqueryd.app.tar.gz",
// dirPath: "/local/path/to/osqueryd.app",
// execPath: "/local/path/to/osqueryd.app/Contents/MacOS/osqueryd",
// Path: "/local/path/to/osqueryd.app.tar.gz",
// DirPath: "/local/path/to/osqueryd.app",
// ExecPath: "/local/path/to/osqueryd.app/Contents/MacOS/osqueryd",
// }
type localTarget struct {
info TargetInfo
path string
dirPath string // empty for non-tar.gz targets.
execPath string
type LocalTarget struct {
// Info holds the TUF target and package structure info.
Info TargetInfo
// Path holds the location of the target as downloaded from TUF.
Path string
// DirPath holds the path of the extracted target.
//
// DirPath is empty for non-tar.gz targets.
DirPath string
// ExecPath is the path of the executable.
ExecPath string
}
// localPath returns the info and local path of a target.
func (u *Updater) localTarget(target string) (*localTarget, error) {
// localTarget returns the info and local path of a target.
func (u *Updater) localTarget(target string) (*LocalTarget, error) {
t, ok := u.opt.Targets[target]
if !ok {
return nil, fmt.Errorf("unknown target: %s", target)
}
lt := &localTarget{
info: t,
path: filepath.Join(
lt := &LocalTarget{
Info: t,
Path: filepath.Join(
u.opt.RootDirectory, binDir, target, t.Platform, t.Channel, t.TargetFile,
),
}
lt.execPath = lt.path
if strings.HasSuffix(lt.path, ".tar.gz") {
lt.execPath = filepath.Join(append([]string{filepath.Dir(lt.path)}, t.ExtractedExecSubPath...)...)
lt.dirPath = filepath.Join(filepath.Dir(lt.path), lt.info.ExtractedExecSubPath[0])
lt.ExecPath = lt.Path
if strings.HasSuffix(lt.Path, ".tar.gz") {
lt.ExecPath = filepath.Join(append([]string{filepath.Dir(lt.Path)}, t.ExtractedExecSubPath...)...)
lt.DirPath = filepath.Join(filepath.Dir(lt.Path), lt.Info.ExtractedExecSubPath[0])
}
return lt, nil
}
@ -232,76 +256,74 @@ func (u *Updater) Targets() (data.TargetFiles, error) {
return targets, nil
}
// Get returns the local path to the specified target. The target is downloaded
// if it does not yet exist locally or the hash does not match.
func (u *Updater) Get(target string) (string, error) {
// Get downloads (if it doesn't exist) a target and returns its local information.
func (u *Updater) Get(target string) (*LocalTarget, error) {
if target == "" {
return "", errors.New("target is required")
return nil, errors.New("target is required")
}
localTarget, err := u.localTarget(target)
if err != nil {
return "", fmt.Errorf("failed to load local path for target %s: %w", target, err)
return nil, fmt.Errorf("failed to load local path for target %s: %w", target, err)
}
repoPath, err := u.repoPath(target)
if err != nil {
return "", fmt.Errorf("failed to load repository path for target %s: %w", target, err)
return nil, fmt.Errorf("failed to load repository path for target %s: %w", target, err)
}
switch stat, err := os.Stat(localTarget.path); {
switch stat, err := os.Stat(localTarget.Path); {
case err == nil:
if !stat.Mode().IsRegular() {
return "", fmt.Errorf("expected %s to be regular file", localTarget.path)
return nil, fmt.Errorf("expected %s to be regular file", localTarget.Path)
}
meta, err := u.Lookup(target)
if err != nil {
return "", err
return nil, err
}
if err := checkFileHash(meta, localTarget.path); err != nil {
if err := checkFileHash(meta, localTarget.Path); err != nil {
log.Debug().Str("info", err.Error()).Msg("change detected")
if err := u.download(target, repoPath, localTarget.path); err != nil {
return "", fmt.Errorf("download %q: %w", repoPath, err)
if err := u.download(target, repoPath, localTarget.Path); err != nil {
return nil, fmt.Errorf("download %q: %w", repoPath, err)
}
if strings.HasSuffix(localTarget.path, ".tar.gz") {
if err := os.RemoveAll(localTarget.dirPath); err != nil {
return "", fmt.Errorf("failed to remove old extracted dir: %q: %w", localTarget.dirPath, err)
if strings.HasSuffix(localTarget.Path, ".tar.gz") {
if err := os.RemoveAll(localTarget.DirPath); err != nil {
return nil, fmt.Errorf("failed to remove old extracted dir: %q: %w", localTarget.DirPath, err)
}
}
} else {
log.Debug().Str("path", localTarget.path).Str("target", target).Msg("found expected target locally")
log.Debug().Str("path", localTarget.Path).Str("target", target).Msg("found expected target locally")
}
case errors.Is(err, os.ErrNotExist):
log.Debug().Err(err).Msg("stat file")
if err := u.download(target, repoPath, localTarget.path); err != nil {
return "", fmt.Errorf("download %q: %w", repoPath, err)
if err := u.download(target, repoPath, localTarget.Path); err != nil {
return nil, fmt.Errorf("download %q: %w", repoPath, err)
}
default:
return "", fmt.Errorf("stat %q: %w", localTarget.path, err)
return nil, fmt.Errorf("stat %q: %w", localTarget.Path, err)
}
if strings.HasSuffix(localTarget.path, ".tar.gz") {
switch s, err := os.Stat(localTarget.execPath); {
if strings.HasSuffix(localTarget.Path, ".tar.gz") {
s, err := os.Stat(localTarget.ExecPath)
switch {
case err == nil:
if s.IsDir() {
return "", fmt.Errorf("expected executable %q: %w", localTarget.execPath, err)
}
// OK
case errors.Is(err, os.ErrNotExist):
if err := extractTarGz(localTarget.path); err != nil {
return "", fmt.Errorf("extract %q: %w", localTarget.path, err)
if err := extractTarGz(localTarget.Path); err != nil {
return nil, fmt.Errorf("extract %q: %w", localTarget.Path, err)
}
s, err := os.Stat(localTarget.execPath)
s, err = os.Stat(localTarget.ExecPath)
if err != nil {
return "", fmt.Errorf("stat %q: %w", localTarget.execPath, err)
}
if s.IsDir() {
return "", fmt.Errorf("expected executable %q: %w", localTarget.execPath, err)
return nil, fmt.Errorf("stat %q: %w", localTarget.ExecPath, err)
}
default:
return "", fmt.Errorf("stat %q: %w", localTarget.execPath, err)
return nil, fmt.Errorf("stat %q: %w", localTarget.ExecPath, err)
}
if !s.Mode().IsRegular() {
return nil, fmt.Errorf("expected a regular file: %q", localTarget.ExecPath)
}
}
return localTarget.execPath, nil
return localTarget, nil
}
func writeDevWarningBanner(w io.Writer) {
@ -431,7 +453,7 @@ func (u *Updater) checkExec(target, tmpPath string) error {
if err != nil {
return err
}
platformGOOS, err := goosFromPlatform(localTarget.info.Platform)
platformGOOS, err := goosFromPlatform(localTarget.Info.Platform)
if err != nil {
return err
}
@ -446,9 +468,9 @@ func (u *Updater) checkExec(target, tmpPath string) error {
if err := extractTarGz(tmpPath); err != nil {
return fmt.Errorf("extract %q: %w", tmpPath, err)
}
tmpDirPath := filepath.Join(filepath.Dir(tmpPath), localTarget.info.ExtractedExecSubPath[0])
tmpDirPath := filepath.Join(filepath.Dir(tmpPath), localTarget.Info.ExtractedExecSubPath[0])
defer os.RemoveAll(tmpDirPath)
tmpPath = filepath.Join(append([]string{filepath.Dir(tmpPath)}, localTarget.info.ExtractedExecSubPath...)...)
tmpPath = filepath.Join(append([]string{filepath.Dir(tmpPath)}, localTarget.Info.ExtractedExecSubPath...)...)
}
// Note that this would fail for any binary that returns nonzero for --help.

View file

@ -3,4 +3,5 @@
sudo launchctl stop com.fleetdm.orbit
sudo launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist
sudo pkill fleet-desktop || true
sudo rm -rf /Library/LaunchDaemons/com.fleetdm.orbit.plist /var/lib/orbit/ /usr/local/bin/orbit /var/log/orbit

84
pkg/open/open.go Normal file
View file

@ -0,0 +1,84 @@
package open
import (
"fmt"
"os"
"os/exec"
"runtime"
)
// Browser opens the default browser at the given url and returns.
func Browser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
case "darwin":
cmd = exec.Command("open", url)
default: // xdg-open is available on most Linux-y systems
cmd = exec.Command("xdg-open", url)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("open in browser: %w", err)
}
return nil
}
type appOpts struct {
env [][2]string
stderrPath string
}
// AppOption are options to use when opening the application with App.
type AppOption func(*appOpts)
// AppWithEnv sets the environment for opening an application.
func AppWithEnv(name, value string) AppOption {
return func(a *appOpts) {
a.env = append(a.env, [2]string{name, value})
}
}
// AppWithStderr sets the stderr destination for the application.
func AppWithStderr(path string) AppOption {
return func(a *appOpts) {
a.stderrPath = path
}
}
// App opens an application at path with the default application.
func App(path string, opts ...AppOption) error {
var o appOpts
for _, fn := range opts {
fn(&o)
}
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("stat path %q: %w", path, err)
}
switch runtime.GOOS {
case "darwin":
if !info.IsDir() {
return fmt.Errorf("path is not an .app directory: %s", path)
}
var arg []string
if o.stderrPath != "" {
arg = append(arg, "--stderr", o.stderrPath)
}
for _, nv := range o.env {
arg = append(arg, "--env", fmt.Sprintf("%s=%s", nv[0], nv[1]))
}
arg = append(arg, path)
cmd := exec.Command("open", arg...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("open path: %w", err)
}
return nil
default:
return fmt.Errorf("platform unsupported: %s", runtime.GOOS)
}
}

View file

@ -335,6 +335,7 @@ func (ds *Datastore) DeleteHost(ctx context.Context, hid uint) error {
"policy_membership",
"host_mdm",
"host_munki_info",
"host_device_auth",
}
for _, table := range hostRefs {

234
tools/desktop/desktop.go Normal file
View file

@ -0,0 +1,234 @@
package main
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/pkg/secure"
"github.com/kolide/kit/version"
"github.com/rs/zerolog"
zlog "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
func main() {
app := createApp(os.Stdin, os.Stdout, exitErrHandler)
app.Run(os.Args)
}
// exitErrHandler implements cli.ExitErrHandlerFunc. If there is an error, prints it to stderr and exits with status 1.
func exitErrHandler(c *cli.Context, err error) {
if err == nil {
return
}
fmt.Fprintf(c.App.ErrWriter, "Error: %+v\n", err)
cli.OsExiter(1)
}
func createApp(reader io.Reader, writer io.Writer, exitErrHandler cli.ExitErrHandlerFunc) *cli.App {
app := cli.NewApp()
app.Name = "desktop"
app.Usage = "Tool to generate the Fleet Desktop application"
app.ExitErrHandler = exitErrHandler
cli.VersionPrinter = func(c *cli.Context) {
version.PrintFull()
}
app.Reader = reader
app.Writer = writer
app.ErrWriter = writer
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "version",
Usage: "Version of the Fleet Desktop application",
EnvVars: []string{"FLEET_DESKTOP_VERSION"},
},
&cli.BoolFlag{
Name: "verbose",
Usage: "Log detailed information when building the application",
EnvVars: []string{"FLEET_DESKTOP_VERBOSE"},
},
}
app.Commands = []*cli.Command{
macos(),
}
return app
}
func macos() *cli.Command {
return &cli.Command{
Name: "macos",
Usage: "Creates the Fleet Desktop Application for macOS",
Description: "Builds and signs the Fleet Desktop .app bundle for macOS",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "authority",
Usage: "Authority to use on the codesign invocation (if not set, app is not signed)",
EnvVars: []string{"FLEET_DESKTOP_APPLE_AUTHORITY"},
},
},
Action: func(c *cli.Context) error {
if !c.Bool("verbose") {
zlog.Logger = zerolog.Nop()
}
return createMacOSApp(c.String("version"), c.String("authority"))
},
}
}
func createMacOSApp(version, authority string) error {
const (
appDir = "Fleet Desktop.app"
bundleIdentifier = "com.fleetdm.desktop"
// infoPList is the Info.plist file to use for the macOS .app bundle.
//
// - NSHighResolutionCapable=true: avoid having a blurry icon and text.
// - LSUIElement=1: avoid showing the app on the Dock.
infoPList = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>fleet-desktop</string>
<key>CFBundleIdentifier</key>
<string>%s</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>fleet-desktop</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>%s</string>
<key>CFBundleVersion</key>
<string>%s</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>LSUIElement</key>
<string>1</string>
</dict>
</plist>
`
)
if runtime.GOOS != "darwin" {
return errors.New(`the "Fleet Desktop" macOS app can only be created from macOS`)
}
defer os.RemoveAll(appDir)
contentsDir := filepath.Join(appDir, "Contents")
macOSDir := filepath.Join(contentsDir, "MacOS")
if err := secure.MkdirAll(macOSDir, constant.DefaultDirMode); err != nil {
return fmt.Errorf("create directory %q: %w", macOSDir, err)
}
infoFile := filepath.Join(contentsDir, "Info.plist")
infoPListContents := fmt.Sprintf(infoPList, bundleIdentifier, version, version)
if err := ioutil.WriteFile(infoFile, []byte(infoPListContents), 0o644); err != nil {
return fmt.Errorf("create Info.plist file %q: %w", infoFile, err)
}
/* #nosec G204 -- arguments are actually well defined */
buildExec := exec.Command("go", "build", "-o", filepath.Join(macOSDir, constant.DesktopAppExecName), "./"+filepath.Join("orbit", "cmd", "desktop"))
buildExec.Env = append(os.Environ(), "CGO_ENABLED=1")
buildExec.Stderr = os.Stderr
buildExec.Stdout = os.Stdout
zlog.Info().Str("command", buildExec.String()).Msg("Build fleet-desktop executable")
if err := buildExec.Run(); err != nil {
return fmt.Errorf("compile application: %w", err)
}
if authority != "" {
codeSign := exec.Command("codesign", "-s", authority, "-i", bundleIdentifier, "-f", "-v", "--timestamp", "--options", "runtime", appDir)
zlog.Info().Str("command", codeSign.String()).Msg("Sign Fleet Desktop.app")
codeSign.Stderr = os.Stderr
codeSign.Stdout = os.Stdout
if err := codeSign.Run(); err != nil {
return fmt.Errorf("sign application: %w", err)
}
}
const tarGzName = "desktop.app.tar.gz"
if err := compressDir(tarGzName, appDir); err != nil {
return fmt.Errorf("compress app: %w", err)
}
fmt.Printf("Generated %s successfully.\n", tarGzName)
return nil
}
func compressDir(outPath, dirPath string) error {
out, err := secure.OpenFile(outPath, os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("open archive: %w", err)
}
defer out.Close()
gw := gzip.NewWriter(out)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
if err := filepath.Walk(dirPath, func(file string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return err
}
// From https://golang.org/src/archive/tar/common.go?#L626
//
// "Since fs.FileInfo's Name method only returns the base name of
// the file it describes, it may be necessary to modify Header.Name
// to provide the full path name of the file."
header.Name = filepath.ToSlash(file)
if err := tw.WriteHeader(header); err != nil {
return err
}
if !fi.IsDir() {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(tw, f); err != nil {
return err
}
}
return nil
}); err != nil {
return fmt.Errorf("walk directory: %w", err)
}
if err := tw.Close(); err != nil {
return fmt.Errorf("close tar: %w", err)
}
if err := gw.Close(); err != nil {
return fmt.Errorf("close gzip: %w", err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("close file: %w", err)
}
return nil
}

View file

@ -19,7 +19,7 @@ To add new updates (osqueryd or orbit), use `push_target.sh`.
E.g. to add a new version of `orbit` for Windows:
```sh
# Compile a new version of Orbit:
GOOS=windows go build -o orbit-windows.exe ./orbit/cmd/orbit
GOOS=windows GOARCH=amd64 go build -o orbit-windows.exe ./orbit/cmd/orbit
# Push the compiled Orbit as a new version:
./tools/tuf/push_target.sh windows orbit orbit-windows.exe 43

View file

@ -60,8 +60,22 @@ function create_repository() {
--platform $system \
--name orbit \
--version 42.0.0 -t 42.0 -t 42 -t stable
rm $orbit_target
# Add Fleet Desktop application on macos (if enabled).
if [[ $system == "macos" && -n "$FLEET_DESKTOP" ]]; then
FLEET_DESKTOP_VERBOSE=1 \
FLEET_DESKTOP_VERSION=42.0.0 \
make desktop-app-tar-gz
./build/fleetctl updates add \
--path $TUF_PATH \
--target desktop.app.tar.gz \
--platform macos \
--name desktop \
--version 42.0.0 -t 42.0 -t 42 -t stable
rm desktop.app.tar.gz
fi
done
# Generate and add osqueryd .app bundle for macos-app.
@ -106,6 +120,7 @@ if [ -n "$GENERATE_PKGS" ]; then
echo "Generating pkg..."
./build/fleetctl package \
--type=pkg \
${FLEET_DESKTOP:+--fleet-desktop} \
--fleet-url=https://$PKG_HOSTNAME:8080 \
--enroll-secret=$ENROLL_SECRET \
--insecure \
@ -113,6 +128,7 @@ if [ -n "$GENERATE_PKGS" ]; then
--update-roots="$root_keys" \
--update-url=http://$PKG_HOSTNAME:8081
echo "Generating deb..."
./build/fleetctl package \
--type=deb \
--fleet-url=https://$DEB_HOSTNAME:8080 \
@ -122,6 +138,7 @@ if [ -n "$GENERATE_PKGS" ]; then
--update-roots="$root_keys" \
--update-url=http://$DEB_HOSTNAME:8081
echo "Generating rpm..."
./build/fleetctl package \
--type=rpm \
--fleet-url=https://$RPM_HOSTNAME:8080 \
@ -131,6 +148,7 @@ if [ -n "$GENERATE_PKGS" ]; then
--update-roots="$root_keys" \
--update-url=http://$RPM_HOSTNAME:8081
echo "Generating msi..."
./build/fleetctl package \
--type=msi \
--fleet-url=https://$MSI_HOSTNAME:8080 \
@ -143,4 +161,4 @@ if [ -n "$GENERATE_PKGS" ]; then
echo "Packages generated"
fi
wait $SERVER_PID
wait $SERVER_PID