plugin/discovery: verify checksum matches Registry response

This commit is contained in:
findkim 2019-03-21 11:17:15 -05:00
parent 2632ccc5d3
commit 1a32617d5e
3 changed files with 73 additions and 26 deletions

View File

@ -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 the publisher of the provider removed the key it was signed with, or that the
distributed files were changed after this version was released.` 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 httpClient *http.Client
var errVersionNotFound = errors.New("version not found") var errVersionNotFound = errors.New("version not found")
@ -391,21 +397,21 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS
return removed, errs return removed, errs
} }
func (i *ProviderInstaller) getProviderChecksum(urls *response.TerraformProviderPlatformLocation) (string, error) { func (i *ProviderInstaller) getProviderChecksum(resp *response.TerraformProviderPlatformLocation) (string, error) {
// Get SHA256SUMS file. // Get SHA256SUMS file.
shasums, err := getFile(urls.ShasumsURL) shasums, err := getFile(resp.ShasumsURL)
if err != nil { if err != nil {
return "", fmt.Errorf("error fetching checksums: %s", err) return "", fmt.Errorf("error fetching checksums: %s", err)
} }
// Get SHA256SUMS.sig file. // Get SHA256SUMS.sig file.
signature, err := getFile(urls.ShasumsSignatureURL) signature, err := getFile(resp.ShasumsSignatureURL)
if err != nil { if err != nil {
return "", fmt.Errorf("error fetching checksums signature: %s", err) return "", fmt.Errorf("error fetching checksums signature: %s", err)
} }
// Verify the GPG signature returned from the Registry. // Verify the GPG signature returned from the Registry.
asciiArmor := urls.SigningKeys.GPGASCIIArmor() asciiArmor := resp.SigningKeys.GPGASCIIArmor()
signer, err := verifySig(shasums, signature, asciiArmor) signer, err := verifySig(shasums, signature, asciiArmor)
if err != nil { if err != nil {
log.Printf("[ERROR] error verifying signature: %s", err) log.Printf("[ERROR] error verifying signature: %s", err)
@ -430,8 +436,13 @@ func (i *ProviderInstaller) getProviderChecksum(urls *response.TerraformProvider
identity := strings.Join(identities, ", ") identity := strings.Join(identities, ", ")
log.Printf("[DEBUG] verified GPG signature with key from %s", identity) log.Printf("[DEBUG] verified GPG signature with key from %s", identity)
// Extract checksum for this os/arch platform binary. // Extract checksum for this os/arch platform binary and verify against Registry
return checksumForFile(shasums, urls.Filename), nil 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) { func (i *ProviderInstaller) hostname() (string, error) {

View File

@ -606,13 +606,14 @@ func TestProviderChecksum(t *testing.T) {
tests := []struct { tests := []struct {
Name string Name string
URLs *response.TerraformProviderPlatformLocation Resp *response.TerraformProviderPlatformLocation
Err bool Err bool
}{ }{
{ {
"good", "good",
&response.TerraformProviderPlatformLocation{ &response.TerraformProviderPlatformLocation{
Filename: "terraform-provider-template_0.1.0_darwin_amd64.zip", 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", 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", ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS.sig",
SigningKeys: response.SigningKeyList{ SigningKeys: response.SigningKeyList{
@ -653,32 +654,68 @@ func TestProviderChecksum(t *testing.T) {
}, },
true, 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{} i := ProviderInstaller{}
for _, test := range tests { for _, test := range tests {
sha256sum, err := i.getProviderChecksum(test.URLs) t.Run(test.Name, func(t *testing.T) {
if test.Err { sha256sum, err := i.getProviderChecksum(test.Resp)
if err == nil { if test.Err {
t.Fatal("succeeded; want error") 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 // get the expected checksum for our os/arch
sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS") sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := checksumForFile(sumData, test.URLs.Filename) expected := checksumForFile(sumData, test.Resp.Filename)
if sha256sum != expected { if sha256sum != expected {
t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected) t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected)
} }
})
} }
} }

View File

@ -2,7 +2,6 @@ package discovery
import ( import (
"bytes" "bytes"
"log"
"strings" "strings"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
@ -13,7 +12,7 @@ import (
func verifySig(data, sig []byte, armor string) (*openpgp.Entity, error) { func verifySig(data, sig []byte, armor string) (*openpgp.Entity, error) {
el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armor)) el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armor))
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
return openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig)) return openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig))