plugin/discovery: verify checksum matches Registry response
This commit is contained in:
parent
2632ccc5d3
commit
1a32617d5e
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue