diff --git a/internal/getproviders/package_authentication.go b/internal/getproviders/package_authentication.go index 987908df6..10f000a26 100644 --- a/internal/getproviders/package_authentication.go +++ b/internal/getproviders/package_authentication.go @@ -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} } diff --git a/internal/getproviders/package_authentication_test.go b/internal/getproviders/package_authentication_test.go index 521306db5..5b734183d 100644 --- a/internal/getproviders/package_authentication_test.go +++ b/internal/getproviders/package_authentication_test.go @@ -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) {