Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Provisioner for SBOM #13171

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft

Conversation

devashish-patel
Copy link
Contributor

@devashish-patel devashish-patel commented Sep 25, 2024

Example templates:

JSON:

{
  "builders": [
    {
      "type": "docker",
      "image": "ubuntu:20.04",
      "commit": true
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "apt-get update -y",
        "apt-get install -y curl",
        "bash -c \"$(curl -sSL https://install.mondoo.com/sh)\""
      ]
    },
    {
      "type": "shell",
      "inline": [
        "cnquery sbom --output cyclonedx-json --output-target /tmp/sbom_cyclonedx.json"
      ]
    },
    {
      "type": "hcp_sbom",
      "source": "/tmp/sbom_cyclonedx.json"
    }
  ]
}

HCL:

packer {
  required_plugins {
    docker = {
      version = ">= 1.0.0"
      source  = "github.com/hashicorp/docker"
    }
  }
}

source "docker" "ubuntu" {
  image  = "ubuntu:20.04"
  commit = true
}

build {
  sources = ["source.docker.ubuntu"]

  provisioner "shell" {
    inline = [
      "apt-get update -y",
      "apt-get install -y curl",
      "bash -c \"$(curl -sSL https://install.mondoo.com/sh)\""
    ]
  }

  provisioner "shell" {
    inline = [
      //"cnquery sbom --output cyclonedx-json | tee /tmp/sbom_cyclonedx.json",
      "cnquery sbom --output cyclonedx-json --output-target /tmp/sbom_cyclonedx.json",
    ]
  }

  provisioner "hcp_sbom" {
      source      = "/tmp/sbom_cyclonedx.json"
      //destination = "sbom_cyclonedx.json"
  }
}

Copy link
Contributor

@lbajolet-hashicorp lbajolet-hashicorp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a bunch of comments on the code, I think we can simplify the download to have it done once only, technically once we've copied the file locally for Packer, we can copy it to the user-specified destination (if specified). That or we can factorise the code for downloading since it's very similar.
I'll let you address those comments and do another pass of review after that.

// press before the provisioner is actually run.
type SBOMInternalProvisioner struct {
Provisioner packersdk.Provisioner
TempFileLoc string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we keep this as part of the provisioner? Since we only create the file during Prepare and wipe it by the end, it doesn't seem that we need it here

if err != nil {
return fmt.Errorf("failed to create internal temporary file for Packer SBOM: %s", err)
}
defer tmpFile.Close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the underlying provisioner writes to this file, we should not defer its closure I'd think, typically on Windows this might fail depending on the kind of Handle we've received from CreateTemp

if fileRemoveErr != nil {
log.Printf("Error removing SBOM temporary file %s: %s", name, fileRemoveErr)
}
}(p.TempFileLoc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point p.TempFileLoc was not set, so this won't work I'd think, since the parameter will be empty when the function executes?

if err != nil {
return nil, err
}
defer sourceFile.Close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use a function like os.ReadFile here instead of manually opening/reading/closing it.


compressedData := encoder.EncodeAll(data, nil)

fmt.Printf(fmt.Sprintf("SBOM file compressed successfully. Size: %d bytes", len(compressedData)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should become a log.Printf, if not removed completely imho

packer/build.go Outdated
Comment on lines 310 to 313
if !ok {
continue
}
b.SBOMFilesCompressed = append(b.SBOMFilesCompressed, sbomInternalProvisioner.CompressedData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the alternative has only one instruction, you can probably invert this logic here

Suggested change
if !ok {
continue
}
b.SBOMFilesCompressed = append(b.SBOMFilesCompressed, sbomInternalProvisioner.CompressedData)
if ok {
b.SBOMFilesCompressed = append(b.SBOMFilesCompressed, sbomInternalProvisioner.CompressedData)
}

return
}

if info.IsDir() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't that fail if the directory doesn't exist? Since we create the hierarchy below if not existing, we should assume that the directory doesn't already exists before attempting to create it, so failing here feels off.


// downloadSBOMForUser downloads a SBOM from a specified source to a local
// destination given by user. It works with all communicators from packersdk.
func (p *Provisioner) downloadSBOMForUser(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is suspiciously similar to the downloadSBOMForPacker function's code, we might be able to factorise the implementation I'd think, if only to avoid unnecessary repetition

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on this one @devashish-patel, we talked about this out-of-band, just want to kickstart a conversation on this line of work

}

// Download the file for user
p.downloadSBOMForUser(ui, comm)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should report errors here, the function should return an error if something went wrong, as the user requests the SBOM be kept somewhere else, if it fails for some reason, so should the provisioner

return
}

if strings.HasSuffix(dst, "/") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This heuristic seems brittle, the output can be a directory even if the path doesn't end with /, we should rely on os.Stat regardless to know if this is a directory, irrespective of what the path looks like

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants