diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index 1702bff46..795782d6a 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -36,6 +36,12 @@ files using the keys downloaded from the Terraform Registry. This may mean that the publisher of the provider removed the key it was signed with, or that the distributed files were changed after this version was released.` +const checksumVerificationError = `Checksum verification error: +The checksum for provider distribution %q from the Terraform Registry +did not match the source (%s). +This may mean that the distributed files were changed after this version +was released.` + var httpClient *http.Client var errVersionNotFound = errors.New("version not found") @@ -391,21 +397,21 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS return removed, errs } -func (i *ProviderInstaller) getProviderChecksum(urls *response.TerraformProviderPlatformLocation) (string, error) { +func (i *ProviderInstaller) getProviderChecksum(resp *response.TerraformProviderPlatformLocation) (string, error) { // Get SHA256SUMS file. - shasums, err := getFile(urls.ShasumsURL) + shasums, err := getFile(resp.ShasumsURL) if err != nil { return "", fmt.Errorf("error fetching checksums: %s", err) } // Get SHA256SUMS.sig file. - signature, err := getFile(urls.ShasumsSignatureURL) + signature, err := getFile(resp.ShasumsSignatureURL) if err != nil { return "", fmt.Errorf("error fetching checksums signature: %s", err) } // Verify the GPG signature returned from the Registry. - asciiArmor := urls.SigningKeys.GPGASCIIArmor() + asciiArmor := resp.SigningKeys.GPGASCIIArmor() signer, err := verifySig(shasums, signature, asciiArmor) if err != nil { log.Printf("[ERROR] error verifying signature: %s", err) @@ -430,8 +436,13 @@ func (i *ProviderInstaller) getProviderChecksum(urls *response.TerraformProvider identity := strings.Join(identities, ", ") log.Printf("[DEBUG] verified GPG signature with key from %s", identity) - // Extract checksum for this os/arch platform binary. - return checksumForFile(shasums, urls.Filename), nil + // Extract checksum for this os/arch platform binary and verify against Registry + checksum := checksumForFile(shasums, resp.Filename) + if checksum == "" || checksum != resp.Shasum { + return "", fmt.Errorf(checksumVerificationError, resp.Filename, resp.ShasumsURL) + } + + return checksum, nil } func (i *ProviderInstaller) hostname() (string, error) { diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index f4ee8bef3..56c802c8e 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -606,13 +606,14 @@ func TestProviderChecksum(t *testing.T) { tests := []struct { Name string - URLs *response.TerraformProviderPlatformLocation + Resp *response.TerraformProviderPlatformLocation Err bool }{ { "good", &response.TerraformProviderPlatformLocation{ Filename: "terraform-provider-template_0.1.0_darwin_amd64.zip", + Shasum: "3c3e7df78b1f0161a3f941c271d5501f7b5e5f2c53738e7a371459712f5d4726", ShasumsURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS", ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS.sig", SigningKeys: response.SigningKeyList{ @@ -653,32 +654,68 @@ func TestProviderChecksum(t *testing.T) { }, true, }, + { + "mismatch checksum", + &response.TerraformProviderPlatformLocation{ + Filename: "terraform-provider-template_0.1.0_darwin_amd64.zip", + Shasum: "force mismatch", + ShasumsURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS", + ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS.sig", + SigningKeys: response.SigningKeyList{ + GPGKeys: []*response.GPGKey{ + &response.GPGKey{ + ASCIIArmor: string(hashicorpKey), + }, + }, + }, + }, + true, + }, + { + "missing checksum for file", + &response.TerraformProviderPlatformLocation{ + Filename: "terraform-provider-template_0.1.0_darwin_amd64_missing_checksum.zip", + Shasum: "checksum", + ShasumsURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS", + ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS.sig", + SigningKeys: response.SigningKeyList{ + GPGKeys: []*response.GPGKey{ + &response.GPGKey{ + ASCIIArmor: string(hashicorpKey), + }, + }, + }, + }, + true, + }, } i := ProviderInstaller{} for _, test := range tests { - sha256sum, err := i.getProviderChecksum(test.URLs) - if test.Err { - if err == nil { - t.Fatal("succeeded; want error") + t.Run(test.Name, func(t *testing.T) { + sha256sum, err := i.getProviderChecksum(test.Resp) + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + return + } else if err != nil { + t.Fatalf("unexpected error: %s", err) } - return - } else if err != nil { - t.Fatalf("unexpected error: %s", err) - } - // get the expected checksum for our os/arch - sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS") - if err != nil { - t.Fatal(err) - } + // get the expected checksum for our os/arch + sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS") + if err != nil { + t.Fatal(err) + } - expected := checksumForFile(sumData, test.URLs.Filename) + expected := checksumForFile(sumData, test.Resp.Filename) - if sha256sum != expected { - t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected) - } + if sha256sum != expected { + t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected) + } + }) } } diff --git a/plugin/discovery/signature.go b/plugin/discovery/signature.go index 4e941aec6..7bbae50c3 100644 --- a/plugin/discovery/signature.go +++ b/plugin/discovery/signature.go @@ -2,7 +2,6 @@ package discovery import ( "bytes" - "log" "strings" "golang.org/x/crypto/openpgp" @@ -13,7 +12,7 @@ import ( func verifySig(data, sig []byte, armor string) (*openpgp.Entity, error) { el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armor)) if err != nil { - log.Fatal(err) + return nil, err } return openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig))