mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Add CIS checks for 2.9.X and add pmset
table to fleetd (#9470)
#9253 - ~[ ] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information.~ - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~ --------- Co-authored-by: Sharon Katz <121527325+sharon-fdm@users.noreply.github.com>
This commit is contained in:
parent
3cdea3c896
commit
d4a1b4d218
64
.github/workflows/build-orbit.yaml
vendored
Normal file
64
.github/workflows/build-orbit.yaml
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
name: Build, Sign and Notarize Orbit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'orbit/**.go'
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # 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: Set up Go
|
||||
uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v2
|
||||
with:
|
||||
go-version: 1.19.4
|
||||
|
||||
- name: Build, codesign and notarize orbit
|
||||
run: go run ./orbit/tools/build/build.go
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AC_USERNAME: ${{ secrets.APPLE_USERNAME }}
|
||||
AC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
AC_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CODESIGN_IDENTITY: 51049B247B25B3119FAE7E9C0CC4375A43E47237
|
||||
|
||||
- name: Upload orbit
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v2
|
||||
with:
|
||||
name: orbit
|
||||
path: |
|
||||
orbit-darwin
|
@ -656,6 +656,145 @@ spec:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
name: CIS - Ensure Power Nap Is Disabled for Intel Macs (Fleetd Required)
|
||||
platforms: macOS
|
||||
platform: darwin
|
||||
description: |
|
||||
Power Nap allows the system to stay in low power mode, especially while on battery power, and periodically
|
||||
connect to previously known networks with stored credentials for user applications to phone home and get updates.
|
||||
This capability requires FileVault to remain unlocked and the use of previously joined networks to be risk accepted
|
||||
based on the SSID without user input.
|
||||
Disabling this feature mitigates the risk of an attacker remotely waking the system and gaining access.
|
||||
resolution: |
|
||||
Automated method:
|
||||
Ask your system administrator to deploy a script that runs the following command to turn off
|
||||
Power Nap on the device:
|
||||
/usr/bin/sudo /usr/bin/pmset -a powernap 0
|
||||
query: |
|
||||
SELECT 1 FROM (SELECT
|
||||
COALESCE(JSON_EXTRACT(
|
||||
JSON_EXTRACT(json_result, '$.AC Power:'),
|
||||
'$.powernap'
|
||||
), '') AS powernap_ac,
|
||||
COALESCE(JSON_EXTRACT(
|
||||
JSON_EXTRACT(json_result, '$.Battery Power:'),
|
||||
'$.powernap'
|
||||
), '') AS powernap_battery
|
||||
FROM pmset WHERE getting = 'custom' AND powernap_battery != '1' AND powernap_ac != '1');
|
||||
purpose: Informational
|
||||
tags: compliance, CIS, CIS_Level1, CIS2.9.1
|
||||
contributors: lucasmrod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
name: CIS - Ensure Wake for Network Access Is Disabled (Fleetd Required)
|
||||
platforms: macOS
|
||||
platform: darwin
|
||||
description: |
|
||||
Wake for Network Access allows the computer to take action when the user is not present and the computer
|
||||
is in energy saving mode. These tools require FileVault to remain unlocked and fully rejoin known networks.
|
||||
|
||||
Disabling this feature mitigates the risk of an attacker remotely waking the system and gaining access.
|
||||
resolution: |
|
||||
Automated method:
|
||||
Ask your system administrator to deploy a script that runs the following command to turn off
|
||||
Wake on on the device:
|
||||
/usr/bin/sudo /usr/bin/pmset -a womp 0
|
||||
query: |
|
||||
SELECT 1 FROM (SELECT
|
||||
COALESCE(JSON_EXTRACT(
|
||||
JSON_EXTRACT(json_result, '$.AC Power:'),
|
||||
'$.womp'
|
||||
), '') AS womp_ac,
|
||||
COALESCE(JSON_EXTRACT(
|
||||
JSON_EXTRACT(json_result, '$.Battery Power:'),
|
||||
'$.womp'
|
||||
), '') AS womp_battery
|
||||
FROM pmset WHERE getting = 'custom' AND womp_battery != '1' AND womp_ac != '1');
|
||||
purpose: Informational
|
||||
tags: compliance, CIS, CIS_Level1, CIS2.9.2
|
||||
contributors: lucasmrod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
name: CIS - Ensure the OS is not Activate When Resuming from Sleep
|
||||
platforms: macOS
|
||||
platform: darwin
|
||||
description: |
|
||||
In order to use a computer with Full Disk Encryption (FDE), macOS must keep encryption keys in memory to allow
|
||||
the use of the disk that has been FileVault protected. When the system is not in use, the volume is protected
|
||||
through encryption. When the system is sleeping and available to quickly resume, the encryption keys remain in memory.
|
||||
If an unauthorized party has possession of the computer and the computer is only slept, there are known attack vectors
|
||||
that can be attempted against the RAM that has the encryption keys or the running operating system protected
|
||||
by a login screen.
|
||||
|
||||
Mac systems should be set to hibernate after sleeping for a risk-acceptable time period.
|
||||
MacBooks should be set so that the `standbydelay` is 15 minutes (900 seconds) or less.
|
||||
resolution: |
|
||||
Ask your system administrator to deploy the following script to Macbook devices:
|
||||
if [[ $(uname -m) == 'arm64' ]]; then
|
||||
# Apple silicon
|
||||
/usr/bin/sudo /usr/bin/pmset -a standby 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1
|
||||
/usr/bin/sudo /usr/bin/pmset -a hibernatemode 25
|
||||
else
|
||||
# Intel
|
||||
/usr/bin/sudo /usr/bin/pmset -a standbydelaylow 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a standbydelayhigh 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a highstandbythreshold 90
|
||||
/usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1
|
||||
/usr/bin/sudo /usr/bin/pmset -a hibernatemode 25
|
||||
fi
|
||||
query: |
|
||||
SELECT 1 WHERE EXISTS(
|
||||
SELECT 1 FROM system_info WHERE
|
||||
regex_match(hardware_model, '^Mac[0-9,]+$', 0) != '' OR regex_match(hardware_model, '^MacBook', 0) != 0
|
||||
)
|
||||
AND EXISTS(
|
||||
SELECT JSON_EXTRACT(system_wide_power_settings, '$.DestroyFVKeyOnStandby') AS destroy_fv_key_on_standby
|
||||
FROM (
|
||||
SELECT JSON_EXTRACT(json_result, '$.System-wide power settings:') AS system_wide_power_settings FROM pmset
|
||||
)
|
||||
WHERE destroy_fv_key_on_standby = '1'
|
||||
)
|
||||
AND EXISTS(
|
||||
SELECT 1 WHERE EXISTS(
|
||||
SELECT 1 WHERE EXISTS(
|
||||
SELECT 1 FROM system_info WHERE cpu_type = 'x86_64h' OR cpu_type = 'x86_64'
|
||||
) AND EXISTS(
|
||||
SELECT
|
||||
CAST(JSON_EXTRACT(battery, '$.standbydelaylow') AS INTEGER) AS standbydelaylow,
|
||||
CAST(JSON_EXTRACT(battery, '$.standbydelayhigh') AS INTEGER) AS standbydelayhigh,
|
||||
CAST(JSON_EXTRACT(battery, '$.highstandbythreshold') AS INTEGER) AS highstandbythreshold,
|
||||
CAST(JSON_EXTRACT(battery, '$.hibernatemode') AS INTEGER) AS hibernatemode
|
||||
FROM (
|
||||
SELECT JSON_EXTRACT(json_result, '$.Battery Power:') as battery FROM pmset WHERE getting = 'custom'
|
||||
)
|
||||
WHERE standbydelaylow <= 900 AND standbydelayhigh <= 900 AND highstandbythreshold >= 90 AND hibernatemode = 25
|
||||
)
|
||||
) OR EXISTS(
|
||||
SELECT 1 WHERE EXISTS(
|
||||
SELECT 1 FROM system_info WHERE cpu_type = 'arm64e'
|
||||
) AND EXISTS (
|
||||
SELECT
|
||||
CAST(JSON_EXTRACT(battery, '$.standby') AS INTEGER) AS standby,
|
||||
CAST(JSON_EXTRACT(battery, '$.hibernatemode') AS INTEGER) AS hibernatemode
|
||||
FROM (
|
||||
SELECT JSON_EXTRACT(json_result, '$.Battery Power:') AS battery FROM pmset WHERE getting = 'custom'
|
||||
)
|
||||
WHERE standby <= 900 AND hibernatemode = 25
|
||||
)
|
||||
)
|
||||
);
|
||||
purpose: Informational
|
||||
tags: compliance, CIS, CIS_Level2, CIS2.9.3
|
||||
contributors: lucasmrod
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
name: CIS - Ensure a Password is Required to Wake the Computer From Sleep or Screen Saver Is Enabled (MDM Required)
|
||||
platforms: macOS
|
||||
|
3
ee/cis/macos-13/test/scripts/CIS_2.9.1.sh
Normal file
3
ee/cis/macos-13/test/scripts/CIS_2.9.1.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
/usr/bin/sudo /usr/bin/pmset -a powernap 0
|
3
ee/cis/macos-13/test/scripts/CIS_2.9.2.sh
Normal file
3
ee/cis/macos-13/test/scripts/CIS_2.9.2.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
/usr/bin/sudo /usr/bin/pmset -a womp 0
|
15
ee/cis/macos-13/test/scripts/CIS_2.9.3.sh
Executable file
15
ee/cis/macos-13/test/scripts/CIS_2.9.3.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $(uname -m) == 'arm64' ]]; then
|
||||
# Apple silicon
|
||||
/usr/bin/sudo /usr/bin/pmset -a standby 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1
|
||||
/usr/bin/sudo /usr/bin/pmset -a hibernatemode 25
|
||||
else
|
||||
# Intel
|
||||
/usr/bin/sudo /usr/bin/pmset -a standbydelaylow 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a standbydelayhigh 900
|
||||
/usr/bin/sudo /usr/bin/pmset -a highstandbythreshold 90
|
||||
/usr/bin/sudo /usr/bin/pmset -a destroyfvkeyonstandby 1
|
||||
/usr/bin/sudo /usr/bin/pmset -a hibernatemode 25
|
||||
fi
|
1
orbit/changes/9253-add-fleetd-table-pmset
Normal file
1
orbit/changes/9253-add-fleetd-table-pmset
Normal file
@ -0,0 +1 @@
|
||||
* Add `pmset` table extension to fleed for CIS check 2.9.1.
|
@ -16,9 +16,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/augeas"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/build"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
@ -34,6 +31,8 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/pkg/certificate"
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
"github.com/fleetdm/fleet/v4/pkg/secure"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
"github.com/oklog/run"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/authdb"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/csrutil_info"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/nvram_info"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/pmset"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/privaterelay"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/pwd_policy"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/user_login_settings"
|
||||
@ -27,6 +28,7 @@ func platformTables() []osquery.OsqueryPlugin {
|
||||
table.NewPlugin("csrutil_info", csrutil_info.Columns(), csrutil_info.Generate),
|
||||
table.NewPlugin("nvram_info", nvram_info.Columns(), nvram_info.Generate),
|
||||
table.NewPlugin("authdb", authdb.Columns(), authdb.Generate),
|
||||
table.NewPlugin("pmset", pmset.Columns(), pmset.Generate),
|
||||
|
||||
// Macadmins extension tables
|
||||
table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate),
|
||||
|
90
orbit/pkg/table/pmset/pmset_darwin.go
Normal file
90
orbit/pkg/table/pmset/pmset_darwin.go
Normal file
@ -0,0 +1,90 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
// Package pmset implements the table for getting macOS power settings
|
||||
// with the `pmset -g` command
|
||||
package pmset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/osquery/osquery-go/plugin/table"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Columns is the schema of the table.
|
||||
func Columns() []table.ColumnDefinition {
|
||||
return []table.ColumnDefinition{
|
||||
table.TextColumn("getting"), // pmset -g (aka GETTING option)
|
||||
table.TextColumn("json_result"),
|
||||
}
|
||||
}
|
||||
|
||||
var linePattern = regexp.MustCompile(`^(\w+)[\t\f\r ]+(\S[\S\t\f\r ]*)$`)
|
||||
|
||||
// Generate is called to return the results for the table at query time.
|
||||
//
|
||||
// Constraints for generating can be retrieved from the queryContext.
|
||||
func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
|
||||
getting := ""
|
||||
if constraints, ok := queryContext.Constraints["getting"]; ok {
|
||||
for _, constraint := range constraints.Constraints {
|
||||
if constraint.Operator == table.OperatorEquals {
|
||||
getting = constraint.Expression
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output, err := exec.CommandContext(ctx, "pmset", "-g", getting).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := parsePMSetOutput(output)
|
||||
|
||||
jsonResult, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []map[string]string{{
|
||||
"getting": getting,
|
||||
|
||||
"json_result": string(jsonResult),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func parsePMSetOutput(output []byte) map[string]interface{} {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(output))
|
||||
|
||||
result := make(map[string]interface{})
|
||||
curKey := ""
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line[0] != ' ' {
|
||||
curKey = strings.TrimSpace(line)
|
||||
result[curKey] = make(map[string]string)
|
||||
continue
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
loc := linePattern.FindStringSubmatch(line)
|
||||
if loc == nil {
|
||||
log.Debug().Str("line", line).Msg("failed to match line, ignoring")
|
||||
continue
|
||||
}
|
||||
if len(loc) != 3 {
|
||||
log.Debug().Str("line", line).Msg("invalid number of submatches")
|
||||
continue
|
||||
}
|
||||
m := result[curKey].(map[string]string)
|
||||
m[loc[1]] = loc[2]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
110
orbit/pkg/table/pmset/pmset_darwin_test.go
Normal file
110
orbit/pkg/table/pmset/pmset_darwin_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package pmset
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParsePMSetOutput(t *testing.T) {
|
||||
// pmset -g
|
||||
sampleOutput := []byte(`System-wide power settings:
|
||||
SleepDisabled 0
|
||||
Currently in use:
|
||||
lidwake 1
|
||||
lowpowermode 0
|
||||
standbydelayhigh 86400
|
||||
proximitywake 1
|
||||
standby 1
|
||||
standbydelaylow 0
|
||||
ttyskeepawake 1
|
||||
hibernatemode 3
|
||||
powernap 0
|
||||
gpuswitch 2
|
||||
hibernatefile /var/vm/sleepimage
|
||||
highstandbythreshold 50
|
||||
displaysleep 10
|
||||
womp 1
|
||||
networkoversleep 0
|
||||
sleep 1 (sleep prevented by bluetoothd, coreaudiod)
|
||||
acwake 0
|
||||
halfdim 1
|
||||
tcpkeepalive 1
|
||||
disksleep 10`)
|
||||
result := parsePMSetOutput(sampleOutput)
|
||||
|
||||
systemWide := result["System-wide power settings:"].(map[string]string)
|
||||
require.NotNil(t, systemWide)
|
||||
require.Equal(t, systemWide["SleepDisabled"], "0")
|
||||
|
||||
currInUse := result["Currently in use:"].(map[string]string)
|
||||
require.Equal(t, currInUse["powernap"], "0")
|
||||
require.Equal(t, currInUse["hibernatefile"], "/var/vm/sleepimage")
|
||||
require.Equal(t, currInUse["highstandbythreshold"], "50")
|
||||
require.Equal(t, currInUse["sleep"], "1 (sleep prevented by bluetoothd, coreaudiod)")
|
||||
|
||||
// pmset -g custom
|
||||
sampleOutput = []byte(`Battery Power:
|
||||
lidwake 1
|
||||
lowpowermode 1
|
||||
standbydelayhigh 86400
|
||||
proximitywake 0
|
||||
standby 1
|
||||
standbydelaylow 10800
|
||||
ttyskeepawake 1
|
||||
hibernatemode 3
|
||||
gpuswitch 2
|
||||
powernap 0
|
||||
hibernatefile /var/vm/sleepimage
|
||||
highstandbythreshold 50
|
||||
displaysleep 2
|
||||
womp 0
|
||||
networkoversleep 0
|
||||
sleep 1
|
||||
lessbright 1
|
||||
halfdim 1
|
||||
tcpkeepalive 1
|
||||
acwake 0
|
||||
disksleep 10
|
||||
AC Power:
|
||||
lidwake 1
|
||||
lowpowermode 0
|
||||
standbydelayhigh 86400
|
||||
proximitywake 1
|
||||
standby 1
|
||||
standbydelaylow 0
|
||||
ttyskeepawake 1
|
||||
hibernatemode 3
|
||||
powernap 0
|
||||
gpuswitch 2
|
||||
hibernatefile /var/vm/sleepimage
|
||||
highstandbythreshold 50
|
||||
displaysleep 10
|
||||
womp 1
|
||||
networkoversleep 0
|
||||
sleep 1
|
||||
acwake 0
|
||||
halfdim 1
|
||||
tcpkeepalive 1
|
||||
disksleep 10
|
||||
`)
|
||||
result = parsePMSetOutput(sampleOutput)
|
||||
|
||||
batteryPower := result["Battery Power:"].(map[string]string)
|
||||
require.NotNil(t, batteryPower)
|
||||
require.Equal(t, batteryPower["powernap"], "0")
|
||||
require.Equal(t, batteryPower["displaysleep"], "2")
|
||||
require.Equal(t, batteryPower["highstandbythreshold"], "50")
|
||||
require.Equal(t, batteryPower["hibernatefile"], "/var/vm/sleepimage")
|
||||
require.Equal(t, batteryPower["disksleep"], "10")
|
||||
|
||||
acPower := result["AC Power:"].(map[string]string)
|
||||
require.Equal(t, acPower["powernap"], "0")
|
||||
require.Equal(t, acPower["displaysleep"], "10")
|
||||
require.Equal(t, acPower["highstandbythreshold"], "50")
|
||||
require.Equal(t, acPower["hibernatefile"], "/var/vm/sleepimage")
|
||||
require.Equal(t, acPower["disksleep"], "10")
|
||||
}
|
@ -6,12 +6,12 @@ package privaterelay
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
tbl_common "github.com/fleetdm/fleet/v4/orbit/pkg/table/common"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
tbl_common "github.com/fleetdm/fleet/v4/orbit/pkg/table/common"
|
||||
"github.com/osquery/osquery-go/plugin/table"
|
||||
)
|
||||
|
111
orbit/tools/build/build.go
Normal file
111
orbit/tools/build/build.go
Normal file
@ -0,0 +1,111 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// This tool builds Orbit as macOS Universal Binary, codesigns it and notarizes it.
|
||||
// It currently doesn't support stapling of the binary.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
|
||||
"github.com/fleetdm/fleet/v4/pkg/buildpkg"
|
||||
"github.com/mitchellh/gon/package/zip"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Codesigning configuration
|
||||
codesignIdentity := os.Getenv("CODESIGN_IDENTITY")
|
||||
|
||||
// Notarization configuration
|
||||
acUsername := os.Getenv("AC_USERNAME")
|
||||
acPassword := os.Getenv("AC_PASSWORD")
|
||||
acTeamID := os.Getenv("AC_TEAM_ID")
|
||||
|
||||
codesign := false
|
||||
if codesignIdentity != "" {
|
||||
codesign = true
|
||||
} else {
|
||||
zlog.Info().Msg("skipping running codesign: CODESIGN_IDENTITY not set")
|
||||
}
|
||||
|
||||
notarize := false
|
||||
if acUsername != "" && acPassword != "" && acTeamID != "" {
|
||||
notarize = true
|
||||
} else {
|
||||
zlog.Info().Msg("skipping running notarization: AC_USERNAME, AC_PASSWORD, AC_TEAM_ID not all set")
|
||||
}
|
||||
|
||||
const (
|
||||
amdBinaryPath = "orbit-darwin-amd64"
|
||||
armBinaryPath = "orbit-darwin-arm64"
|
||||
binaryPath = "orbit-darwin"
|
||||
bundleIdentifier = "com.fleetdm.orbit"
|
||||
)
|
||||
if err := buildOrbit(amdBinaryPath, "amd64"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := buildOrbit(armBinaryPath, "arm64"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := buildpkg.MakeMacOSFatExecutable(binaryPath, amdBinaryPath, armBinaryPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.Remove(amdBinaryPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.Remove(armBinaryPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if codesign {
|
||||
codeSign := exec.Command("codesign", "-s", codesignIdentity, "-i", bundleIdentifier,
|
||||
"-f", "-v", "--timestamp", "--options", "runtime", binaryPath,
|
||||
)
|
||||
zlog.Info().Str("command", codeSign.String()).Msgf("signing %s", binaryPath)
|
||||
|
||||
codeSign.Stderr = os.Stderr
|
||||
codeSign.Stdout = os.Stdout
|
||||
if err := codeSign.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if notarize {
|
||||
const notarizationZip = "orbit.zip"
|
||||
// NOTE(lucas): The binary needs to be zipped in order to upload to Apple for Notarization.
|
||||
if err := zip.Zip(context.Background(), &zip.Options{Files: []string{binaryPath}, OutputPath: notarizationZip}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.Remove(notarizationZip)
|
||||
|
||||
if err := packaging.Notarize(notarizationZip, bundleIdentifier); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// TODO(lucas): packaging.Staple doesn't work on plain binaries.
|
||||
}
|
||||
}
|
||||
|
||||
func buildOrbit(binaryPath, arch string) error {
|
||||
/* #nosec G204 -- arguments are actually well defined */
|
||||
buildExec := exec.Command("go", "build",
|
||||
"-o", binaryPath,
|
||||
"./"+filepath.Join("orbit", "cmd", "orbit"),
|
||||
)
|
||||
buildExec.Env = append(os.Environ(), "GOOS=darwin", "GOARCH="+arch)
|
||||
buildExec.Stderr = os.Stderr
|
||||
buildExec.Stdout = os.Stdout
|
||||
|
||||
zlog.Info().Str("command", buildExec.String()).Str("arch", arch).Msg("build orbit executable")
|
||||
if err := buildExec.Run(); err != nil {
|
||||
return fmt.Errorf("compile for %s: %w", arch, err)
|
||||
}
|
||||
return nil
|
||||
}
|
131
pkg/buildpkg/buildpkg.go
Normal file
131
pkg/buildpkg/buildpkg.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Package buildpkg contains utilities to build Fleet components.
|
||||
package buildpkg
|
||||
|
||||
import (
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Adapted from Unlicensed https://github.com/randall77/makefat/blob/master/makefat.go
|
||||
const (
|
||||
magicFat64 = macho.MagicFat + 1 // TODO: add to stdlib (...when it works)
|
||||
|
||||
// Alignment wanted for each sub-file.
|
||||
// amd64 needs 12 bits, arm64 needs 14. We choose the max of all requirements here.
|
||||
alignBits = 14
|
||||
align = 1 << alignBits
|
||||
)
|
||||
|
||||
// MakeMacOSFatExecutable makes a macOS fat executable from the given binaries.
|
||||
func MakeMacOSFatExecutable(outPath string, inPaths ...string) error {
|
||||
// Read input files.
|
||||
type input struct {
|
||||
data []byte
|
||||
cpu uint32
|
||||
subcpu uint32
|
||||
offset int64
|
||||
}
|
||||
var inputs []input
|
||||
offset := int64(align)
|
||||
for _, i := range inPaths {
|
||||
data, err := ioutil.ReadFile(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) < 12 {
|
||||
return fmt.Errorf("file %s too small", i)
|
||||
}
|
||||
// All currently supported mac archs (386,amd64,arm,arm64) are little endian.
|
||||
magic := binary.LittleEndian.Uint32(data[0:4])
|
||||
if magic != macho.Magic32 && magic != macho.Magic64 {
|
||||
return fmt.Errorf("input %s is not a macho file, magic=%x", i, magic)
|
||||
}
|
||||
cpu := binary.LittleEndian.Uint32(data[4:8])
|
||||
subcpu := binary.LittleEndian.Uint32(data[8:12])
|
||||
inputs = append(inputs, input{data: data, cpu: cpu, subcpu: subcpu, offset: offset})
|
||||
offset += int64(len(data))
|
||||
offset = (offset + align - 1) / align * align
|
||||
}
|
||||
|
||||
// Decide on whether we're doing fat32 or fat64.
|
||||
sixtyfour := false
|
||||
if inputs[len(inputs)-1].offset >= 1<<32 || len(inputs[len(inputs)-1].data) >= 1<<32 {
|
||||
// fat64 doesn't seem to work:
|
||||
// - the resulting binary won't run.
|
||||
// - the resulting binary is parseable by lipo, but reports that the contained files are "hidden".
|
||||
// - the native OSX lipo can't make a fat64.
|
||||
return errors.New("files too large to fit into a fat binary")
|
||||
}
|
||||
|
||||
// Make output file.
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = out.Chmod(0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build a fat_header.
|
||||
var hdr []uint32
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, magicFat64)
|
||||
} else {
|
||||
hdr = append(hdr, macho.MagicFat)
|
||||
}
|
||||
hdr = append(hdr, uint32(len(inputs)))
|
||||
|
||||
// Build a fat_arch for each input file.
|
||||
for _, i := range inputs {
|
||||
hdr = append(hdr, i.cpu)
|
||||
hdr = append(hdr, i.subcpu)
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, uint32(i.offset>>32)) // big endian
|
||||
}
|
||||
hdr = append(hdr, uint32(i.offset))
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, uint32(len(i.data)>>32)) // big endian
|
||||
}
|
||||
hdr = append(hdr, uint32(len(i.data)))
|
||||
hdr = append(hdr, alignBits)
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, 0) // reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Write header.
|
||||
// Note that the fat binary header is big-endian, regardless of the
|
||||
// endianness of the contained files.
|
||||
err = binary.Write(out, binary.BigEndian, hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = int64(4 * len(hdr))
|
||||
|
||||
// Write each contained file.
|
||||
for _, i := range inputs {
|
||||
if offset < i.offset {
|
||||
_, err = out.Write(make([]byte, i.offset-offset))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = i.offset
|
||||
}
|
||||
_, err := out.Write(i.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset += int64(len(i.data))
|
||||
}
|
||||
err = out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -4,8 +4,6 @@ import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -17,6 +15,7 @@ import (
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
|
||||
"github.com/fleetdm/fleet/v4/pkg/buildpkg"
|
||||
"github.com/fleetdm/fleet/v4/pkg/secure"
|
||||
"github.com/kolide/kit/version"
|
||||
"github.com/mitchellh/gon/package/zip"
|
||||
@ -183,8 +182,8 @@ func createMacOSApp(version, authority string, notarize bool) error {
|
||||
}
|
||||
|
||||
// Make the fat exe and remove the separate binaries
|
||||
if err := makeFatExecutable(binaryPath, amdBinaryPath, armBinaryPath); err != nil {
|
||||
return fmt.Errorf("make fat exectuable: %w", err)
|
||||
if err := buildpkg.MakeMacOSFatExecutable(binaryPath, amdBinaryPath, armBinaryPath); err != nil {
|
||||
return fmt.Errorf("make fat executable: %w", err)
|
||||
}
|
||||
if err := os.Remove(amdBinaryPath); err != nil {
|
||||
return fmt.Errorf("remove amd64 binary: %w", err)
|
||||
@ -294,122 +293,3 @@ func compressDir(outPath, dirPath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Adapted from Unlicensed https://github.com/randall77/makefat/blob/master/makefat.go
|
||||
const (
|
||||
MagicFat64 = macho.MagicFat + 1 // TODO: add to stdlib (...when it works)
|
||||
|
||||
// Alignment wanted for each sub-file.
|
||||
// amd64 needs 12 bits, arm64 needs 14. We choose the max of all requirements here.
|
||||
alignBits = 14
|
||||
align = 1 << alignBits
|
||||
)
|
||||
|
||||
func makeFatExecutable(outPath string, inPaths ...string) error {
|
||||
// Read input files.
|
||||
type input struct {
|
||||
data []byte
|
||||
cpu uint32
|
||||
subcpu uint32
|
||||
offset int64
|
||||
}
|
||||
var inputs []input
|
||||
offset := int64(align)
|
||||
for _, i := range inPaths {
|
||||
data, err := ioutil.ReadFile(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) < 12 {
|
||||
return fmt.Errorf("file %s too small", i)
|
||||
}
|
||||
// All currently supported mac archs (386,amd64,arm,arm64) are little endian.
|
||||
magic := binary.LittleEndian.Uint32(data[0:4])
|
||||
if magic != macho.Magic32 && magic != macho.Magic64 {
|
||||
return fmt.Errorf("input %s is not a macho file, magic=%x", i, magic)
|
||||
}
|
||||
cpu := binary.LittleEndian.Uint32(data[4:8])
|
||||
subcpu := binary.LittleEndian.Uint32(data[8:12])
|
||||
inputs = append(inputs, input{data: data, cpu: cpu, subcpu: subcpu, offset: offset})
|
||||
offset += int64(len(data))
|
||||
offset = (offset + align - 1) / align * align
|
||||
}
|
||||
|
||||
// Decide on whether we're doing fat32 or fat64.
|
||||
sixtyfour := false
|
||||
if inputs[len(inputs)-1].offset >= 1<<32 || len(inputs[len(inputs)-1].data) >= 1<<32 {
|
||||
// fat64 doesn't seem to work:
|
||||
// - the resulting binary won't run.
|
||||
// - the resulting binary is parseable by lipo, but reports that the contained files are "hidden".
|
||||
// - the native OSX lipo can't make a fat64.
|
||||
return errors.New("files too large to fit into a fat binary")
|
||||
}
|
||||
|
||||
// Make output file.
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = out.Chmod(0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build a fat_header.
|
||||
var hdr []uint32
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, MagicFat64)
|
||||
} else {
|
||||
hdr = append(hdr, macho.MagicFat)
|
||||
}
|
||||
hdr = append(hdr, uint32(len(inputs)))
|
||||
|
||||
// Build a fat_arch for each input file.
|
||||
for _, i := range inputs {
|
||||
hdr = append(hdr, i.cpu)
|
||||
hdr = append(hdr, i.subcpu)
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, uint32(i.offset>>32)) // big endian
|
||||
}
|
||||
hdr = append(hdr, uint32(i.offset))
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, uint32(len(i.data)>>32)) // big endian
|
||||
}
|
||||
hdr = append(hdr, uint32(len(i.data)))
|
||||
hdr = append(hdr, alignBits)
|
||||
if sixtyfour {
|
||||
hdr = append(hdr, 0) // reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Write header.
|
||||
// Note that the fat binary header is big-endian, regardless of the
|
||||
// endianness of the contained files.
|
||||
err = binary.Write(out, binary.BigEndian, hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = int64(4 * len(hdr))
|
||||
|
||||
// Write each contained file.
|
||||
for _, i := range inputs {
|
||||
if offset < i.offset {
|
||||
_, err = out.Write(make([]byte, i.offset-offset))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = i.offset
|
||||
}
|
||||
_, err := out.Write(i.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset += int64(len(i.data))
|
||||
}
|
||||
err = out.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user