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

800. Add sbom section to deploy configure/review #1713

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cedf771
800. Update api read.go to walk the archive and eand read the zarf-ya…
mike-winberry May 16, 2023
ee1afac
Merge branch 'main' into 800-add-sbom-section-to-configure-page-in-de…
mike-winberry May 16, 2023
bfad7c5
#800. Updated ui List references in dialogs to not have a background …
mike-winberry May 18, 2023
00262a5
#800. Update api sbom.go with a DeleteSBOM and cleanupSBOM method to …
mike-winberry May 18, 2023
dcfd786
Update api sbom to use the zarf-sboms folder instead of sub tmp folde…
mike-winberry May 18, 2023
0048c07
Fix api/packages read by moving ErrStopWalk out of else block
mike-winberry May 18, 2023
c9da96c
Fix api/packages unused car and comments
mike-winberry May 18, 2023
882252e
Fix ui copy-to-clipboard missing SPDX
mike-winberry May 18, 2023
9248a60
Merge branch 'main' into 800-add-sbom-section-to-configure-page-in-de…
mike-winberry May 18, 2023
1172c93
#800 Fix playwright test to propely wait for sbom to load
mike-winberry May 18, 2023
dacf295
800 Fix types api missing comment
mike-winberry May 18, 2023
783036f
#800. Fix UI test for sboms
mike-winberry May 19, 2023
bc2fe58
#800. Update api/start to serve html files from the zarf-sbom file us…
mike-winberry May 19, 2023
f1cc7c3
Merge branch 'main' into 800-add-sbom-section-to-configure-page-in-de…
mike-winberry May 19, 2023
afea36f
#800. Update api/start with redirect behavior when sbom file does not…
mike-winberry May 19, 2023
f6dd67f
#800 Fix api/start.go redirect block
mike-winberry May 19, 2023
f4479f0
#800 Update ui by changing the copy in the build-providence to match …
mike-winberry May 21, 2023
903dfcb
#800 Update ui build-providence ViewSBOM button to only appear if the…
mike-winberry May 21, 2023
d1fa7dd
Merge branch 'main' into 800-add-sbom-section-to-configure-page-in-de…
mike-winberry May 21, 2023
07d25db
Merge branch 'main' into 800-add-sbom-section-to-configure-page-in-de…
Racer159 May 22, 2023
01bca7e
Make more things use constants in config
Racer159 May 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cmd/tools/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/mholt/archiver/v3"
Expand Down Expand Up @@ -55,7 +56,7 @@ var archiverDecompressCmd = &cobra.Command{
if strings.HasSuffix(path, ".tar") {
dst := filepath.Join(strings.TrimSuffix(path, ".tar"), "..")
// Unpack sboms.tar differently since it has a different folder structure than components
if info.Name() == "sboms.tar" {
if info.Name() == config.ZarfSBOMTar {
dst = strings.TrimSuffix(path, ".tar")
}
err := archiver.Unarchive(path, dst)
Expand Down
3 changes: 3 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ const (
ZarfImageCacheDir = "images"

ZarfYAML = "zarf.yaml"
ZarfYAMLSignature = "zarf.yaml.sig"
ZarfChecksumsTxt = "checksums.txt"
ZarfSBOMDir = "zarf-sbom"
ZarfSBOMTar = "sboms.tar"
ZarfPackagePrefix = "zarf-package-"

ZarfInClusterContainerRegistryNodePort = 31999
Expand Down
33 changes: 17 additions & 16 deletions src/internal/api/packages/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@
package packages

import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/internal/api/common"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/go-chi/chi/v5"
goyaml "github.com/goccy/go-yaml"
"github.com/mholt/archiver/v3"
)

Expand All @@ -35,26 +33,29 @@ func Read(w http.ResponseWriter, r *http.Request) {

// internal function to read a package from the local filesystem.
func readPackage(path string) (pkg types.APIZarfPackage, err error) {
var file []byte

pkg.Path, err = url.QueryUnescape(path)
if err != nil {
return pkg, err
}

tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
return pkg, fmt.Errorf("unable to create tmpdir: %w", err)
}
defer os.RemoveAll(tmpDir)

// Extract the archive
err = archiver.Extract(pkg.Path, config.ZarfYAML, tmpDir)
// Check for zarf.yaml in the package and read into file
err = archiver.Walk(pkg.Path, func(f archiver.File) error {
if f.Name() == config.ZarfYAML {
file, err = ioutil.ReadAll(f)
if err != nil {
return err
}
return archiver.ErrStopWalk
}

return nil
})
if err != nil {
return pkg, err
}

// Read the Zarf yaml
configPath := filepath.Join(tmpDir, config.ZarfYAML)
err = utils.ReadYaml(configPath, &pkg.ZarfPackage)

err = goyaml.Unmarshal(file, &pkg.ZarfPackage)
return pkg, err
}
134 changes: 134 additions & 0 deletions src/internal/api/packages/sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package packages provides api functions for managing Zarf packages.
package packages

import (
"net/http"
"net/url"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/internal/api/common"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/go-chi/chi/v5"
"github.com/mholt/archiver/v3"
)

var signalChan = make(chan os.Signal, 1)

// ExtractSBOM Extracts the SBOM from the package and returns the path to the SBOM
func ExtractSBOM(w http.ResponseWriter, r *http.Request) {
path := chi.URLParam(r, "path")

sbom, err := extractSBOM(path)

if err != nil {
message.ErrorWebf(err, w, err.Error())
} else {
common.WriteJSONResponse(w, sbom, http.StatusOK)
}

}

// DeleteSBOM removes the SBOM directory
func DeleteSBOM(w http.ResponseWriter, _ *http.Request) {
err := cleanupSBOM()
if err != nil {
message.ErrorWebf(err, w, err.Error())
return
}
common.WriteJSONResponse(w, nil, http.StatusOK)
}

// cleanupSBOM removes the SBOM directory
func cleanupSBOM() error {
err := os.RemoveAll(config.ZarfSBOMDir)
if err != nil {
return err
}
return nil
}

// Extracts the SBOM from the package and returns the path to the SBOM
func extractSBOM(escapedPath string) (sbom types.APIPackageSBOM, err error) {
path, err := url.QueryUnescape(escapedPath)
if err != nil {
return sbom, err
}

// Ensure we can get the cwd
cwd, err := os.Getwd()
if err != nil {
return sbom, err
}

// ensure the package exists
if _, err := os.Stat(path); os.IsNotExist(err) {
return sbom, err
}

// Join the current working directory with the zarf-sbom directory
sbomPath := filepath.Join(cwd, config.ZarfSBOMDir)

// ensure the zarf-sbom directory is empty
if _, err := os.Stat(sbomPath); !os.IsNotExist(err) {
cleanupSBOM()
}

// Create the Zarf SBOM directory
err = utils.CreateDirectory(sbomPath, 0700)
if err != nil {
return sbom, err
}

// Extract the SBOM tar.gz from the package
err = archiver.Extract(path, config.ZarfSBOMTar, sbomPath)
if err != nil {
cleanupSBOM()
return sbom, err
}

// Unarchive the SBOM tar.gz
err = archiver.Unarchive(filepath.Join(sbomPath, config.ZarfSBOMTar), sbomPath)
if err != nil {
cleanupSBOM()
return sbom, err
}

// Get the SBOM viewer files
sbom, err = getSbomViewFiles(sbomPath)
if err != nil {
cleanupSBOM()
return sbom, err
}

// Cleanup the temp directory on exit
go func() {
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
// Wait for a signal to be received
<-signalChan

cleanupSBOM()

// Exit the program
os.Exit(0)
}()

return sbom, err
}

func getSbomViewFiles(sbomPath string) (sbom types.APIPackageSBOM, err error) {
sbomViewFiles, err := filepath.Glob(filepath.Join(sbomPath, "sbom-viewer-*"))
if len(sbomViewFiles) > 0 {
sbom.Path = sbomViewFiles[0]
sbom.SBOMS = sbomViewFiles
}
return sbom, err
}
27 changes: 27 additions & 0 deletions src/internal/api/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func LaunchAPIServer() {
r.Delete("/{pkg}/disconnect/{name}", packages.DisconnectTunnel)
r.Get("/{pkg}/connections", packages.ListPackageConnections)
r.Get("/connections", packages.ListConnections)
r.Get("/sbom/{path}", packages.ExtractSBOM)
r.Delete("/sbom", packages.DeleteSBOM)
})

r.Route("/components", func(r chi.Router) {
Expand All @@ -115,6 +117,30 @@ func LaunchAPIServer() {
message.Infof("Zarf UI connection: http://127.0.0.1:%s/auth?token=%s", devPort, token)
}

// Setup the static SBOM server
sbomSub := os.DirFS(config.ZarfSBOMDir)
sbomFs := http.FileServer(http.FS(sbomSub))

// Serve the SBOM viewer files
router.Get("/sbom-viewer/*", func(w http.ResponseWriter, r *http.Request) {
message.Debug("api.LaunchAPIServer() - /sbom-viewer/*")

// Extract the file name from the URL
file := strings.TrimPrefix(r.URL.Path, "/sbom-viewer/")

// Ensure SBOM file exists in the config.ZarfSBOMDir
if test, err := sbomSub.Open(file); err != nil {
// If the file doesn't exist, redirect to the homepage
r.URL.Path = "/"
http.Redirect(w, r, "/", http.StatusFound)
} else {
// If the file exists, close the file and serve it
test.Close()
}
r.URL.Path = file
sbomFs.ServeHTTP(w, r)
})

// Load the static UI files
if sub, err := fs.Sub(config.UIAssets, "build/ui"); err != nil {
message.Error(err, "Unable to load the embedded ui assets")
Expand All @@ -124,6 +150,7 @@ func LaunchAPIServer() {

// Catch all routes
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
message.Debug("api.LaunchAPIServer() - /*")
// If the request is not a real file, serve the index.html instead
if test, err := sub.Open(strings.TrimPrefix(r.URL.Path, "/")); err != nil {
r.URL.Path = "/"
Expand Down
3 changes: 1 addition & 2 deletions src/internal/packager/sbom/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import (

// ViewSBOMFiles opens a browser to view the SBOM files and pauses for user input.
func ViewSBOMFiles(tmp types.TempPaths) {
sbomFilePath := filepath.Join(tmp.Base, "sboms")
sbomViewFiles, _ := filepath.Glob(filepath.Join(sbomFilePath, "sbom-viewer-*"))
sbomViewFiles, _ := filepath.Glob(filepath.Join(tmp.Sboms, "sbom-viewer-*"))

if len(sbomViewFiles) > 0 {
link := sbomViewFiles[0]
Expand Down
8 changes: 4 additions & 4 deletions src/pkg/packager/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ func createPaths() (paths types.TempPaths, err error) {
SeedImages: filepath.Join(basePath, "seed-images"),
Images: filepath.Join(basePath, "images"),
Components: filepath.Join(basePath, "components"),
SbomTar: filepath.Join(basePath, "sboms.tar"),
SbomTar: filepath.Join(basePath, config.ZarfSBOMTar),
Sboms: filepath.Join(basePath, "sboms"),
Checksums: filepath.Join(basePath, "checksums.txt"),
Checksums: filepath.Join(basePath, config.ZarfChecksumsTxt),
ZarfYaml: filepath.Join(basePath, config.ZarfYAML),
ZarfSig: filepath.Join(basePath, "zarf.yaml.sig"),
ZarfSig: filepath.Join(basePath, config.ZarfYAMLSignature),
}

return paths, err
Expand Down Expand Up @@ -456,7 +456,7 @@ func (p *Packager) validatePackageChecksums() error {
filepathMap[p.tmp.ZarfSig] = true

// Load the contents of the checksums file
checksumsFile, err := os.Open(filepath.Join(p.tmp.Base, "checksums.txt"))
checksumsFile, err := os.Open(filepath.Join(p.tmp.Base, config.ZarfChecksumsTxt))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/packager/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ func generatePackageChecksums(basePath string) (string, error) {
}

// Create the checksums file
checksumsFilePath := filepath.Join(basePath, "checksums.txt")
checksumsFilePath := filepath.Join(basePath, config.ZarfChecksumsTxt)
if err := utils.WriteFile(checksumsFilePath, []byte(checksumsData)); err != nil {
return "", err
}
Expand Down
4 changes: 2 additions & 2 deletions src/pkg/packager/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ func (p *Packager) Inspect(includeSBOM bool, outputSBOM string, inspectPublicKey

layersToPull := []string{config.ZarfYAML}
if pullSBOM {
layersToPull = append(layersToPull, "sboms.tar")
layersToPull = append(layersToPull, config.ZarfSBOMTar)
}
if pullZarfSig {
layersToPull = append(layersToPull, "zarf.yaml.sig")
layersToPull = append(layersToPull, config.ZarfYAMLSignature)
}

message.Debugf("Pulling layers %v from %s", layersToPull, p.cfg.DeployOpts.PackagePath)
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/packager/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (p *Packager) handleOciPackage(url string, out string, concurrency int, lay
copyOpts.PostCopy = copyOpts.OnCopySkipped
isPartialPull := len(layers) > 0
if isPartialPull {
alwaysPull := []string{"zarf.yaml", "checksums.txt", "zarf.yaml.sig"}
alwaysPull := []string{config.ZarfYAML, config.ZarfChecksumsTxt, config.ZarfYAMLSignature}
layers = append(layers, alwaysPull...)
copyOpts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
nodes, err := content.Successors(ctx, fetcher, desc)
Expand Down
11 changes: 9 additions & 2 deletions src/test/ui/02_initialize_cluster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,21 @@ test.describe('initialize a zarf cluster', () => {
// Find first init package deploy button.
const deployInit = page.getByTitle('init').first();
// click the init package deploy button.
await deployInit.click();
deployInit.click();

// Validate that the SBOM has been loaded
const sbomInfo = await page.waitForSelector('#sbom-info', { timeout: 10000 });
expect(await sbomInfo.innerText()).toMatch(/[0-9]+ artifacts to be reviewed/);

// Components (check most functionaliy with k3s component)
const k3s = page.locator('.accordion:has-text("k3s")');
await expect(k3s.locator('.deploy-component-toggle')).toHaveAttribute('aria-pressed', 'false');
await k3s.locator('text=Deploy').click();
await expect(k3s.locator('.deploy-component-toggle')).toHaveAttribute('aria-pressed', 'true');
await expect(
page.locator('.component-accordion-header:has-text("*** REQUIRES ROOT (not sudo) *** Install K3s")')
page.locator(
'.component-accordion-header:has-text("*** REQUIRES ROOT (not sudo) *** Install K3s")'
)
).toBeVisible();
await expect(k3s.locator('code')).toBeHidden();
await k3s.locator('.accordion-toggle').click();
Expand Down
7 changes: 7 additions & 0 deletions src/types/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type RestAPI struct {
APIZarfPackageConnection APIDeployedPackageConnection `json:"apiZarfPackageConnection"`
APIDeployedPackageConnections APIDeployedPackageConnections `json:"apiZarfPackageConnections"`
APIConnections APIConnections `json:"apiConnections"`
APIPackageSBOM APIPackageSBOM `json:"apiPackageSBOM"`
}

// ClusterSummary contains the summary of a cluster for the API.
Expand All @@ -48,6 +49,12 @@ type APIZarfDeployPayload struct {
InitOpts *ZarfInitOptions `json:"initOpts,omitempty"`
}

// APIPackageSBOM represents the SBOM viewer files for a package
type APIPackageSBOM struct {
Path string `json:"path"`
SBOMS []string `json:"sboms"`
}

// APIConnections represents all of the existing connections
type APIConnections map[string]APIDeployedPackageConnections

Expand Down
Loading