2022-12-13 11:51:14 +02:00
|
|
|
package syft
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/tar"
|
|
|
|
"compress/gzip"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/SAP/jenkins-library/pkg/command"
|
|
|
|
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/piperutils"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
func GenerateSBOM(syftDownloadURL, dockerConfigDir string, execRunner command.ExecRunner, fileUtils piperutils.FileUtils, httpClient piperhttp.Sender, registryURL string, images []string) error {
|
|
|
|
if registryURL == "" {
|
|
|
|
return errors.New("syft: regisitry url must not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(images) == 0 {
|
|
|
|
return errors.New("syft: no images provided")
|
|
|
|
}
|
|
|
|
|
2022-12-13 16:37:06 +02:00
|
|
|
execRunner.AppendEnv([]string{fmt.Sprintf("DOCKER_CONFIG=%s", dockerConfigDir)})
|
2022-12-13 11:51:14 +02:00
|
|
|
|
|
|
|
tmpDir, err := fileUtils.TempDir("", "syft")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
syftFile := filepath.Join(tmpDir, "syft")
|
|
|
|
|
|
|
|
err = install(syftDownloadURL, syftFile, fileUtils, httpClient)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to install syft")
|
|
|
|
}
|
|
|
|
|
|
|
|
for index, image := range images {
|
|
|
|
if image == "" {
|
|
|
|
return errors.New("syft: image name must not be empty")
|
|
|
|
}
|
|
|
|
// TrimPrefix needed as syft needs containerRegistry name only
|
2022-12-13 16:37:06 +02:00
|
|
|
err = execRunner.RunExecutable(syftFile, "packages", fmt.Sprintf("registry:%s/%s", strings.TrimPrefix(registryURL, "https://"), image), "-o", "cyclonedx-xml", "--file", fmt.Sprintf("bom-docker-%v.xml", index), "-q")
|
2022-12-13 11:51:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to generate SBOM: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func install(syftDownloadURL, dest string, fileUtils piperutils.FileUtils, httpClient piperhttp.Sender) error {
|
|
|
|
response, err := httpClient.SendRequest(http.MethodGet, syftDownloadURL, nil, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to download syft binary: %w", err)
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
|
|
|
err = extractSyft(response.Body, dest, fileUtils)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to extract syft binary")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = fileUtils.Chmod(dest, 0755)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func extractSyft(archive io.Reader, dest string, fileUtils piperutils.FileUtils) error {
|
|
|
|
zr, err := gzip.NewReader(archive)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer zr.Close()
|
|
|
|
|
|
|
|
tr := tar.NewReader(zr)
|
|
|
|
|
|
|
|
fileFound := false
|
|
|
|
for {
|
|
|
|
f, err := tr.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to read archive")
|
|
|
|
}
|
|
|
|
|
|
|
|
if filepath.Base(f.Name) == "syft" {
|
|
|
|
fileFound = true
|
|
|
|
|
|
|
|
df, err := fileUtils.Create(dest)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to create file %q", dest)
|
|
|
|
}
|
|
|
|
|
|
|
|
size, err := io.Copy(df, tr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = df.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if size != f.Size {
|
|
|
|
return fmt.Errorf("only wrote %d bytes to %s; expected %d", size, dest, f.Size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !fileFound {
|
|
|
|
return errors.New("no file with the name 'syft' was found")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|