mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Orbit: Add Fleet Desktop support to Windows (#4873)
* Orbit: Add Fleet Desktop support to Windows * Rename workflow, fix linux build * Do not compile systray on linux * nolint on unused * Fix lint properly * nolint both checkers * Fix monitor logic in desktopRunner * Fix interrupt and execute order
This commit is contained in:
parent
fc68e41514
commit
c82c580716
@ -1,4 +1,4 @@
|
||||
name: Generate desktop.app.tar.gz for Orbit
|
||||
name: Generate Fleet Desktop targets for Orbit
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -6,7 +6,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
# The workflow can be triggered by modifying FLEET_DESKTOP_VERSION env.
|
||||
- '.github/workflows/generate-desktop-app-tar-gz.yml'
|
||||
- '.github/workflows/generate-desktop-targets.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@ -16,9 +16,10 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
desktop-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2
|
||||
with:
|
||||
@ -41,6 +42,7 @@ jobs:
|
||||
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 }}
|
||||
@ -53,8 +55,32 @@ jobs:
|
||||
FLEET_DESKTOP_NOTARIZE=true \
|
||||
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
|
||||
make desktop-app-tar-gz
|
||||
|
||||
- name: Upload desktop.app.tar.gz
|
||||
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2
|
||||
with:
|
||||
name: desktop.app.tar.gz
|
||||
path: desktop.app.tar.gz
|
||||
|
||||
desktop-windows:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2
|
||||
with:
|
||||
go-version: '^1.17.0'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||
|
||||
- name: Generate fleet-desktop.exe
|
||||
run: |
|
||||
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
|
||||
make desktop-windows
|
||||
|
||||
- name: Upload fleet-desktop.exe
|
||||
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2
|
||||
with:
|
||||
name: fleet-desktop.exe
|
||||
path: fleet-desktop.exe
|
@ -1,5 +1,5 @@
|
||||
FROM alpine
|
||||
MAINTAINER Fleet Developers <hello@fleetdm.com>
|
||||
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
|
||||
|
||||
RUN apk --update add ca-certificates
|
||||
|
||||
|
7
Makefile
7
Makefile
@ -311,3 +311,10 @@ ifneq ($(shell uname), Darwin)
|
||||
@exit 1
|
||||
endif
|
||||
go run ./tools/desktop macos
|
||||
|
||||
# Build desktop executable for Windows.
|
||||
#
|
||||
# Usage:
|
||||
# FLEET_DESKTOP_VERSION=0.0.1 make desktop-windows
|
||||
desktop-windows:
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui" -o fleet-desktop.exe ./orbit/cmd/desktop
|
1
changes/issue-4807-fleet-desktop-windows
Normal file
1
changes/issue-4807-fleet-desktop-windows
Normal file
@ -0,0 +1 @@
|
||||
* Add beta support for Fleet Desktop on Windows.
|
@ -16,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
"github.com/fleetdm/fleet/v4/pkg/secure"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
@ -247,6 +248,9 @@ func updatesAddFunc(c *cli.Context) error {
|
||||
// - an ".exe" file for target=osqueryd is expected to be called "osqueryd.exe".
|
||||
dstPath := filepath.Join(name, platform, tag, name)
|
||||
switch {
|
||||
case name == "desktop" && platform == "windows":
|
||||
// This is a special case for the desktop target on Windows.
|
||||
dstPath = filepath.Join(filepath.Dir(dstPath), constant.DesktopAppExecName+".exe")
|
||||
case strings.HasSuffix(target, ".exe"):
|
||||
dstPath += ".exe"
|
||||
case strings.HasSuffix(target, ".app.tar.gz"):
|
||||
@ -480,7 +484,7 @@ func startRotatePseudoTx(repoPath string) (commit, rollback func() error, err er
|
||||
func createBackups(dirPath string) error {
|
||||
// Only *.json files need to be backed up (other files are not modified)
|
||||
backupPath := filepath.Join(dirPath, backupDirectory)
|
||||
if err := os.Mkdir(backupPath, os.ModeDir|0744); err != nil {
|
||||
if err := os.Mkdir(backupPath, os.ModeDir|0o744); err != nil {
|
||||
if errors.Is(err, fs.ErrExist) {
|
||||
return fmt.Errorf("backup directory already exists: %w", err)
|
||||
}
|
||||
@ -577,11 +581,11 @@ func copyTarget(srcPath, dstPath string) error {
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if err := secure.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
|
||||
if err := secure.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||
return fmt.Errorf("create dst dir for copy: %w", err)
|
||||
}
|
||||
|
||||
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0644)
|
||||
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open dst for copy: %w", err)
|
||||
}
|
||||
|
10
go.mod
10
go.mod
@ -29,11 +29,15 @@ 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/getlantern/golog v0.0.0-20211223150227-d4d95a44d873 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 // indirect
|
||||
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-kit/kit v0.9.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/gocarina/gocsv v0.0.0-20220310154401-d4df709ca055
|
||||
github.com/gocolly/colly v1.2.0
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0
|
||||
@ -99,9 +103,11 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0
|
||||
go.opentelemetry.io/otel/sdk v1.3.0
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
|
||||
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008
|
||||
google.golang.org/grpc v1.42.0
|
||||
gopkg.in/guregu/null.v3 v3.4.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
|
36
go.sum
36
go.sum
@ -261,6 +261,7 @@ github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
@ -419,18 +420,23 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
|
||||
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/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw=
|
||||
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
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/golog v0.0.0-20211223150227-d4d95a44d873 h1:nnod94N4hMKb7pyJmnXDk+HR23o1S2CbZ4oMKzHbp9A=
|
||||
github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
|
||||
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/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU=
|
||||
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM=
|
||||
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/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE=
|
||||
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A=
|
||||
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/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWSvoHI6ZatlG4B+GBDLxV/2ZkBsTA=
|
||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8 h1:ois/A+ZnsBYCw8ezd8Zibsk4wKOQzhbPVlR1fZ/Yg9w=
|
||||
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8/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=
|
||||
@ -491,8 +497,9 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
@ -1252,7 +1259,6 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
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=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c h1:TWQ2UvXPkhPxI2KmApKBOCaV6yD2N4mlvqFQ/DlPtpQ=
|
||||
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c/go.mod h1:OYYulo9tUqRadRLwB0+LE914sa1ui2yL7OrcU3Q/1XY=
|
||||
@ -1301,20 +1307,28 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
gocloud.dev v0.24.0 h1:cNtHD07zQQiv02OiwwDyVMuHmR7iQt2RLkzoAgz7wBs=
|
||||
gocloud.dev v0.24.0/go.mod h1:uA+als++iBX5ShuG4upQo/3Zoz49iIPlYUWHV5mM8w8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@ -1586,8 +1600,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008 h1:pq9pwoi2rjLWvmiVser/lIOgiyA3fli4M+RfGVMA7nE=
|
||||
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
|
@ -1,3 +1,6 @@
|
||||
//go:build darwin || windows
|
||||
// +build darwin windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@ -14,9 +17,6 @@ import (
|
||||
"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" {
|
11
orbit/cmd/desktop/desktop_unix.go
Normal file
11
orbit/cmd/desktop/desktop_unix.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// TODO(lucas): Once we support Linux, amend the above build tags.
|
||||
|
||||
package main
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed icon_white.png
|
||||
var icoBytes []byte
|
12
orbit/cmd/desktop/desktop_windows.go
Normal file
12
orbit/cmd/desktop/desktop_windows.go
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// For Windows we must use ico format for the icon,
|
||||
// see https://github.com/getlantern/systray/blob/6065fda28be8c8d91aeb5e20de25e1600b8664a3/systray_windows.go#L850-L856.
|
||||
|
||||
//go:embed icon_white.ico
|
||||
var icoBytes []byte
|
BIN
orbit/cmd/desktop/icon_white.ico
Normal file
BIN
orbit/cmd/desktop/icon_white.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/insecure"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/osquery"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table"
|
||||
@ -23,7 +24,6 @@ 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"
|
||||
@ -216,8 +216,15 @@ func main() {
|
||||
opt.Targets = update.DarwinLegacyTargets
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
|
||||
opt.Targets["desktop"] = update.DesktopMacOSTarget
|
||||
if c.Bool("fleet-desktop") {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
opt.Targets["desktop"] = update.DesktopMacOSTarget
|
||||
case "windows":
|
||||
opt.Targets["desktop"] = update.DesktopWindowsTarget
|
||||
default:
|
||||
log.Fatal().Str("GOOS", runtime.GOOS).Msg("unsupported GOOS for desktop target")
|
||||
}
|
||||
// Override default channel with the provided value.
|
||||
opt.Targets.SetTargetChannel("desktop", c.String("desktop-channel"))
|
||||
}
|
||||
@ -232,9 +239,9 @@ func main() {
|
||||
opt.InsecureTransport = c.Bool("insecure")
|
||||
|
||||
var (
|
||||
updater *update.Updater
|
||||
osquerydPath string
|
||||
desktopAppPath string
|
||||
updater *update.Updater
|
||||
osquerydPath string
|
||||
desktopPath string
|
||||
)
|
||||
|
||||
// NOTE: When running in dev-mode, even if `disable-updates` is set,
|
||||
@ -252,12 +259,16 @@ func main() {
|
||||
return fmt.Errorf("get osqueryd target: %w", err)
|
||||
}
|
||||
osquerydPath = osquerydLocalTarget.ExecPath
|
||||
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
|
||||
if c.Bool("fleet-desktop") {
|
||||
fleetDesktopLocalTarget, err := updater.Get("desktop")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get desktop target: %w", err)
|
||||
}
|
||||
desktopAppPath = fleetDesktopLocalTarget.DirPath
|
||||
if runtime.GOOS == "darwin" {
|
||||
desktopPath = fleetDesktopLocalTarget.DirPath
|
||||
} else {
|
||||
desktopPath = fleetDesktopLocalTarget.ExecPath
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Info().Msg("running with auto updates disabled")
|
||||
@ -266,10 +277,17 @@ func main() {
|
||||
if err != nil {
|
||||
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)
|
||||
if c.Bool("fleet-desktop") {
|
||||
if runtime.GOOS == "darwin" {
|
||||
desktopPath, err = updater.DirLocalPath("desktop")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get desktop target: %w", err)
|
||||
}
|
||||
} else {
|
||||
desktopPath, err = updater.ExecutableLocalPath("desktop")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get desktop target: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,7 +314,7 @@ func main() {
|
||||
|
||||
if !c.Bool("disable-updates") {
|
||||
targets := []string{"orbit", "osqueryd"}
|
||||
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
|
||||
if c.Bool("fleet-desktop") && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
|
||||
targets = append(targets, "desktop")
|
||||
}
|
||||
updateRunner, err := update.NewRunner(updater, update.RunnerOptions{
|
||||
@ -458,8 +476,8 @@ func main() {
|
||||
}))
|
||||
g.Add(ext.Execute, ext.Interrupt)
|
||||
|
||||
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
|
||||
desktopRunner := newDesktopRunner(desktopAppPath, fleetURL, deviceAuthToken, c.Bool("insecure"))
|
||||
if c.Bool("fleet-desktop") && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
|
||||
desktopRunner := newDesktopRunner(desktopPath, fleetURL, deviceAuthToken, c.Bool("insecure"))
|
||||
g.Add(desktopRunner.actor())
|
||||
}
|
||||
|
||||
@ -481,20 +499,22 @@ func main() {
|
||||
}
|
||||
|
||||
type desktopRunner struct {
|
||||
appPath string
|
||||
desktopPath string
|
||||
fleetURL string
|
||||
deviceAuthToken string
|
||||
insecure bool
|
||||
done chan struct{}
|
||||
interruptCh chan struct{} // closed when interrupt is triggered
|
||||
executeDoneCh chan struct{} // closed when execute returns
|
||||
}
|
||||
|
||||
func newDesktopRunner(appPath, fleetURL, deviceAuthToken string, insecure bool) *desktopRunner {
|
||||
func newDesktopRunner(desktopPath, fleetURL, deviceAuthToken string, insecure bool) *desktopRunner {
|
||||
return &desktopRunner{
|
||||
appPath: appPath,
|
||||
desktopPath: desktopPath,
|
||||
fleetURL: fleetURL,
|
||||
deviceAuthToken: deviceAuthToken,
|
||||
insecure: insecure,
|
||||
done: make(chan struct{}),
|
||||
interruptCh: make(chan struct{}),
|
||||
executeDoneCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,43 +522,88 @@ func (d *desktopRunner) actor() (func() error, func(error)) {
|
||||
return d.execute, d.interrupt
|
||||
}
|
||||
|
||||
// execute makes sure the fleet-desktop application is running.
|
||||
//
|
||||
// We have to support the scenario where the user closes its sessions (log out).
|
||||
// To support this, we add retries to execuser.Run. Basically retry execuser.Run until it succeds,
|
||||
// which will happen when the user logs in.
|
||||
// Once fleet-desktop is started, the process is monitored (user processes get killed when the user
|
||||
// closes all its sessions).
|
||||
//
|
||||
// NOTE(lucas): This logic could be improved to detect if there's a valid session or not first.
|
||||
func (d *desktopRunner) execute() error {
|
||||
log.Info().Str("path", d.appPath).Msg("opening")
|
||||
defer close(d.executeDoneCh)
|
||||
|
||||
log.Info().Str("path", d.desktopPath).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)),
|
||||
opts := []execuser.Option{
|
||||
execuser.WithEnv("FLEET_DESKTOP_DEVICE_URL", url.String()),
|
||||
execuser.WithEnv("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)
|
||||
opts = append(opts, execuser.WithEnv("FLEET_DESKTOP_INSECURE", "1"))
|
||||
}
|
||||
|
||||
// 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)
|
||||
// First retry logic to start fleet-desktop.
|
||||
if done := retry(30*time.Second, d.interruptCh, func() bool {
|
||||
// Orbit runs as root user on Unix and as SYSTEM (Windows Service) user on Windows.
|
||||
// To be able to run the desktop application (mostly to register the icon in the system tray)
|
||||
// we need to run the application as the login user.
|
||||
// Package execuser provides multi-platform support for this.
|
||||
if err := execuser.Run(d.desktopPath, opts...); err != nil {
|
||||
log.Debug().Err(err).Msg("execuser.Run")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}); done {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Second retry logic to monitor fleet-desktop.
|
||||
if done := retry(30*time.Second, d.interruptCh, func() bool {
|
||||
switch _, err := getProcessByName(constant.DesktopAppExecName); {
|
||||
case err == nil:
|
||||
return true // all good, process is running, retry.
|
||||
case errors.Is(err, errProcessNotFound):
|
||||
log.Debug().Msgf("%s process not running", constant.DesktopAppExecName)
|
||||
return false // process is not running, do not retry.
|
||||
default:
|
||||
log.Debug().Err(err).Msg("getProcessByName")
|
||||
return true // failed to get process by name, retry.
|
||||
}
|
||||
}); done {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func retry(d time.Duration, done chan struct{}, fn func() bool) bool {
|
||||
ticker := time.NewTicker(d)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
if retry := fn(); !retry {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
return true
|
||||
case <-ticker.C:
|
||||
// OK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *desktopRunner) interrupt(err error) {
|
||||
log.Debug().Err(err).Msg("interrupt desktopRunner")
|
||||
defer close(d.done)
|
||||
|
||||
close(d.interruptCh) // Signal execute to return.
|
||||
<-d.executeDoneCh // Wait for execute to return.
|
||||
|
||||
if err := killProcessByName(constant.DesktopAppExecName); err != nil {
|
||||
log.Error().Err(err).Msg("killProcess")
|
||||
@ -590,7 +655,7 @@ func getProcessByName(name string) (*gopsutil_process.Process, error) {
|
||||
log.Debug().Err(err).Int32("pid", process.Pid).Msg("get process name")
|
||||
continue
|
||||
}
|
||||
if processName == name {
|
||||
if strings.HasPrefix(processName, name) {
|
||||
foundProcess = process
|
||||
break
|
||||
}
|
||||
|
@ -6,5 +6,8 @@ const (
|
||||
// DefaultFileMode is the default file mode to apply to created files.
|
||||
DefaultFileMode = 0o600
|
||||
// DesktopAppExecName is the name of Fleet's Desktop executable.
|
||||
//
|
||||
// We use fleet-desktop as name to properly identify the process when listing
|
||||
// running processes/tasks.
|
||||
DesktopAppExecName = "fleet-desktop"
|
||||
)
|
||||
|
30
orbit/pkg/execuser/execuser.go
Normal file
30
orbit/pkg/execuser/execuser.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Package execuser is used to run applications from a high privilege user (root on Unix,
|
||||
// SYSTEM service on Windows) as the current login user.
|
||||
package execuser
|
||||
|
||||
type eopts struct {
|
||||
env [][2]string
|
||||
stderrPath string //nolint:structcheck,unused
|
||||
}
|
||||
|
||||
// Option allows configuring the application.
|
||||
type Option func(*eopts)
|
||||
|
||||
// WithEnv sets environment variables for the application.
|
||||
func WithEnv(name, value string) Option {
|
||||
return func(a *eopts) {
|
||||
a.env = append(a.env, [2]string{name, value})
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs an application as the current login user.
|
||||
// It assumes the caller is running with high privileges (root on Unix, SYSTEM on Windows).
|
||||
//
|
||||
// It returns after starting the child process.
|
||||
func Run(path string, opts ...Option) error {
|
||||
var o eopts
|
||||
for _, fn := range opts {
|
||||
fn(&o)
|
||||
}
|
||||
return run(path, o)
|
||||
}
|
34
orbit/pkg/execuser/execuser_darwin.go
Normal file
34
orbit/pkg/execuser/execuser_darwin.go
Normal file
@ -0,0 +1,34 @@
|
||||
package execuser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// run uses macOS open command to start application as the current login user.
|
||||
func run(path string, opts eopts) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat path %q: %w", path, err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("path is not an .app directory: %s", path)
|
||||
}
|
||||
var arg []string
|
||||
if opts.stderrPath != "" {
|
||||
arg = append(arg, "--stderr", opts.stderrPath)
|
||||
}
|
||||
for _, nv := range opts.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 %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
5
orbit/pkg/execuser/execuser_linux.go
Normal file
5
orbit/pkg/execuser/execuser_linux.go
Normal file
@ -0,0 +1,5 @@
|
||||
package execuser
|
||||
|
||||
func run(path string, opts eopts) error {
|
||||
panic("unimplemented")
|
||||
}
|
232
orbit/pkg/execuser/execuser_windows.go
Normal file
232
orbit/pkg/execuser/execuser_windows.go
Normal file
@ -0,0 +1,232 @@
|
||||
package execuser
|
||||
|
||||
// NOTE: The following was copied from
|
||||
// https://gist.github.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc
|
||||
//
|
||||
// To view what was modified/added, you can use the execuser_windows_diff.sh script.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll")
|
||||
modkernel32 *windows.LazyDLL = windows.NewLazySystemDLL("kernel32.dll")
|
||||
modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll")
|
||||
moduserenv *windows.LazyDLL = windows.NewLazySystemDLL("userenv.dll")
|
||||
|
||||
procWTSEnumerateSessionsW *windows.LazyProc = modwtsapi32.NewProc("WTSEnumerateSessionsW")
|
||||
procWTSGetActiveConsoleSessionId *windows.LazyProc = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
|
||||
procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken")
|
||||
procDuplicateTokenEx *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx")
|
||||
procCreateEnvironmentBlock *windows.LazyProc = moduserenv.NewProc("CreateEnvironmentBlock")
|
||||
procCreateProcessAsUser *windows.LazyProc = modadvapi32.NewProc("CreateProcessAsUserW")
|
||||
)
|
||||
|
||||
const (
|
||||
WTS_CURRENT_SERVER_HANDLE uintptr = 0
|
||||
)
|
||||
|
||||
type WTS_CONNECTSTATE_CLASS int
|
||||
|
||||
const (
|
||||
WTSActive WTS_CONNECTSTATE_CLASS = iota
|
||||
WTSConnected
|
||||
WTSConnectQuery
|
||||
WTSShadow
|
||||
WTSDisconnected
|
||||
WTSIdle
|
||||
WTSListen
|
||||
WTSReset
|
||||
WTSDown
|
||||
WTSInit
|
||||
)
|
||||
|
||||
type SECURITY_IMPERSONATION_LEVEL int
|
||||
|
||||
const (
|
||||
SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota
|
||||
SecurityIdentification
|
||||
SecurityImpersonation
|
||||
SecurityDelegation
|
||||
)
|
||||
|
||||
type TOKEN_TYPE int
|
||||
|
||||
const (
|
||||
TokenPrimary TOKEN_TYPE = iota + 1
|
||||
TokenImpersonazion
|
||||
)
|
||||
|
||||
type SW int
|
||||
|
||||
const (
|
||||
SW_HIDE SW = 0
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_NORMAL = 1
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_MAXIMIZE = 3
|
||||
SW_SHOWNOACTIVATE = 4
|
||||
SW_SHOW = 5
|
||||
SW_MINIMIZE = 6
|
||||
SW_SHOWMINNOACTIVE = 7
|
||||
SW_SHOWNA = 8
|
||||
SW_RESTORE = 9
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_MAX = 1
|
||||
)
|
||||
|
||||
type WTS_SESSION_INFO struct {
|
||||
SessionID windows.Handle
|
||||
WinStationName *uint16
|
||||
State WTS_CONNECTSTATE_CLASS
|
||||
}
|
||||
|
||||
const (
|
||||
CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
CREATE_NEW_CONSOLE = 0x00000010
|
||||
)
|
||||
|
||||
// run uses the Windows API to run a child process as the current login user.
|
||||
// It assumes the caller is running as a SYSTEM Windows service.
|
||||
//
|
||||
// It sets the environment of the current process so that it gets inherited by
|
||||
// the child process (see call to CreateEnvironmentBlock).
|
||||
// From https://docs.microsoft.com/en-us/windows/win32/procthread/changing-environment-variables:
|
||||
// "If you want the child process to inherit most of the parent's environment with
|
||||
// only a few changes, retrieve the current values using GetEnvironmentVariable, save these values,
|
||||
// create an updated block for the child process to inherit, create the child process, and then
|
||||
// restore the saved values using SetEnvironmentVariable, as shown in the following example."
|
||||
//
|
||||
func run(path string, opts eopts) error {
|
||||
for _, nv := range opts.env {
|
||||
os.Setenv(nv[0], nv[1])
|
||||
}
|
||||
return startProcessAsCurrentUser(path, "", "")
|
||||
}
|
||||
|
||||
// getCurrentUserSessionId will attempt to resolve
|
||||
// the session ID of the user currently active on
|
||||
// the system.
|
||||
func getCurrentUserSessionId() (windows.Handle, error) {
|
||||
sessionList, err := wtsEnumerateSessions()
|
||||
if err != nil {
|
||||
return 0xFFFFFFFF, fmt.Errorf("get current user session token: %s", err)
|
||||
}
|
||||
|
||||
for i := range sessionList {
|
||||
if sessionList[i].State == WTSActive {
|
||||
return sessionList[i].SessionID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(lucas): Check which sessions is assigned to current log in user.
|
||||
if sessionId, _, err := procWTSGetActiveConsoleSessionId.Call(); sessionId == 0xFFFFFFFF {
|
||||
return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err)
|
||||
} else {
|
||||
return windows.Handle(sessionId), nil
|
||||
}
|
||||
}
|
||||
|
||||
// wtsEnumerateSession will call the native
|
||||
// version for Windows and parse the result
|
||||
// to a Golang friendly version
|
||||
func wtsEnumerateSessions() ([]*WTS_SESSION_INFO, error) {
|
||||
var (
|
||||
sessionInformation windows.Handle = windows.Handle(0)
|
||||
sessionCount int = 0
|
||||
sessionList []*WTS_SESSION_INFO = make([]*WTS_SESSION_INFO, 0)
|
||||
)
|
||||
|
||||
if returnCode, _, err := procWTSEnumerateSessionsW.Call(WTS_CURRENT_SERVER_HANDLE, 0, 1, uintptr(unsafe.Pointer(&sessionInformation)), uintptr(unsafe.Pointer(&sessionCount))); returnCode == 0 {
|
||||
return nil, fmt.Errorf("call native WTSEnumerateSessionsW: %s", err)
|
||||
}
|
||||
|
||||
structSize := unsafe.Sizeof(WTS_SESSION_INFO{})
|
||||
current := uintptr(sessionInformation)
|
||||
for i := 0; i < sessionCount; i++ {
|
||||
sessionList = append(sessionList, (*WTS_SESSION_INFO)(unsafe.Pointer(current)))
|
||||
current += structSize
|
||||
}
|
||||
|
||||
return sessionList, nil
|
||||
}
|
||||
|
||||
// duplicateUserTokenFromSessionID will attempt
|
||||
// to duplicate the user token for the user logged
|
||||
// into the provided session ID
|
||||
func duplicateUserTokenFromSessionID(sessionId windows.Handle) (windows.Token, error) {
|
||||
var (
|
||||
impersonationToken windows.Handle = 0
|
||||
userToken windows.Token = 0
|
||||
)
|
||||
|
||||
if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 {
|
||||
return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err)
|
||||
}
|
||||
|
||||
if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(SecurityImpersonation), uintptr(TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 {
|
||||
return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err)
|
||||
}
|
||||
|
||||
if err := windows.CloseHandle(impersonationToken); err != nil {
|
||||
return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err)
|
||||
}
|
||||
|
||||
return userToken, nil
|
||||
}
|
||||
|
||||
func startProcessAsCurrentUser(appPath, cmdLine, workDir string) error {
|
||||
var (
|
||||
sessionId windows.Handle
|
||||
userToken windows.Token
|
||||
envInfo windows.Handle
|
||||
|
||||
startupInfo windows.StartupInfo
|
||||
processInfo windows.ProcessInformation
|
||||
|
||||
commandLine uintptr = 0
|
||||
workingDir uintptr = 0
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
if sessionId, err = getCurrentUserSessionId(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userToken, err = duplicateUserTokenFromSessionID(sessionId); err != nil {
|
||||
return fmt.Errorf("get duplicate user token for current user session: %s", err)
|
||||
}
|
||||
|
||||
if returnCode, _, err := procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&envInfo)), uintptr(userToken), 1); returnCode == 0 {
|
||||
return fmt.Errorf("create environment details for process: %s", err)
|
||||
}
|
||||
|
||||
// TODO(lucas): Test out creation flags and startup info values.
|
||||
creationFlags := CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE
|
||||
startupInfo.ShowWindow = SW_SHOW
|
||||
startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default")
|
||||
|
||||
if len(cmdLine) > 0 {
|
||||
commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine)))
|
||||
}
|
||||
if len(workDir) > 0 {
|
||||
workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir)))
|
||||
}
|
||||
|
||||
if returnCode, _, err := procCreateProcessAsUser.Call(
|
||||
uintptr(userToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), commandLine, 0, 0, 0,
|
||||
uintptr(creationFlags), uintptr(envInfo), workingDir, uintptr(unsafe.Pointer(&startupInfo)), uintptr(unsafe.Pointer(&processInfo)),
|
||||
); returnCode == 0 {
|
||||
return fmt.Errorf("create process as user: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
3
orbit/pkg/execuser/execuser_windows_diff.sh
Executable file
3
orbit/pkg/execuser/execuser_windows_diff.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
diff execuser_windows.go <(curl https://gist.githubusercontent.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc/raw/2cfaf36cd326ace04c000fb988a5ef6b1f02a116/native.go)
|
@ -41,6 +41,12 @@ func BuildMSI(opt Options) (string, error) {
|
||||
updateOpt.RootDirectory = orbitRoot
|
||||
updateOpt.Targets = update.WindowsTargets
|
||||
|
||||
if opt.Desktop {
|
||||
updateOpt.Targets["desktop"] = update.DesktopWindowsTarget
|
||||
// Override default channel with the provided value.
|
||||
updateOpt.Targets.SetTargetChannel("desktop", opt.DesktopChannel)
|
||||
}
|
||||
|
||||
// Override default channels with the provided values.
|
||||
updateOpt.Targets.SetTargetChannel("orbit", opt.OrbitChannel)
|
||||
updateOpt.Targets.SetTargetChannel("osqueryd", opt.OsquerydChannel)
|
||||
|
@ -53,7 +53,7 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
|
||||
ErrorControl="ignore"
|
||||
Start="auto"
|
||||
Type="ownProcess"
|
||||
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log"{{ if .FleetURL }} --fleet-url "{{ .FleetURL }}"{{ end }}{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}"'
|
||||
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log"{{ if .FleetURL }} --fleet-url "{{ .FleetURL }}"{{ end }}{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }}{{ if .Desktop }} --fleet-desktop --desktop-channel {{ .DesktopChannel }}{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}"'
|
||||
>
|
||||
<util:ServiceConfig
|
||||
FirstFailureActionType="restart"
|
||||
|
@ -66,4 +66,10 @@ var (
|
||||
TargetFile: "desktop.app.tar.gz",
|
||||
ExtractedExecSubPath: []string{"Fleet Desktop.app", "Contents", "MacOS", constant.DesktopAppExecName},
|
||||
}
|
||||
|
||||
DesktopWindowsTarget = TargetInfo{
|
||||
Platform: "windows",
|
||||
Channel: "stable",
|
||||
TargetFile: constant.DesktopAppExecName + ".exe",
|
||||
}
|
||||
)
|
||||
|
@ -1,84 +1,11 @@
|
||||
package open
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// 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 {
|
||||
if err := browser(url); 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)
|
||||
}
|
||||
}
|
||||
|
7
pkg/open/open_darwin.go
Normal file
7
pkg/open/open_darwin.go
Normal file
@ -0,0 +1,7 @@
|
||||
package open
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func browser(url string) error {
|
||||
return exec.Command("open", url).Run()
|
||||
}
|
8
pkg/open/open_linux.go
Normal file
8
pkg/open/open_linux.go
Normal file
@ -0,0 +1,8 @@
|
||||
package open
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func browser(url string) error {
|
||||
// xdg-open is available on most Linux-y systems
|
||||
return exec.Command("xdg-open", url).Run()
|
||||
}
|
16
pkg/open/open_windows.go
Normal file
16
pkg/open/open_windows.go
Normal file
@ -0,0 +1,16 @@
|
||||
package open
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func browser(url string) error {
|
||||
cmd := exec.Command("cmd", "/c", "start", url)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
// HideWindow avoids a brief cmd console from opening
|
||||
// before the browser opens the URL.
|
||||
HideWindow: true,
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
FROM alpine
|
||||
MAINTAINER Fleet Developers <hello@fleetdm.com>
|
||||
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
|
||||
|
||||
RUN apk --update add ca-certificates
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
FROM alpine
|
||||
MAINTAINER Fleet Developers <hello@fleetdm.com>
|
||||
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
|
||||
|
||||
RUN apk --update add ca-certificates
|
||||
|
||||
|
@ -76,6 +76,19 @@ function create_repository() {
|
||||
rm desktop.app.tar.gz
|
||||
fi
|
||||
|
||||
# Add Fleet Desktop application on (if enabled).
|
||||
if [[ $system == "windows" && -n "$FLEET_DESKTOP" ]]; then
|
||||
FLEET_DESKTOP_VERSION=42.0.0 \
|
||||
make desktop-windows
|
||||
./build/fleetctl updates add \
|
||||
--path $TUF_PATH \
|
||||
--target fleet-desktop.exe \
|
||||
--platform windows \
|
||||
--name desktop \
|
||||
--version 42.0.0 -t 42.0 -t 42 -t stable
|
||||
rm fleet-desktop.exe
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# Generate and add osqueryd .app bundle for macos-app.
|
||||
@ -151,6 +164,7 @@ if [ -n "$GENERATE_PKGS" ]; then
|
||||
echo "Generating msi..."
|
||||
./build/fleetctl package \
|
||||
--type=msi \
|
||||
${FLEET_DESKTOP:+--fleet-desktop} \
|
||||
--fleet-url=https://$MSI_HOSTNAME:8080 \
|
||||
--enroll-secret=$ENROLL_SECRET \
|
||||
--insecure \
|
||||
|
Loading…
Reference in New Issue
Block a user