internal/getproviders: package authenticator for our new-style hashes

Earlier we introduced a new package hashing mechanism that is compatible
with both packed and unpacked packages, because it's a hash of the
contents of the package rather than of the archive it's delivered in.
However, we were using that only for the local selections file and not
for any remote package authentication yet.

The provider network mirrors protocol includes new-style hashes as a step
towards transitioning over to the new hash format in all cases, so this
new authenticator is here in preparation for verifying the checksums of
packages coming from network mirrors, for mirrors that support them.

For now this leaves us in a kinda confusing situation where we have both
NewPackageHashAuthentication for the new style and
NewArchiveChecksumAuthentication for the old style, which for the moment
is represented only by a doc comment on the latter. Hopefully we can
remove NewArchiveChecksumAuthentication in a future commit, if we can
get the registry updated to use the new hashing format.
This commit is contained in:
Martin Atkins 2020-08-24 18:17:26 -07:00
parent 23a8bdd522
commit 146e983c36
2 changed files with 103 additions and 0 deletions

View File

@ -114,6 +114,44 @@ func (checks packageAuthenticationAll) AuthenticatePackage(localLocation Package
return authResult, nil
}
type packageHashAuthentication struct {
RequiredHash string
}
// NewPackageHashAuthentication returns a PackageAuthentication implementation
// that checks whether the contents of the package match whichever of the
// given hashes is most preferred by the current version of Terraform.
//
// This uses the hash algorithms implemented by functions Hash and MatchesHash.
// The PreferredHash function will select which of the given hashes is
// considered by Terraform to be the strongest verification, and authentication
// succeeds as long as that chosen hash matches.
func NewPackageHashAuthentication(validHashes []string) PackageAuthentication {
requiredHash := PreferredHash(validHashes)
return packageHashAuthentication{
RequiredHash: requiredHash,
}
}
func (a packageHashAuthentication) AuthenticatePackage(localLocation PackageLocation) (*PackageAuthenticationResult, error) {
if a.RequiredHash == "" {
// Indicates that none of the hashes given to
// NewPackageHashAuthentication were considered to be usable by this
// version of Terraform.
return nil, fmt.Errorf("this version of Terraform does not support any of the checksum formats given for this provider")
}
matches, err := PackageMatchesHash(localLocation, a.RequiredHash)
if err != nil {
return nil, fmt.Errorf("failed to verify provider package checksums: %s", err)
}
if matches {
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
}
return nil, fmt.Errorf("provider package doesn't match the expected checksum %q", a.RequiredHash)
}
type archiveHashAuthentication struct {
WantSHA256Sum [sha256.Size]byte
}
@ -127,6 +165,10 @@ type archiveHashAuthentication struct {
// (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.
//
// NewPackageHashAuthentication is preferable to use when possible because
// it uses the newer hashing scheme (implemented by function Hash) that
// can work with both packed and unpacked provider packages.
func NewArchiveChecksumAuthentication(wantSHA256Sum [sha256.Size]byte) PackageAuthentication {
return archiveHashAuthentication{wantSHA256Sum}
}

View File

@ -97,6 +97,67 @@ func TestPackageAuthenticationAll_failure(t *testing.T) {
}
}
// Package hash authentication requires a zip file or directory fixture and a
// known-good set of hashes, of which the authenticator will pick one. The
// result should be "verified checksum".
func TestPackageHashAuthentication_success(t *testing.T) {
// Location must be a PackageLocalArchive path
location := PackageLocalDir("testdata/filesystem-mirror/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64")
wantHashes := []string{
// Known-good HashV1 result for this directory
"h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g=",
}
auth := NewPackageHashAuthentication(wantHashes)
result, err := auth.AuthenticatePackage(location)
wantResult := PackageAuthenticationResult{result: verifiedChecksum}
if result == nil || *result != wantResult {
t.Errorf("wrong result: got %#v, want %#v", result, wantResult)
}
if err != nil {
t.Errorf("wrong err: got %s, want nil", err)
}
}
// Package has authentication can fail for various reasons.
func TestPackageHashAuthentication_failure(t *testing.T) {
tests := map[string]struct {
location PackageLocation
err string
}{
"missing file": {
PackageLocalArchive("testdata/no-package-here.zip"),
"failed to verify provider package checksums: lstat testdata/no-package-here.zip: no such file or directory",
},
"checksum mismatch": {
PackageLocalDir("testdata/filesystem-mirror/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64"),
"provider package doesn't match the expected checksum \"h1:invalid\"",
},
"invalid zip file": {
PackageLocalArchive("testdata/filesystem-mirror/registry.terraform.io/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip"),
"failed to verify provider package checksums: zip: not a valid zip file",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
// Empty expected hash, either because we'll error before we
// reach it, or we want to force a checksum mismatch.
auth := NewPackageHashAuthentication([]string{"h1:invalid"})
result, err := auth.AuthenticatePackage(test.location)
if result != nil {
t.Errorf("wrong result: got %#v, want nil", result)
}
if gotErr := err.Error(); gotErr != test.err {
t.Errorf("wrong err: got %q, want %q", gotErr, test.err)
}
})
}
}
// Archive checksum authentication requires a file fixture and a known-good
// SHA256 hash. The result should be "verified checksum".
func TestArchiveChecksumAuthentication_success(t *testing.T) {