Skip to content

Commit

Permalink
Changes to migrate to new TUF repository (#23588)
Browse files Browse the repository at this point in the history
# Changes

- orbit >= 1.38.0, when configured to connect to
https://tuf.fleetctl.com (existing fleetd deployments) will now connect
to https://updates.fleetdm.com and start using the metadata in path
`/opt/orbit/updates-metadata.json`.
- orbit >= 1.38.0, when configured to connect to some custom TUF (not
Fleet's TUFs) will copy `/opt/orbit/tuf-metadata.json` to
`/opt/orbit/updates-metadata.json` (if it doesn't exist) and start using
the latter.
- fleetctl `4.63.0` will now generate artifacts using
https://updates.fleetdm.com by default (or a custom TUF if
`--update-url` is set) and generate two (same file) metadata files
`/opt/orbit/updates-metadata.json` and the legacy one to support
downgrades `/opt/orbit/tuf-metadata.json`.
- fleetctl `4.62.0` when configured to use custom TUF (not Fleet's TUF)
will generate just the legacy metadata file
`/opt/orbit/tuf-metadata.json`.

## User stories

See "User stories" in
fleetdm/confidential#8488.

- [x] Update `update.defaultRootMetadata` and `update.DefaultURL` when
the new repository is ready.
- [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] Added/updated tests
- [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)).
  • Loading branch information
lucasmrod authored Jan 10, 2025
1 parent 5873cb9 commit 009f54b
Show file tree
Hide file tree
Showing 23 changed files with 1,149 additions and 106 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/check-automated-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ jobs:
make generate-doc
if [[ $(git diff) ]]; then
echo "❌ fail: uncommited changes"
echo "please run `make generate-doc` and commit the changes"
echo "please run 'make generate-doc' and commit the changes"
git --no-pager diff
exit 1
fi
Expand All @@ -62,6 +63,7 @@ jobs:
./node_modules/sails/bin/sails.js run generate-merged-schema
if [[ $(git diff) ]]; then
echo "❌ fail: uncommited changes"
echo "please run `cd website && npm install && ./node_modules/sails/bin/sails.js run generate-merged-schema` and commit the changes"
echo "please run 'cd website && npm install && ./node_modules/sails/bin/sails.js run generate-merged-schema' and commit the changes"
git --no-pager diff
exit 1
fi
3 changes: 2 additions & 1 deletion cmd/fleetctl/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

eefleetctl "github.com/fleetdm/fleet/v4/ee/fleetctl"
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
"github.com/fleetdm/fleet/v4/pkg/filepath_windows"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -127,7 +128,7 @@ func packageCommand() *cli.Command {
&cli.StringFlag{
Name: "update-url",
Usage: "URL for update server",
Value: "https://tuf.fleetctl.com",
Value: update.DefaultURL,
Destination: &opt.UpdateURL,
},
&cli.StringFlag{
Expand Down
2 changes: 1 addition & 1 deletion cmd/fleetctl/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ func previewResetCommand() *cli.Command {
return fmt.Errorf("Failed to stop orbit: %w", err)
}

if err := os.RemoveAll(filepath.Join(orbitDir, "tuf-metadata.json")); err != nil {
if err := os.RemoveAll(filepath.Join(orbitDir, update.MetadataFileName)); err != nil {
return fmt.Errorf("failed to remove preview update metadata file: %w", err)
}
if err := os.RemoveAll(filepath.Join(orbitDir, "bin")); err != nil {
Expand Down
1 change: 1 addition & 0 deletions orbit/changes/8488-new-tuf-repository
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Added changes to migrate to new TUF repository from https://tuf.fleetctl.com to https://updates.fleetdm.com.
65 changes: 57 additions & 8 deletions orbit/cmd/orbit/orbit.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,26 @@ func main() {
}
}

localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json"))
if updateURL := c.String("update-url"); updateURL != update.OldFleetTUFURL && updateURL != update.DefaultURL {
// Migrate agents running with a custom TUF to use the new metadata file.
// We'll keep the old metadata file to support downgrades.
newMetadataFilePath := filepath.Join(c.String("root-dir"), update.MetadataFileName)
ok, err := file.Exists(newMetadataFilePath)
if err != nil {
// If we cannot stat this file then we cannot do other operations on it thus we fail with fatal error.
log.Fatal().Err(err).Msg("failed to check for new metadata file path")
}
if !ok {
oldMetadataFilePath := filepath.Join(c.String("root-dir"), update.OldMetadataFileName)
err := file.Copy(oldMetadataFilePath, newMetadataFilePath, constant.DefaultFileMode)
if err != nil {
// If we cannot write to this file then we cannot do other operations on it thus we fail with fatal error.
log.Fatal().Err(err).Msg("failed to copy new metadata file path")
}
}
}

localStore, err := filestore.New(filepath.Join(c.String("root-dir"), update.MetadataFileName))
if err != nil {
log.Fatal().Err(err).Msg("create local metadata store")
}
Expand Down Expand Up @@ -503,6 +522,29 @@ func main() {

opt.RootDirectory = c.String("root-dir")
opt.ServerURL = c.String("update-url")
checkAccessToNewTUF := false
if opt.ServerURL == update.OldFleetTUFURL {
//
// This only gets executed on orbit 1.38.0+
// when it is configured to connect to the old TUF server
// (fleetd instances packaged before the migration,
// built by fleetctl previous to v4.63.0).
//

if ok := update.HasAccessToNewTUFServer(opt); ok {
// orbit 1.38.0+ will use the new TUF server if it has access to the new TUF repository.
opt.ServerURL = update.DefaultURL
} else {
// orbit 1.38.0+ will use the old TUF server and old metadata path if it does not have access
// to the new TUF repository. During its execution (update.Runner) it will exit once it finds
// out it can access the new TUF server.
localStore, err = filestore.New(filepath.Join(c.String("root-dir"), update.OldMetadataFileName))
if err != nil {
log.Fatal().Err(err).Msg("create local old metadata store")
}
checkAccessToNewTUF = true
}
}
opt.LocalStore = localStore
opt.InsecureTransport = c.Bool("insecure")
opt.ServerCertificatePath = c.String("update-tls-certificate")
Expand Down Expand Up @@ -545,13 +587,12 @@ func main() {
var updater *update.Updater
var updateRunner *update.Runner
if !c.Bool("disable-updates") || c.Bool("dev-mode") {
updater, err = update.NewUpdater(opt)
updater, err := update.NewUpdater(opt)
if err != nil {
return fmt.Errorf("create updater: %w", err)
}

if err := updater.UpdateMetadata(); err != nil {
log.Info().Err(err).Msg("update metadata, using saved metadata")
log.Info().Err(err).Msg("update metadata")
}

signaturesExpiredAtStartup := updater.SignaturesExpired()
Expand All @@ -571,6 +612,7 @@ func main() {
CheckInterval: c.Duration("update-interval"),
Targets: targets,
SignaturesExpiredAtStartup: signaturesExpiredAtStartup,
CheckAccessToNewTUF: checkAccessToNewTUF,
})
if err != nil {
return err
Expand Down Expand Up @@ -1394,10 +1436,17 @@ func getFleetdComponentPaths(
log.Error().Err(err).Msg("update metadata before getting components")
}

// "root", "targets", or "snapshot" signatures have expired, thus
// we attempt to get local paths for the targets (updater.Get will fail
// because of the expired signatures).
if updater.SignaturesExpired() {
//
// updater.SignaturesExpired():
// "root", "targets", or "snapshot" signatures have expired, thus
// we attempt to get local paths for the targets (updater.Get will fail
// because of the expired signatures).
//
// updater.LookupsFail():
// Any of the targets fails to load thus we resort to the local executables we have.
// This could happen if the new TUF server is down during the first run of the TUF migration.
//
if updater.SignaturesExpired() || updater.LookupsFail() {
log.Error().Err(err).Msg("expired metadata, using local targets")

// Attempt to get local path of osqueryd.
Expand Down
2 changes: 1 addition & 1 deletion orbit/cmd/orbit/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var shellCommand = &cli.Command{
return fmt.Errorf("initialize root dir: %w", err)
}

localStore, err := filestore.New(filepath.Join(c.String("root-dir"), "tuf-metadata.json"))
localStore, err := filestore.New(filepath.Join(c.String("root-dir"), update.MetadataFileName))
if err != nil {
log.Fatal().Err(err).Msg("failed to create local metadata store")
}
Expand Down
13 changes: 12 additions & 1 deletion orbit/pkg/packaging/packaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (u UpdatesData) String() string {
}

func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
localStore, err := filestore.New(filepath.Join(updateOpt.RootDirectory, "tuf-metadata.json"))
localStore, err := filestore.New(filepath.Join(updateOpt.RootDirectory, update.MetadataFileName))
if err != nil {
return nil, fmt.Errorf("failed to create local metadata store: %w", err)
}
Expand Down Expand Up @@ -236,6 +236,17 @@ func InitializeUpdates(updateOpt update.Options) (*UpdatesData, error) {
}
}

// Copy the new metadata file to the old location (pre-migration) to
// support orbit downgrades to 1.37.0 or lower.
//
// Once https://tuf.fleetctl.com is brought down (which means downgrades to 1.37.0 or
// lower won't be possible), we can remove this copy.
oldMetadataPath := filepath.Join(updateOpt.RootDirectory, update.OldMetadataFileName)
newMetadataPath := filepath.Join(updateOpt.RootDirectory, update.MetadataFileName)
if err := file.Copy(newMetadataPath, oldMetadataPath, constant.DefaultFileMode); err != nil {
return nil, fmt.Errorf("failed to create %s copy: %w", oldMetadataPath, err)
}

return &UpdatesData{
OrbitPath: orbitPath,
OrbitVersion: orbitCustom.Version,
Expand Down
4 changes: 2 additions & 2 deletions orbit/pkg/update/options_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

var defaultOptions = Options{
RootDirectory: "/opt/orbit",
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
ServerURL: DefaultURL,
RootKeys: defaultRootMetadata,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Targets: DarwinTargets,
Expand Down
4 changes: 2 additions & 2 deletions orbit/pkg/update/options_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

var defaultOptions = Options{
RootDirectory: "/opt/orbit",
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
ServerURL: DefaultURL,
RootKeys: defaultRootMetadata,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Targets: LinuxTargets,
Expand Down
4 changes: 2 additions & 2 deletions orbit/pkg/update/options_linux_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

var defaultOptions = Options{
RootDirectory: "/opt/orbit",
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
ServerURL: DefaultURL,
RootKeys: defaultRootMetadata,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Targets: LinuxArm64Targets,
Expand Down
4 changes: 2 additions & 2 deletions orbit/pkg/update/options_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

var defaultOptions = Options{
RootDirectory: `C:\Program Files\Orbit`,
ServerURL: defaultURL,
RootKeys: defaultRootKeys,
ServerURL: DefaultURL,
RootKeys: defaultRootMetadata,
LocalStore: client.MemoryLocalStore(),
InsecureTransport: false,
Targets: WindowsTargets,
Expand Down
20 changes: 20 additions & 0 deletions orbit/pkg/update/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type RunnerOptions struct {
// An expired signature for the "timestamp" role does not cause issues
// at start up (the go-tuf libary allows loading the targets).
SignaturesExpiredAtStartup bool

// CheckAccessToNewTUF, if set to true, will perform a check of access to the new Fleet TUF
// server on every update interval (once the access is confirmed it will store the confirmation
// of access to disk and will exit to restart).
CheckAccessToNewTUF bool
}

// Runner is a specialized runner for an Updater. It is designed with Execute and
Expand Down Expand Up @@ -121,6 +126,14 @@ func NewRunner(updater *Updater, opt RunnerOptions) (*Runner, error) {
return runner, nil
}

if _, err := updater.Lookup(constant.OrbitTUFTargetName); errors.Is(err, client.ErrNoLocalSnapshot) {
// Return early and skip optimization, this will cause an unnecessary auto-update of orbit
// but allows orbit to start up if there's no local metadata AND if the TUF server is down
// (which may be the case during the migration from https://tuf.fleetctl.com to
// https://updates.fleetdm.com).
return runner, nil
}

// Initialize the hashes of the local files for all tracked targets.
//
// This is an optimization to not compute the hash of the local files every opt.CheckInterval
Expand Down Expand Up @@ -204,6 +217,13 @@ func (r *Runner) Execute() error {
case <-ticker.C:
ticker.Reset(r.opt.CheckInterval)

if r.opt.CheckAccessToNewTUF {
if HasAccessToNewTUFServer(r.updater.opt) {
log.Info().Msg("detected access to new TUF repository, exiting")
return nil
}
}

if r.opt.SignaturesExpiredAtStartup {
if r.updater.SignaturesExpired() {
log.Debug().Msg("signatures still expired")
Expand Down
Loading

0 comments on commit 009f54b

Please sign in to comment.