fleet/server/datastore/filesystem/software_installer.go
Victor Lyuboslavsky 68b7cf9141
Added signed URLs (#25197)
For #24869 

This subtask contains code to sign the CloudFront software installer and
bootstrap package URL using AWS SDK URL signer.
It works with the current bootstrap package delivery. For software
installers, fleetd will need to be modified to take advantage of this
URL in a future subtask (which will also include updated API contributor
docs).

My article on signed URLs, for context:
https://victoronsoftware.com/posts/cloudfront-signed-urls/

# Checklist for submitter

- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
2025-01-09 12:56:54 -06:00

144 lines
4.3 KiB
Go

package filesystem
import (
"context"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)
const softwareInstallersPrefix = "software-installers"
type installerNotFoundError struct{}
var _ fleet.NotFoundError = (*installerNotFoundError)(nil)
func (p installerNotFoundError) Error() string {
return "installer not found"
}
func (p installerNotFoundError) IsNotFound() bool {
return true
}
type SoftwareInstallerStore struct {
rootDir string
}
// NewSoftwareInstallerStore creates a software installer store using the
// local filesystem rooted at the provided rootDir.
func NewSoftwareInstallerStore(rootDir string) (*SoftwareInstallerStore, error) {
// ensure the directories exist (the provided rootDir and the
// softwareInstallersPrefix we create inside it).
dir := filepath.Join(rootDir, softwareInstallersPrefix)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
return &SoftwareInstallerStore{rootDir}, nil
}
// Get retrieves the requested software installer from the local filesystem.
// It is important that the caller closes the reader when done.
func (i *SoftwareInstallerStore) Get(ctx context.Context, installerID string) (io.ReadCloser, int64, error) {
path := i.pathForInstaller(installerID)
st, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return nil, int64(0), installerNotFoundError{}
}
return nil, 0, ctxerr.Wrap(ctx, err, "retrieving software installer from filesystem store")
}
sz := st.Size()
f, err := os.Open(path)
if err != nil {
return nil, sz, ctxerr.Wrap(ctx, err, "opening software installer file from filesystem store")
}
return f, sz, nil
}
// Put stores a software installer in the local filesystem.
func (i *SoftwareInstallerStore) Put(ctx context.Context, installerID string, content io.ReadSeeker) error {
path := i.pathForInstaller(installerID)
f, err := os.Create(path)
if err != nil {
return ctxerr.Wrap(ctx, err, "creating software installer file in filesystem store")
}
defer f.Close()
if _, err := io.Copy(f, content); err != nil {
return ctxerr.Wrap(ctx, err, "writing software installer file in filesystem store")
}
if err := f.Close(); err != nil {
return ctxerr.Wrap(ctx, err, "closing software installer file in filesystem store")
}
return nil
}
// Exists checks if a software installer exists in the filesystem for the ID.
func (i *SoftwareInstallerStore) Exists(ctx context.Context, installerID string) (bool, error) {
path := i.pathForInstaller(installerID)
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, ctxerr.Wrap(ctx, err, "looking up software installer in filesystem store")
}
return true, nil
}
func (i *SoftwareInstallerStore) Cleanup(ctx context.Context, usedInstallerIDs []string, removeCreatedBefore time.Time) (int, error) {
usedSet := make(map[string]struct{}, len(usedInstallerIDs))
for _, id := range usedInstallerIDs {
usedSet[id] = struct{}{}
}
baseDir := filepath.Join(i.rootDir, softwareInstallersPrefix)
dirEnts, err := os.ReadDir(baseDir)
if err != nil {
return 0, ctxerr.Wrap(ctx, err, "listing software installers in filesystem store")
}
// collect deletion errors so that it keeps going if possible
var errs []error
var count int
for _, de := range dirEnts {
if !de.Type().IsRegular() {
continue
}
if _, isUsed := usedSet[de.Name()]; isUsed {
continue
}
info, err := de.Info()
if err != nil {
return 0, ctxerr.Wrap(ctx, err, "get software installer modtime in filesystem store")
}
if info.ModTime().After(removeCreatedBefore) {
continue
}
if err := os.Remove(filepath.Join(baseDir, de.Name())); err != nil {
errs = append(errs, err)
} else {
count++
}
}
return count, ctxerr.Wrap(ctx, errors.Join(errs...), "delete unused software installers")
}
func (i *SoftwareInstallerStore) Sign(ctx context.Context, _ string) (string, error) {
return "", ctxerr.New(ctx, "signing not supported for software installers in filesystem store")
}
// pathForInstaller builds local filesystem path to identify the software
// installer.
func (i *SoftwareInstallerStore) pathForInstaller(installerID string) string {
return filepath.Join(i.rootDir, softwareInstallersPrefix, installerID)
}