opentofu/internal/getproviders/package_authentication.go
Martin Atkins 0ad4c1be2f internal/getproviders: Tidy up some confusion about package hashes
Earlier on in the stubbing of this package we realized that it wasn't
going to be possible to populate the authentication-related bits for all
packages because the relevant metadata just isn't available for packages
that are already local.

However, we just moved ahead with that awkward design at the time because
we needed to get other work done, and so we've been mostly producing
PackageMeta values with all-zeros hashes and just ignoring them entirely
as a temporary workaround.

This is a first step towards what is hopefully a more intuitive model:
authentication is an optional thing in a PackageMeta that is currently
populated only for packages coming from a registry.

So far this still just models checking a SHA256 hash, which is not a
sufficient set of checks for a real release but hopefully the "real"
implementation is a natural iteration of this starting point, and if not
then at least this interim step is a bit more honest about the fact that
Authentication will not be populated on every PackageMeta.
2020-04-06 16:31:23 -07:00

94 lines
3.1 KiB
Go

package getproviders
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"os"
)
// PackageAuthentication is an interface implemented by the optional package
// authentication implementations a source may include on its PackageMeta
// objects.
//
// A PackageAuthentication implementation is responsible for authenticating
// that a package is what its distributor intended to distribute and that it
// has not been tampered with.
type PackageAuthentication interface {
// AuthenticatePackage takes the metadata about the package as returned
// by its original source, and also the "localLocation" where it has
// been staged for local inspection (which may or may not be the same
// as the original source location) and returns an error if the
// authentication checks fail.
//
// The localLocation is guaranteed not to be a PackageHTTPURL: a
// remote package will always be staged locally for inspection first.
AuthenticatePackage(meta PackageMeta, localLocation PackageLocation) error
}
type packageAuthenticationAll []PackageAuthentication
// PackageAuthenticationAll combines several authentications together into a
// single check value, which passes only if all of the given ones pass.
//
// The checks are processed in the order given, so a failure of an earlier
// check will prevent execution of a later one.
func PackageAuthenticationAll(checks ...PackageAuthentication) PackageAuthentication {
return packageAuthenticationAll(checks)
}
func (checks packageAuthenticationAll) AuthenticatePackage(meta PackageMeta, localLocation PackageLocation) error {
for _, check := range checks {
err := check.AuthenticatePackage(meta, localLocation)
if err != nil {
return err
}
}
return nil
}
type archiveHashAuthentication struct {
WantSHA256Sum [sha256.Size]byte
}
// NewArchiveChecksumAuthentication returns a PackageAuthentication
// implementation that checks that the original distribution archive matches
// the given hash.
//
// This authentication is suitable only for PackageHTTPURL and
// PackageLocalArchive source locations, because the unpacked layout
// (represented by PackageLocalDir) does not retain access to the original
// source archive. Therefore this authenticator will return an error if its
// given localLocation is not PackageLocalArchive.
func NewArchiveChecksumAuthentication(wantSHA256Sum [sha256.Size]byte) PackageAuthentication {
return archiveHashAuthentication{wantSHA256Sum}
}
func (a archiveHashAuthentication) AuthenticatePackage(meta PackageMeta, localLocation PackageLocation) error {
archiveLocation, ok := localLocation.(PackageLocalArchive)
if !ok {
// A source should not use this authentication type for non-archive
// locations.
return fmt.Errorf("cannot check archive hash for non-archive location %s", localLocation)
}
f, err := os.Open(string(archiveLocation))
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
if err != nil {
return err
}
gotHash := h.Sum(nil)
if !bytes.Equal(gotHash, a.WantSHA256Sum[:]) {
return fmt.Errorf("archive has incorrect SHA-256 checksum %x (expected %x)", gotHash, a.WantSHA256Sum[:])
}
return nil
}