mirror of
https://github.com/hashicorp/packer.git
synced 2026-05-28 04:35:38 -04:00
move implementation to hcp-sbom
This commit is contained in:
parent
0929f45e72
commit
520e8dd5f4
2 changed files with 477 additions and 189 deletions
|
|
@ -4,13 +4,11 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hcpSbomProvisioner "github.com/hashicorp/packer/provisioner/hcp-sbom"
|
||||
|
|
@ -247,65 +245,18 @@ func (p *DebuggedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, co
|
|||
// SBOMInternalProvisioner is a wrapper provisioner for the `hcp-sbom` provisioner
|
||||
// that sets the path for SBOM file download and, after the successful execution of
|
||||
// the `hcp-sbom` provisioner, compresses the SBOM and prepares the data for API
|
||||
// integration. It also supports native SBOM generation by automatically downloading
|
||||
// and running a scanner tool (default: Syft) on the remote host.
|
||||
// integration.
|
||||
type SBOMInternalProvisioner struct {
|
||||
Provisioner packersdk.Provisioner
|
||||
CompressedData []byte
|
||||
SBOMFormat hcpPackerModels.HashicorpCloudPacker20230101SbomFormat
|
||||
SBOMName string
|
||||
|
||||
// Native SBOM generation configuration
|
||||
EnableNativeGeneration bool // Flag to enable/disable native SBOM generation
|
||||
ScannerURL string // URL to scanner tool (if empty, auto-download Syft based on detected OS)
|
||||
ScannerChecksum string // Expected SHA256 checksum of scanner binary for verification
|
||||
ScannerArgs []string // Arguments to pass to the scanner tool
|
||||
ScanPath string // Path to scan on remote host (default: "/")
|
||||
}
|
||||
|
||||
func (p *SBOMInternalProvisioner) ConfigSpec() hcldec.ObjectSpec { return p.Provisioner.ConfigSpec() }
|
||||
func (p *SBOMInternalProvisioner) FlatConfig() interface{} { return nil }
|
||||
|
||||
func (p *SBOMInternalProvisioner) Prepare(raws ...interface{}) error {
|
||||
// Parse configuration for native generation options
|
||||
for _, raw := range raws {
|
||||
if config, ok := raw.(map[string]interface{}); ok {
|
||||
if val, ok := config["enable_native_generation"].(bool); ok {
|
||||
p.EnableNativeGeneration = val
|
||||
}
|
||||
if val, ok := config["scanner_url"].(string); ok {
|
||||
p.ScannerURL = val
|
||||
}
|
||||
if val, ok := config["scanner_checksum"].(string); ok {
|
||||
p.ScannerChecksum = val
|
||||
}
|
||||
if val, ok := config["scanner_args"].([]interface{}); ok {
|
||||
p.ScannerArgs = make([]string, len(val))
|
||||
for i, arg := range val {
|
||||
if argStr, ok := arg.(string); ok {
|
||||
p.ScannerArgs[i] = argStr
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := config["scan_path"].(string); ok {
|
||||
p.ScanPath = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if p.ScanPath == "" {
|
||||
p.ScanPath = "/"
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
if p.EnableNativeGeneration {
|
||||
// If checksum is provided, URL must also be provided
|
||||
if p.ScannerChecksum != "" && p.ScannerURL == "" {
|
||||
return fmt.Errorf("scanner_checksum requires scanner_url to be specified")
|
||||
}
|
||||
}
|
||||
|
||||
return p.Provisioner.Prepare(raws...)
|
||||
}
|
||||
|
||||
|
|
@ -313,139 +264,7 @@ func (p *SBOMInternalProvisioner) Provision(
|
|||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
// Check if native generation is enabled
|
||||
if !p.EnableNativeGeneration {
|
||||
// Original behavior: user provides SBOM file
|
||||
ui.Say("Using existing SBOM provisioner behavior (user-provided SBOM)")
|
||||
return p.provisionWithExistingSBOM(ctx, ui, comm, generatedData)
|
||||
}
|
||||
|
||||
// Native generation enabled
|
||||
ui.Say("Native SBOM generation enabled")
|
||||
|
||||
var osType, osArch string
|
||||
var err error
|
||||
|
||||
// Only detect OS if scanner_url is NOT provided
|
||||
if p.ScannerURL == "" {
|
||||
ui.Say("No scanner URL provided, detecting remote OS/Arch...")
|
||||
osType, osArch, err = p.detectRemoteOS(ctx, ui, comm, generatedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect remote OS: %s", err)
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Detected: OS=%s, Arch=%s", osType, osArch))
|
||||
} else {
|
||||
ui.Say("Scanner URL provided, skipping OS detection")
|
||||
// User provided scanner URL, assume they know their platform
|
||||
osType = "unknown"
|
||||
osArch = "unknown"
|
||||
}
|
||||
|
||||
return p.provisionWithNativeGeneration(ctx, ui, comm, osType, osArch)
|
||||
}
|
||||
|
||||
// detectRemoteOS performs OS detection within the provisioner
|
||||
func (p *SBOMInternalProvisioner) detectRemoteOS(ctx context.Context, ui packersdk.Ui,
|
||||
comm packersdk.Communicator,
|
||||
generatedData map[string]interface{}) (string, string, error) {
|
||||
// First check if already detected (from generatedData)
|
||||
if osType, ok := generatedData["OSType"].(string); ok {
|
||||
if osArch, ok := generatedData["OSArch"].(string); ok {
|
||||
ui.Message("Using previously detected OS information from generated data")
|
||||
return osType, osArch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Not in generatedData, detect now
|
||||
ui.Message("Running OS detection commands on remote host...")
|
||||
|
||||
// Get communicator type
|
||||
connType := "ssh" // default
|
||||
if ct, ok := generatedData["ConnType"].(string); ok {
|
||||
connType = ct
|
||||
}
|
||||
|
||||
// Run detection command based on communicator
|
||||
var cmd *packersdk.RemoteCmd
|
||||
if connType == "winrm" {
|
||||
cmd = &packersdk.RemoteCmd{
|
||||
Command: "echo %PROCESSOR_ARCHITECTURE%",
|
||||
}
|
||||
} else {
|
||||
cmd = &packersdk.RemoteCmd{
|
||||
Command: "uname -s -m",
|
||||
}
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
if err := comm.Start(ctx, cmd); err != nil {
|
||||
return "", "", fmt.Errorf("failed to run OS detection command: %s", err)
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return "", "", fmt.Errorf("OS detection command exited with status %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
output := strings.TrimSpace(stdout.String())
|
||||
ui.Message(fmt.Sprintf("OS detection output: %s", output))
|
||||
|
||||
// Parse output
|
||||
var osType, osArch string
|
||||
if connType == "winrm" {
|
||||
osType = "Windows"
|
||||
osArch = strings.ToLower(output) // AMD64, ARM64, etc.
|
||||
} else {
|
||||
parts := strings.Fields(output)
|
||||
if len(parts) >= 2 {
|
||||
osType = parts[0] // Linux, Darwin, FreeBSD, etc.
|
||||
osArch = parts[1] // x86_64, aarch64, etc.
|
||||
} else if len(parts) == 1 {
|
||||
// Some systems might only return one value
|
||||
osType = parts[0]
|
||||
osArch = "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
if osType == "" || osArch == "" {
|
||||
return "", "", fmt.Errorf("failed to parse OS detection output: %s", output)
|
||||
}
|
||||
|
||||
// Store in generatedData for potential reuse
|
||||
generatedData["OSType"] = osType
|
||||
generatedData["OSArch"] = osArch
|
||||
|
||||
return osType, osArch, nil
|
||||
}
|
||||
|
||||
// provisionWithNativeGeneration handles the new native SBOM generation flow
|
||||
func (p *SBOMInternalProvisioner) provisionWithNativeGeneration(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
osType, osArch string,
|
||||
) error {
|
||||
ui.Say("Starting native SBOM generation workflow...")
|
||||
|
||||
// TODO: Implement in next commits
|
||||
// Step 1: Download scanner binary
|
||||
// Step 2: Verify checksum if provided
|
||||
// Step 3: Upload scanner to remote
|
||||
// Step 4: Run scanner on remote
|
||||
// Step 5: Download SBOM
|
||||
// Step 6: Process SBOM for HCP
|
||||
// Step 7: Cleanup remote files
|
||||
|
||||
return fmt.Errorf("native SBOM generation not yet fully implemented")
|
||||
}
|
||||
|
||||
// provisionWithExistingSBOM handles the original SBOM provisioner behavior
|
||||
func (p *SBOMInternalProvisioner) provisionWithExistingSBOM(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
// Original implementation
|
||||
// Original implementation - all logic now in hcp-sbom provisioner
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current working directory for Packer SBOM: %s", err)
|
||||
|
|
|
|||
|
|
@ -7,18 +7,23 @@
|
|||
package hcp_sbom
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/go-getter/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models"
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
|
|
@ -32,7 +37,8 @@ type Config struct {
|
|||
|
||||
// The file path or URL to the SBOM file in the Packer artifact.
|
||||
// This file must either be in the SPDX or CycloneDX format.
|
||||
Source string `mapstructure:"source" required:"true"`
|
||||
// Not required if enable_native_generation is true.
|
||||
Source string `mapstructure:"source"`
|
||||
|
||||
// The path on the local machine to store a copy of the SBOM file.
|
||||
// You can specify an absolute or a path relative to the working directory
|
||||
|
|
@ -46,7 +52,28 @@ type Config struct {
|
|||
// This value must be between three and 36 characters from the following set: `[A-Za-z0-9_-]`.
|
||||
// You must specify a unique name for each build in an artifact version.
|
||||
SbomName string `mapstructure:"sbom_name"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// Native SBOM generation configuration
|
||||
// Enable native SBOM generation by automatically downloading and running a scanner
|
||||
EnableNativeGeneration bool `mapstructure:"enable_native_generation"`
|
||||
|
||||
// URL to scanner tool (supports go-getter syntax: HTTP, local files, Git, S3, etc.)
|
||||
// If empty and enable_native_generation is true, Syft will be auto-downloaded based on detected OS/Arch
|
||||
ScannerURL string `mapstructure:"scanner_url"`
|
||||
|
||||
// Expected SHA256 checksum of scanner binary for verification
|
||||
// If provided, scanner_url must also be specified
|
||||
ScannerChecksum string `mapstructure:"scanner_checksum"`
|
||||
|
||||
// Arguments to pass to the scanner tool
|
||||
// Default for Syft: ["-o", "spdx-json"]
|
||||
ScannerArgs []string `mapstructure:"scanner_args"`
|
||||
|
||||
// Path to scan on remote host
|
||||
// Default: "/"
|
||||
ScanPath string `mapstructure:"scan_path"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
|
|
@ -74,8 +101,26 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
|
||||
var errs error
|
||||
|
||||
if p.config.Source == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("source must be specified"))
|
||||
// Validate based on mode
|
||||
if p.config.EnableNativeGeneration {
|
||||
// Native generation mode: source is optional
|
||||
// Set defaults
|
||||
if p.config.ScanPath == "" {
|
||||
p.config.ScanPath = "/"
|
||||
}
|
||||
if len(p.config.ScannerArgs) == 0 {
|
||||
p.config.ScannerArgs = []string{"-o", "spdx-json"}
|
||||
}
|
||||
|
||||
// Validate: if checksum is provided, URL must also be provided
|
||||
if p.config.ScannerChecksum != "" && p.config.ScannerURL == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("scanner_checksum requires scanner_url to be specified"))
|
||||
}
|
||||
} else {
|
||||
// Traditional mode: source is required
|
||||
if p.config.Source == "" {
|
||||
errs = packersdk.MultiErrorAppend(errs, errors.New("source must be specified (or enable enable_native_generation)"))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.SbomName != "" && !sbomFormatRegexp.MatchString(p.config.SbomName) {
|
||||
|
|
@ -128,6 +173,42 @@ func (p *Provisioner) Provision(
|
|||
}
|
||||
p.config.ctx.Data = generatedData
|
||||
|
||||
// Check if native generation is enabled
|
||||
if !p.config.EnableNativeGeneration {
|
||||
// Original behavior: user provides SBOM file
|
||||
ui.Say("Using existing SBOM provisioner behavior (user-provided SBOM)")
|
||||
return p.provisionWithExistingSBOM(ctx, ui, comm, generatedData)
|
||||
}
|
||||
|
||||
// Native generation enabled
|
||||
ui.Say("Native SBOM generation enabled")
|
||||
|
||||
var osType, osArch string
|
||||
var err error
|
||||
|
||||
// Only detect OS if scanner_url is NOT provided
|
||||
if p.config.ScannerURL == "" {
|
||||
ui.Say("No scanner URL provided, detecting remote OS/Arch...")
|
||||
osType, osArch, err = p.detectRemoteOS(ctx, ui, comm, generatedData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect remote OS: %s", err)
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Detected: OS=%s, Arch=%s", osType, osArch))
|
||||
} else {
|
||||
ui.Say("Scanner URL provided, skipping OS detection")
|
||||
// User provided scanner URL, assume they know their platform
|
||||
osType = "unknown"
|
||||
osArch = "unknown"
|
||||
}
|
||||
|
||||
return p.provisionWithNativeGeneration(ctx, ui, comm, generatedData, osType, osArch)
|
||||
}
|
||||
|
||||
// provisionWithExistingSBOM handles the original flow where user provides an SBOM file
|
||||
func (p *Provisioner) provisionWithExistingSBOM(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{},
|
||||
) error {
|
||||
src := p.config.Source
|
||||
|
||||
pkrDst := generatedData["dst"].(string)
|
||||
|
|
@ -178,6 +259,83 @@ func (p *Provisioner) Provision(
|
|||
return nil
|
||||
}
|
||||
|
||||
// detectRemoteOS performs OS detection on the remote host
|
||||
func (p *Provisioner) detectRemoteOS(ctx context.Context, ui packersdk.Ui,
|
||||
comm packersdk.Communicator,
|
||||
generatedData map[string]interface{}) (string, string, error) {
|
||||
// First check if already detected (from generatedData)
|
||||
if osType, ok := generatedData["OSType"].(string); ok {
|
||||
if osArch, ok := generatedData["OSArch"].(string); ok {
|
||||
ui.Message("Using previously detected OS information from generated data")
|
||||
return osType, osArch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Not in generatedData, detect now
|
||||
ui.Message("Running OS detection commands on remote host...")
|
||||
|
||||
// Get communicator type
|
||||
connType := "ssh" // default
|
||||
if ct, ok := generatedData["ConnType"].(string); ok {
|
||||
connType = ct
|
||||
}
|
||||
|
||||
// Run detection command based on communicator
|
||||
var cmd *packersdk.RemoteCmd
|
||||
if connType == "winrm" {
|
||||
cmd = &packersdk.RemoteCmd{
|
||||
Command: "echo %PROCESSOR_ARCHITECTURE%",
|
||||
}
|
||||
} else {
|
||||
cmd = &packersdk.RemoteCmd{
|
||||
Command: "uname -s -m",
|
||||
}
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
if err := comm.Start(ctx, cmd); err != nil {
|
||||
return "", "", fmt.Errorf("failed to run OS detection command: %s", err)
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return "", "", fmt.Errorf("OS detection command exited with status %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
output := strings.TrimSpace(stdout.String())
|
||||
ui.Message(fmt.Sprintf("OS detection output: %s", output))
|
||||
|
||||
// Parse output
|
||||
var osType, osArch string
|
||||
if connType == "winrm" {
|
||||
osType = "Windows"
|
||||
osArch = strings.ToLower(output) // AMD64, ARM64, etc.
|
||||
} else {
|
||||
parts := strings.Fields(output)
|
||||
if len(parts) >= 2 {
|
||||
osType = parts[0] // Linux, Darwin, FreeBSD, etc.
|
||||
osArch = parts[1] // x86_64, aarch64, etc.
|
||||
} else if len(parts) == 1 {
|
||||
// Some systems might only return one value
|
||||
osType = parts[0]
|
||||
osArch = "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
if osType == "" || osArch == "" {
|
||||
return "", "", fmt.Errorf("failed to parse OS detection output: %s", output)
|
||||
}
|
||||
|
||||
// Store in generatedData for potential reuse
|
||||
generatedData["OSType"] = osType
|
||||
generatedData["OSArch"] = osArch
|
||||
|
||||
return osType, osArch, nil
|
||||
}
|
||||
|
||||
// getUserDestination determines and returns the destination path for the user SBOM file.
|
||||
func (p *Provisioner) getUserDestination() (string, error) {
|
||||
dst := p.config.Destination
|
||||
|
|
@ -222,3 +380,314 @@ func (p *Provisioner) getUserDestination() (string, error) {
|
|||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
|
||||
// provisionWithNativeGeneration handles the new native SBOM generation flow
|
||||
func (p *Provisioner) provisionWithNativeGeneration(
|
||||
ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator,
|
||||
generatedData map[string]interface{}, osType, osArch string,
|
||||
) error {
|
||||
ui.Say("Starting native SBOM generation workflow...")
|
||||
|
||||
// Step 1: Download scanner binary
|
||||
ui.Say("Downloading scanner binary...")
|
||||
scannerLocalPath, err := p.downloadScanner(ctx, ui, osType, osArch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download scanner: %s", err)
|
||||
}
|
||||
defer os.Remove(scannerLocalPath)
|
||||
|
||||
// Step 2: Verify checksum if provided
|
||||
if p.config.ScannerChecksum != "" {
|
||||
ui.Say("Verifying scanner checksum...")
|
||||
if err := p.verifyChecksum(scannerLocalPath); err != nil {
|
||||
return fmt.Errorf("checksum verification failed: %s", err)
|
||||
}
|
||||
ui.Say("Checksum verification passed")
|
||||
}
|
||||
|
||||
// Step 3: Upload scanner to remote
|
||||
// Step 4: Run scanner on remote
|
||||
// Step 5: Download SBOM
|
||||
// Step 6: Process SBOM for HCP
|
||||
// Step 7: Cleanup remote files
|
||||
// TODO: Implement in commits 4-6
|
||||
|
||||
return fmt.Errorf("native SBOM generation partially implemented (commits 4-6 pending)")
|
||||
}
|
||||
|
||||
// downloadScanner downloads the scanner binary using go-getter
|
||||
func (p *Provisioner) downloadScanner(ctx context.Context, ui packersdk.Ui,
|
||||
osType, osArch string) (string, error) {
|
||||
var downloadURL string
|
||||
|
||||
// If user provided a URL, use it
|
||||
if p.config.ScannerURL != "" {
|
||||
downloadURL = p.config.ScannerURL
|
||||
ui.Message(fmt.Sprintf("Using custom scanner URL: %s", downloadURL))
|
||||
} else {
|
||||
// Default to Syft from GitHub releases
|
||||
if osType == "unknown" || osArch == "unknown" {
|
||||
return "", fmt.Errorf("cannot auto-download scanner: OS/Arch unknown (provide scanner_url)")
|
||||
}
|
||||
downloadURL = p.buildDefaultSyftURL(osType, osArch)
|
||||
ui.Message(fmt.Sprintf("Auto-downloading Syft for %s/%s", osType, osArch))
|
||||
ui.Message(fmt.Sprintf("Download URL: %s", downloadURL))
|
||||
}
|
||||
|
||||
// Create temporary directory for download
|
||||
tmpDir, err := os.MkdirTemp("", "packer-scanner-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temp directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Use go-getter to download
|
||||
client := &getter.Client{}
|
||||
|
||||
req := &getter.Request{
|
||||
Src: downloadURL,
|
||||
Dst: tmpDir,
|
||||
}
|
||||
|
||||
ui.Message("Downloading scanner binary...")
|
||||
if _, err := client.Get(ctx, req); err != nil {
|
||||
return "", fmt.Errorf("failed to download scanner: %s", err)
|
||||
}
|
||||
|
||||
// Find the scanner binary in the downloaded files
|
||||
scannerPath, err := p.findScannerBinary(tmpDir, osType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to locate scanner binary: %s", err)
|
||||
}
|
||||
|
||||
// Copy to a permanent temp location
|
||||
finalPath, err := p.copyScannerToTemp(scannerPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to copy scanner: %s", err)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Scanner downloaded to: %s", finalPath))
|
||||
return finalPath, nil
|
||||
}
|
||||
|
||||
// buildDefaultSyftURL constructs the default Syft download URL
|
||||
func (p *Provisioner) buildDefaultSyftURL(osType, osArch string) string {
|
||||
// Map to Syft platform naming
|
||||
syftOS, syftArch := p.mapToSyftPlatform(osType, osArch)
|
||||
|
||||
// Default to latest stable version
|
||||
version := "v0.100.0"
|
||||
|
||||
// Construct GitHub release URL
|
||||
// Example: https://github.com/anchore/syft/releases/download/v0.100.0/syft_0.100.0_linux_amd64.tar.gz
|
||||
versionNum := strings.TrimPrefix(version, "v")
|
||||
fileName := fmt.Sprintf("syft_%s_%s_%s.tar.gz", versionNum, syftOS, syftArch)
|
||||
|
||||
return fmt.Sprintf("https://github.com/anchore/syft/releases/download/%s/%s",
|
||||
version, fileName)
|
||||
}
|
||||
|
||||
// mapToSyftPlatform maps detected OS/Arch to Syft naming conventions
|
||||
func (p *Provisioner) mapToSyftPlatform(osType, osArch string) (string, string) {
|
||||
osType = strings.ToLower(osType)
|
||||
osArch = strings.ToLower(osArch)
|
||||
|
||||
// Map OS
|
||||
syftOS := "linux"
|
||||
if strings.Contains(osType, "darwin") || strings.Contains(osType, "macos") {
|
||||
syftOS = "darwin"
|
||||
} else if strings.Contains(osType, "windows") {
|
||||
syftOS = "windows"
|
||||
} else if strings.Contains(osType, "freebsd") {
|
||||
syftOS = "freebsd"
|
||||
}
|
||||
|
||||
// Map Architecture
|
||||
syftArch := osArch
|
||||
switch osArch {
|
||||
case "x86_64", "amd64":
|
||||
syftArch = "amd64"
|
||||
case "aarch64", "arm64":
|
||||
syftArch = "arm64"
|
||||
case "i386", "i686":
|
||||
syftArch = "386"
|
||||
case "armv7l", "armv7":
|
||||
syftArch = "arm"
|
||||
}
|
||||
|
||||
return syftOS, syftArch
|
||||
}
|
||||
|
||||
// findScannerBinary locates the scanner executable in the downloaded directory
|
||||
func (p *Provisioner) findScannerBinary(dir, osType string) (string, error) {
|
||||
osType = strings.ToLower(osType)
|
||||
|
||||
var binaryName string
|
||||
if strings.Contains(osType, "windows") {
|
||||
binaryName = "syft.exe"
|
||||
} else {
|
||||
binaryName = "syft"
|
||||
}
|
||||
|
||||
var foundPath string
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
fileName := filepath.Base(path)
|
||||
// Match exact name or name as part of the file
|
||||
if fileName == binaryName || strings.Contains(fileName, binaryName) {
|
||||
// For archives, we want the actual binary, not the archive
|
||||
if !strings.HasSuffix(fileName, ".tar.gz") &&
|
||||
!strings.HasSuffix(fileName, ".zip") &&
|
||||
!strings.HasSuffix(fileName, ".tar") {
|
||||
foundPath = path
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if foundPath == "" {
|
||||
// If not found, try to extract from tar.gz
|
||||
foundPath, err = p.extractScannerFromArchive(dir, binaryName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("scanner binary '%s' not found in downloaded files", binaryName)
|
||||
}
|
||||
}
|
||||
|
||||
return foundPath, nil
|
||||
}
|
||||
|
||||
// extractScannerFromArchive extracts the scanner binary from a tar.gz archive
|
||||
func (p *Provisioner) extractScannerFromArchive(dir, binaryName string) (string, error) {
|
||||
// Find tar.gz file
|
||||
var archivePath string
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".tar.gz") {
|
||||
archivePath = path
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if archivePath == "" {
|
||||
return "", fmt.Errorf("no tar.gz archive found")
|
||||
}
|
||||
|
||||
// Open and extract
|
||||
file, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
// Find and extract the binary
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Look for the binary
|
||||
if filepath.Base(header.Name) == binaryName {
|
||||
// Create temporary file for the binary
|
||||
tmpBinary, err := os.CreateTemp(dir, "syft-binary-*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer tmpBinary.Close()
|
||||
|
||||
// Copy binary content
|
||||
if _, err := io.Copy(tmpBinary, tr); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Make executable
|
||||
if err := os.Chmod(tmpBinary.Name(), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpBinary.Name(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("binary '%s' not found in archive", binaryName)
|
||||
}
|
||||
|
||||
// copyScannerToTemp copies the scanner binary to a permanent temp location
|
||||
func (p *Provisioner) copyScannerToTemp(srcPath string) (string, error) {
|
||||
// Create temp file
|
||||
tmpFile, err := os.CreateTemp("", "packer-scanner-*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
// Open source
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Copy
|
||||
if _, err := io.Copy(tmpFile, src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Make executable
|
||||
if err := os.Chmod(tmpFile.Name(), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
|
||||
// verifyChecksum verifies the SHA256 checksum of the scanner binary
|
||||
func (p *Provisioner) verifyChecksum(filePath string) error {
|
||||
// Open file
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Calculate SHA256
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get hex string
|
||||
actualChecksum := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
// Compare with expected
|
||||
expectedChecksum := strings.ToLower(strings.TrimSpace(p.config.ScannerChecksum))
|
||||
if actualChecksum != expectedChecksum {
|
||||
return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedChecksum, actualChecksum)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue