Skip to content

Commit

Permalink
Add team_identifier to macOS software (#23766)
Browse files Browse the repository at this point in the history
Changes to add `team_identifier` signing information to macOS
applications on the `/api/latest/fleet/hosts/:id/software` API endpoint.

Docs: #23743

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [X] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [X] Added/updated tests
- [X] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes
- [X] If database migrations are included, checked table schema to
confirm autoupdate
- For database migrations:
- [X] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [X] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [ X Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [X] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).

---------

Co-authored-by: Tim Lee <[email protected]>
Co-authored-by: Ian Littman <[email protected]>
  • Loading branch information
3 people committed Nov 15, 2024
1 parent 13ca79f commit 07a16c5
Show file tree
Hide file tree
Showing 20 changed files with 455 additions and 101 deletions.
1 change: 1 addition & 0 deletions changes/8750-add-team_identifier-to-software
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint.
42 changes: 19 additions & 23 deletions docs/Contributing/Understanding-host-vitals.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,6 @@ SELECT
version AS version,
identifier AS extension_id,
browser_type AS browser,
'Browser plugin (Chrome)' AS type,
'chrome_extensions' AS source,
'' AS vendor,
'' AS installed_path
Expand All @@ -500,7 +499,6 @@ WITH cached_users AS (WITH cached_groups AS (select * from groups)
SELECT
name AS name,
version AS version,
'Package (deb)' AS type,
'' AS extension_id,
'' AS browser,
'deb_packages' AS source,
Expand All @@ -514,7 +512,6 @@ UNION
SELECT
package AS name,
version AS version,
'Package (Portage)' AS type,
'' AS extension_id,
'' AS browser,
'portage_packages' AS source,
Expand All @@ -527,7 +524,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (RPM)' AS type,
'' AS extension_id,
'' AS browser,
'rpm_packages' AS source,
Expand All @@ -540,7 +536,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (NPM)' AS type,
'' AS extension_id,
'' AS browser,
'npm_packages' AS source,
Expand All @@ -553,7 +548,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Chrome)' AS type,
identifier AS extension_id,
browser_type AS browser,
'chrome_extensions' AS source,
Expand All @@ -566,7 +560,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Firefox)' AS type,
identifier AS extension_id,
'firefox' AS browser,
'firefox_addons' AS source,
Expand All @@ -579,7 +572,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (Python)' AS type,
'' AS extension_id,
'' AS browser,
'python_packages' AS source,
Expand All @@ -603,7 +595,6 @@ WITH cached_users AS (WITH cached_groups AS (select * from groups)
SELECT
name AS name,
COALESCE(NULLIF(bundle_short_version, ''), bundle_version) AS version,
'Application (macOS)' AS type,
bundle_identifier AS bundle_identifier,
'' AS extension_id,
'' AS browser,
Expand All @@ -616,7 +607,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (Python)' AS type,
'' AS bundle_identifier,
'' AS extension_id,
'' AS browser,
Expand All @@ -629,7 +619,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Chrome)' AS type,
'' AS bundle_identifier,
identifier AS extension_id,
browser_type AS browser,
Expand All @@ -642,7 +631,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Firefox)' AS type,
'' AS bundle_identifier,
identifier AS extension_id,
'firefox' AS browser,
Expand All @@ -655,7 +643,6 @@ UNION
SELECT
name As name,
version AS version,
'Browser plugin (Safari)' AS type,
'' AS bundle_identifier,
'' AS extension_id,
'' AS browser,
Expand All @@ -668,7 +655,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (Homebrew)' AS type,
'' AS bundle_identifier,
'' AS extension_id,
'' AS browser,
Expand All @@ -679,9 +665,27 @@ SELECT
FROM homebrew_packages;
```
## software_macos_codesign
- Description: A software override query[^1] to append codesign information to macOS software entries. Requires `fleetd`
- Platforms: darwin
- Discovery query:
```sql
SELECT 1 FROM osquery_registry WHERE active = true AND registry = 'table' AND name = 'codesign'
```
- Query:
```sql
SELECT a.path, c.team_identifier
FROM apps a
JOIN codesign c ON a.path = c.path
```
## software_macos_firefox
- Description: A software override query[^1] to differentiate between Firefox and Firefox ESR on macOS. Requires `fleetd`
- Description: A software override query[^1] to differentiate between Firefox and Firefox ESR on macOS. Requires `fleetd`
- Platforms: darwin
Expand Down Expand Up @@ -709,7 +713,6 @@ WITH app_paths AS (
ELSE 'Firefox.app'
END AS name,
COALESCE(NULLIF(apps.bundle_short_version, ''), apps.bundle_version) AS version,
'Application (macOS)' AS type,
apps.bundle_identifier AS bundle_identifier,
'' AS extension_id,
'' AS browser,
Expand Down Expand Up @@ -740,7 +743,6 @@ WITH cached_users AS (WITH cached_groups AS (select * from groups)
SELECT
name,
version,
'IDE extension (VS Code)' AS type,
'' AS bundle_identifier,
uuid AS extension_id,
'' AS browser,
Expand All @@ -764,7 +766,6 @@ WITH cached_users AS (WITH cached_groups AS (select * from groups)
SELECT
name AS name,
version AS version,
'Program (Windows)' AS type,
'' AS extension_id,
'' AS browser,
'programs' AS source,
Expand All @@ -775,7 +776,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (Python)' AS type,
'' AS extension_id,
'' AS browser,
'python_packages' AS source,
Expand All @@ -786,7 +786,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (IE)' AS type,
'' AS extension_id,
'' AS browser,
'ie_extensions' AS source,
Expand All @@ -797,7 +796,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Chrome)' AS type,
identifier AS extension_id,
browser_type AS browser,
'chrome_extensions' AS source,
Expand All @@ -808,7 +806,6 @@ UNION
SELECT
name AS name,
version AS version,
'Browser plugin (Firefox)' AS type,
identifier AS extension_id,
'firefox' AS browser,
'firefox_addons' AS source,
Expand All @@ -819,7 +816,6 @@ UNION
SELECT
name AS name,
version AS version,
'Package (Chocolatey)' AS type,
'' AS extension_id,
'' AS browser,
'chocolatey_packages' AS source,
Expand Down
1 change: 1 addition & 0 deletions orbit/changes/add-codesign-table
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Added `codesign` table to provide the "Team identifier" of macOS applications.
100 changes: 100 additions & 0 deletions orbit/pkg/table/codesign/codesign_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//go:build darwin
// +build darwin

// Package codesign implements an extension osquery table
// to get signature information of macOS applications.
package codesign

import (
"bufio"
"bytes"
"context"
"errors"
"os/exec"
"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{
// path is the absolute path to the app bundle.
// It's required and only supports the equality operator.
table.TextColumn("path"),
// team_identifier is the "Team ID", aka "Signature ID", "Developer ID".
// The value is "" if the app doesn't have a team identifier set.
// (this is the case for example for builtin Apple apps).
//
// See https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/.
table.TextColumn("team_identifier"),
}
}

// 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) {
constraints, ok := queryContext.Constraints["path"]
if !ok || len(constraints.Constraints) == 0 {
return nil, errors.New("missing path")
}

var paths []string
for _, constraint := range constraints.Constraints {
if constraint.Operator != table.OperatorEquals {
return nil, errors.New("only supported operator for 'path' is '='")
}
paths = append(paths, constraint.Expression)
}

var rows []map[string]string
for _, path := range paths {
row := map[string]string{
"path": path,
"team_identifier": "",
}
output, err := exec.CommandContext(ctx, "/usr/bin/codesign",
// `codesign --display` does not perform any verification of executables/resources,
// it just parses and displays signature information read from the `Contents` folder.
"--display",
// If we don't set verbose it only prints the executable path.
"--verbose",
path,
).CombinedOutput() // using CombinedOutput because output is in stderr and stdout is empty.
if err != nil {
// Logging as debug to prevent non signed apps to generate a lot of logged errors.
log.Debug().Err(err).Str("output", string(output)).Str("path", path).Msg("codesign --display failed")
rows = append(rows, row)
continue
}
info := parseCodesignOutput(output)
row["team_identifier"] = info.teamIdentifier
rows = append(rows, row)
}

return rows, nil
}

type parsedInfo struct {
teamIdentifier string
}

func parseCodesignOutput(output []byte) parsedInfo {
const teamIdentifierPrefix = "TeamIdentifier="

scanner := bufio.NewScanner(bytes.NewReader(output))
var info parsedInfo
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, teamIdentifierPrefix) {
info.teamIdentifier = strings.TrimSpace(strings.TrimPrefix(line, teamIdentifierPrefix))
// "not set" is usually displayed on Apple builtin apps.
if info.teamIdentifier == "not set" {
info.teamIdentifier = ""
}
}
}
return info
}
3 changes: 3 additions & 0 deletions orbit/pkg/table/extension_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"

"github.com/fleetdm/fleet/v4/orbit/pkg/table/authdb"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/codesign"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/csrutil_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/diskutil/apfs"
Expand Down Expand Up @@ -92,6 +93,8 @@ func PlatformTables(opts PluginOpts) ([]osquery.OsqueryPlugin, error) {

// Table for parsing Apple Property List files, which are typically stored in ~/Library/Preferences/
dataflattentable.TablePlugin(log.Logger, dataflattentable.PlistType), // table name is "parse_plist"

table.NewPlugin("codesign", codesign.Columns(), codesign.Generate),
}

// append platform specific tables
Expand Down
25 changes: 25 additions & 0 deletions schema/osquery_fleet_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4171,6 +4171,31 @@
"url": "https://fleetdm.com/tables/cis_audit",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/cis_audit.yml"
},
{
"name": "codesign",
"platforms": [
"darwin"
],
"description": "Retrieves codesign information of a given .app path. It doesn't perform (expensive) verification, it just parses the signature from the 'Contents' folder using the \"codesign --display\" command.",
"columns": [
{
"name": "path",
"type": "text",
"required": true,
"description": "Path is the absolute path to the app folder."
},
{
"name": "team_identifier",
"type": "text",
"required": false,
"description": "Unique 10-character string generated by Apple that's assigned to a developer account to sign packages. This value is empty on unsigned applications and built-in Apple applications."
}
],
"notes": "This table is not a core osquery table. It is included as part of Fleet's agent ([fleetd](https://fleetdm.com/docs/get-started/anatomy#fleetd)).",
"evented": false,
"url": "https://fleetdm.com/tables/codesign",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/codesign.yml"
},
{
"name": "connected_displays",
"description": "Provides information about the connected displays of the machine.",
Expand Down
15 changes: 15 additions & 0 deletions schema/tables/codesign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: codesign
platforms:
- darwin
description: Retrieves codesign information of a given .app path. It doesn't perform (expensive) verification, it just parses the signature from the 'Contents' folder using the "codesign --display" command.
columns:
- name: path
type: text
required: true
description: Path is the absolute path to the app folder.
- name: team_identifier
type: text
required: false
description: Unique 10-character string generated by Apple that's assigned to a developer account to sign packages. This value is empty on unsigned applications and built-in Apple applications.
notes: This table is not a core osquery table. It is included as part of Fleet's agent ([fleetd](https://fleetdm.com/docs/get-started/anatomy#fleetd)).
evented: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tables

import (
"database/sql"
"fmt"
)

func init() {
MigrationClient.AddMigration(Up_20241110152839, Down_20241110152839)
}

func Up_20241110152839(tx *sql.Tx) error {
if _, err := tx.Exec(`
ALTER TABLE host_software_installed_paths ADD COLUMN team_identifier VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''`,
); err != nil {
return fmt.Errorf("failed to add team_identifier to host_software_installed_paths table: %w", err)
}
return nil
}

func Down_20241110152839(tx *sql.Tx) error {
return nil
}
Loading

0 comments on commit 07a16c5

Please sign in to comment.