diff --git a/cmd/hauler/cli/store/sync.go b/cmd/hauler/cli/store/sync.go index 255e554..ae45f57 100644 --- a/cmd/hauler/cli/store/sync.go +++ b/cmd/hauler/cli/store/sync.go @@ -135,12 +135,14 @@ func SyncCmd(ctx context.Context, o *flags.SyncOpts, s *store.Layout, rso *flags if err != nil { return fmt.Errorf("failed to fetch product manifest for [%s]: %w", productName, err) } - err = ExtractCmd(ctx, &flags.ExtractOpts{StoreRootOpts: o.StoreRootOpts}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag)) + // The manifest is output for the user, so it goes to workDir, not tempDir (removed when sync returns). + workDir := flags.ResolveWorkDir(ro) + err = ExtractCmd(ctx, &flags.ExtractOpts{StoreRootOpts: o.StoreRootOpts, DestinationDir: workDir}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag)) if err != nil { return err } fileName := fmt.Sprintf("%s-manifest.yaml", parts[0]) - fi, err := os.Open(fileName) + fi, err := os.Open(filepath.Join(workDir, fileName)) if err != nil { return err } diff --git a/internal/flags/cli.go b/internal/flags/cli.go index c68cadc..e64ca4c 100644 --- a/internal/flags/cli.go +++ b/internal/flags/cli.go @@ -6,6 +6,11 @@ type CliRootOpts struct { LogLevel string HaulerDir string IgnoreErrors bool +<<<<<<< HEAD +======= + AuditLevel string + WorkDir string +>>>>>>> 29a5c69 (fixed bug with store sync extraction into unwritable directory (#737)) } func AddRootFlags(cmd *cobra.Command, ro *CliRootOpts) { @@ -13,5 +18,11 @@ func AddRootFlags(cmd *cobra.Command, ro *CliRootOpts) { pf.StringVarP(&ro.LogLevel, "log-level", "l", "info", "Set the logging level (i.e. info, debug, warn)") pf.StringVarP(&ro.HaulerDir, "haulerdir", "d", "", "Set the location of the hauler directory (default $HOME/.hauler)") +<<<<<<< HEAD pf.BoolVar(&ro.IgnoreErrors, "ignore-errors", false, "Ignore/Bypass errors (i.e. warn on error) (defaults false)") +======= + pf.BoolVar(&ro.IgnoreErrors, "ignore-errors", false, "Warn and continue instead of failing on errors, including storing images that failed verification (defaults false)") + pf.StringVar(&ro.AuditLevel, "audit-level", "", "Set the audit logging level (none, standard, verbose) (defaults standard)") + pf.StringVarP(&ro.WorkDir, "work-dir", "w", "", "(Optional) Set the directory for output that commands would otherwise write to the current directory (default: current directory)") +>>>>>>> 29a5c69 (fixed bug with store sync extraction into unwritable directory (#737)) } diff --git a/internal/flags/store.go b/internal/flags/store.go index 2ef89bf..d11b665 100644 --- a/internal/flags/store.go +++ b/internal/flags/store.go @@ -61,3 +61,26 @@ func (o *StoreRootOpts) Store(ctx context.Context) (*store.Layout, error) { } return s, nil } +<<<<<<< HEAD +======= + +// resolveHaulerDir mirrors other variable detection, but duplicated to avoid an import cycle +func resolveHaulerDir(ro *CliRootOpts) string { + if ro != nil && ro.HaulerDir != "" { + return ro.HaulerDir + } + if d := os.Getenv(consts.HaulerDir); d != "" { + return d + } + home, _ := os.UserHomeDir() + return filepath.Join(home, consts.DefaultHaulerDirName) +} + +// ResolveWorkDir returns the configured output dir, or "" to mean the current directory (legacy behavior). +func ResolveWorkDir(ro *CliRootOpts) string { + if ro != nil && ro.WorkDir != "" { + return ro.WorkDir + } + return os.Getenv(consts.HaulerWorkDir) +} +>>>>>>> 29a5c69 (fixed bug with store sync extraction into unwritable directory (#737)) diff --git a/internal/mapper/filestore.go b/internal/mapper/filestore.go index c0d9fd9..2ee3003 100644 --- a/internal/mapper/filestore.go +++ b/internal/mapper/filestore.go @@ -89,8 +89,10 @@ func (s *pusher) Push(ctx context.Context, desc ocispec.Descriptor) (ccontent.Wr } fullFileName := filepath.Join(destDir, filename) - // Guard against path traversal (e.g. filename containing "../") - if !strings.HasPrefix(fullFileName, destDir+string(filepath.Separator)) { + // Guard against path traversal (e.g. "../"). filepath.Rel handles this + // correctly even when destDir is "/", unlike a plain prefix check. + rel, err := filepath.Rel(destDir, fullFileName) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { return nil, fmt.Errorf("path_traversal_disallowed: %q resolves outside destination dir", filename) } diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go index 8a3f28a..a1a009e 100644 --- a/pkg/consts/consts.go +++ b/pkg/consts/consts.go @@ -87,10 +87,26 @@ const ( CollectionGroup = "collection.hauler.cattle.io" // environment variables +<<<<<<< HEAD HaulerDir = "HAULER_DIR" HaulerTempDir = "HAULER_TEMP_DIR" HaulerStoreDir = "HAULER_STORE_DIR" HaulerIgnoreErrors = "HAULER_IGNORE_ERRORS" +======= + HaulerDir = "HAULER_DIR" + HaulerTempDir = "HAULER_TEMP_DIR" + HaulerWorkDir = "HAULER_WORK_DIR" + HaulerStoreDir = "HAULER_STORE_DIR" + HaulerIgnoreErrors = "HAULER_IGNORE_ERRORS" + HaulerRetries = "HAULER_RETRIES" + HaulerConcurrency = "HAULER_CONCURRENCY" + HaulerBlobConcurrency = "HAULER_BLOB_CONCURRENCY" + HaulerLogLevel = "HAULER_LOG_LEVEL" + HaulerAuditLevel = "HAULER_AUDIT_LEVEL" + + CaFile = "CA_FILE" + InsecureSkipTLSVerify = "INSECURE_SKIP_TLS_VERIFY" +>>>>>>> 29a5c69 (fixed bug with store sync extraction into unwritable directory (#737)) // container files and directories ImageManifestFile = "manifest.json"