From 45aa2e928dd7c4a2022bb62b12364f0a6a534369 Mon Sep 17 00:00:00 2001 From: Sai Diliyaer Date: Tue, 30 Apr 2024 12:23:35 -0400 Subject: [PATCH] api: add checksum validation in Content Library update session file API --- govc/USAGE.md | 3 +++ govc/library/import.go | 20 ++++++++++++++++--- govc/test/library.bats | 6 ++++-- .../library_item_updatesession_file.go | 9 ++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/govc/USAGE.md b/govc/USAGE.md index 51c6fb58c..85f4348e0 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -3613,8 +3613,11 @@ Examples: govc library.import library_name/item_name file.ova # update existing item govc library.import library_name http://example.com/file.ovf # download and push to vCenter govc library.import -pull library_name http://example.com/file.ova # direct pull from vCenter + govc library.import -pull -c= -a= library_name http://example.com/file.ova # direct pull from vCenter with checksum validation Options: + -a=SHA256 Algorithm used to calculate the checksum. Possible values are: SHA1, MD5, SHA256 (default), SHA512 + -c= Checksum value to verify the pulled library item -m=false Require ova manifest -n= Library item name -pull=false Pull library item from http endpoint diff --git a/govc/library/import.go b/govc/library/import.go index 17236bc7d..4b18d84f9 100644 --- a/govc/library/import.go +++ b/govc/library/import.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2019 VMware, Inc. All Rights Reserved. +Copyright (c) 2019-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ type item struct { *flags.ClientFlag *flags.OutputFlag library.Item + library.Checksum manifest bool pull bool @@ -58,6 +59,8 @@ func (cmd *item) Register(ctx context.Context, f *flag.FlagSet) { f.StringVar(&cmd.Type, "t", "", "Library item type") f.BoolVar(&cmd.manifest, "m", false, "Require ova manifest") f.BoolVar(&cmd.pull, "pull", false, "Pull library item from http endpoint") + f.StringVar(&cmd.Checksum.Checksum, "c", "", "Checksum value to verify the pulled library item") + f.StringVar(&cmd.Checksum.Algorithm, "a", "SHA256", "Algorithm used to calculate the checksum. Possible values are: SHA1, MD5, SHA256 (default), SHA512") } func (cmd *item) Usage() string { @@ -74,7 +77,8 @@ Examples: govc library.import library_id file.iso # Use library id if multiple libraries have the same name govc library.import library_name/item_name file.ova # update existing item govc library.import library_name http://example.com/file.ovf # download and push to vCenter - govc library.import -pull library_name http://example.com/file.ova # direct pull from vCenter` + govc library.import -pull library_name http://example.com/file.ova # direct pull from vCenter + govc library.import -pull -c= -a= library_name http://example.com/file.ova # direct pull from vCenter with checksum validation` } func (cmd *item) Process(ctx context.Context) error { @@ -89,6 +93,16 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error { return flag.ErrHelp } + // Checksums are verified after the file is uploaded to a server. + // Check the algorithm and fail early if it's not supported. + if cmd.pull && cmd.Checksum.Checksum != "" { + switch cmd.Checksum.Algorithm { + case "SHA1", "MD5", "SHA256", "SHA512": + default: + return fmt.Errorf("invalid checksum algorithm: %s", cmd.Checksum.Algorithm) + } + } + file := f.Arg(1) base := filepath.Base(file) ext := filepath.Ext(base) @@ -172,7 +186,7 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error { return err } if cmd.pull { - _, err = m.AddLibraryItemFileFromURI(ctx, session, filepath.Base(file), file) + _, err = m.AddLibraryItemFileFromURI(ctx, session, filepath.Base(file), file, cmd.Checksum) if err != nil { return err } diff --git a/govc/test/library.bats b/govc/test/library.bats index 695c21c32..058868187 100755 --- a/govc/test/library.bats +++ b/govc/test/library.bats @@ -187,7 +187,8 @@ load test_helper dir=$(govc datastore.info -json | jq -r .datastores[].info.url) ln -s "$GOVC_IMAGES/$TTYLINUX_NAME."* "$dir" - run govc library.import -pull my-content "https://$(govc env GOVC_URL)/folder/$TTYLINUX_NAME.ovf" + # vcsim doesn't verify checksums. Use a fake checksum and a possible algorithm to ensure the args are accepted. + run govc library.import -pull -c=fake -a=SHA1 my-content "https://$(govc env GOVC_URL)/folder/$TTYLINUX_NAME.ovf" assert_success run govc library.deploy "my-content/$TTYLINUX_NAME" ttylinux @@ -196,7 +197,8 @@ load test_helper run govc vm.info ttylinux assert_success - run govc library.import -pull -n ttylinux-unpacked my-content "https://$(govc env GOVC_URL)/folder/$TTYLINUX_NAME.ova" + # vcsim doesn't verify checksums. Use a fake checksum and a possible algorithm to ensure the args are accepted. + run govc library.import -pull -c=fake -a=MD5 -n ttylinux-unpacked my-content "https://$(govc env GOVC_URL)/folder/$TTYLINUX_NAME.ova" assert_success item_id=$(govc library.info -json /my-content/ttylinux-unpacked | jq -r .[].id) diff --git a/vapi/library/library_item_updatesession_file.go b/vapi/library/library_item_updatesession_file.go index a38a763c1..d63abe721 100644 --- a/vapi/library/library_item_updatesession_file.go +++ b/vapi/library/library_item_updatesession_file.go @@ -19,6 +19,7 @@ package library import ( "bufio" "context" + "fmt" "io" "net/http" "strings" @@ -87,7 +88,7 @@ func (c *Manager) AddLibraryItemFile(ctx context.Context, sessionID string, upda } // AddLibraryItemFileFromURI adds a file from a remote URI. -func (c *Manager) AddLibraryItemFileFromURI(ctx context.Context, sessionID, name, uri string) (*UpdateFile, error) { +func (c *Manager) AddLibraryItemFileFromURI(ctx context.Context, sessionID, name, uri string, checksum ...Checksum) (*UpdateFile, error) { source := &TransferEndpoint{ URI: uri, } @@ -98,6 +99,12 @@ func (c *Manager) AddLibraryItemFileFromURI(ctx context.Context, sessionID, name SourceEndpoint: source, } + if len(checksum) == 1 && checksum[0].Checksum != "" { + file.Checksum = &checksum[0] + } else if len(checksum) > 1 { + return nil, fmt.Errorf("expected 0 or 1 checksum, got %d", len(checksum)) + } + if res, err := c.Head(uri); err == nil { file.Size = res.ContentLength if res.TLS != nil {