add signature verification
Fetch the SHA256SUMS file and verify it's signature before downloading any plugins. This embeds the hashicorp public key in the binary. If the publickey is replaced, new releases will need to be cut anyway. A --verify-plugin=false flag will be added to skip signature verification in these cases.
This commit is contained in:
parent
afe891a80e
commit
415d562d36
|
@ -38,6 +38,10 @@ func providerName(name string) string {
|
||||||
return "terraform-provider-" + name
|
return "terraform-provider-" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providerFileName(name, version string) string {
|
||||||
|
return fmt.Sprintf("%s_%s_%s_%s.zip", providerName(name), version, runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
// providerVersionsURL returns the path to the released versions directory for the provider:
|
// providerVersionsURL returns the path to the released versions directory for the provider:
|
||||||
// https://releases.hashicorp.com/terraform-provider-name/
|
// https://releases.hashicorp.com/terraform-provider-name/
|
||||||
func providerVersionsURL(name string) string {
|
func providerVersionsURL(name string) string {
|
||||||
|
@ -48,7 +52,11 @@ func providerVersionsURL(name string) string {
|
||||||
// and ARCH:
|
// and ARCH:
|
||||||
// .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
// .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
||||||
func providerURL(name, version string) string {
|
func providerURL(name, version string) string {
|
||||||
fileName := fmt.Sprintf("%s_%s_%s_%s.zip", providerName(name), version, runtime.GOOS, runtime.GOARCH)
|
return fmt.Sprintf("%s%s/%s", providerVersionsURL(name), version, providerFileName(name, version))
|
||||||
|
}
|
||||||
|
|
||||||
|
func providerChecksumURL(name, version string) string {
|
||||||
|
fileName := fmt.Sprintf("%s_%s_SHA256SUMS", providerName(name), version)
|
||||||
u := fmt.Sprintf("%s%s/%s", providerVersionsURL(name), version, fileName)
|
u := fmt.Sprintf("%s%s/%s", providerVersionsURL(name), version, fileName)
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
@ -69,6 +77,9 @@ type ProviderInstaller struct {
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
PluginProtocolVersion uint
|
PluginProtocolVersion uint
|
||||||
|
|
||||||
|
// Skip checksum and signature verification
|
||||||
|
SkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
||||||
|
@ -93,6 +104,19 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
||||||
// take the first matching plugin we find
|
// take the first matching plugin we find
|
||||||
for _, v := range versions {
|
for _, v := range versions {
|
||||||
url := providerURL(provider, v.String())
|
url := providerURL(provider, v.String())
|
||||||
|
|
||||||
|
if !i.SkipVerify {
|
||||||
|
sha256, err := getProviderChecksum(provider, v.String())
|
||||||
|
if err != nil {
|
||||||
|
return PluginMeta{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the checksum parameter for go-getter to verify the download for us.
|
||||||
|
if sha256 != "" {
|
||||||
|
url = url + "?checksum=sha256:" + sha256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v)
|
log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v)
|
||||||
if checkPlugin(url, i.PluginProtocolVersion) {
|
if checkPlugin(url, i.PluginProtocolVersion) {
|
||||||
log.Printf("[DEBUG] getting provider %q version %q at %s", provider, v, url)
|
log.Printf("[DEBUG] getting provider %q version %q at %s", provider, v, url)
|
||||||
|
@ -107,10 +131,10 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
||||||
// the archive directly into a shared dir here.)
|
// the archive directly into a shared dir here.)
|
||||||
log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider, v)
|
log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider, v)
|
||||||
metas := FindPlugins("provider", []string{i.Dir})
|
metas := FindPlugins("provider", []string{i.Dir})
|
||||||
log.Printf("all plugins found %#v", metas)
|
log.Printf("[DEBUG] all plugins found %#v", metas)
|
||||||
metas, _ = metas.ValidateVersions()
|
metas, _ = metas.ValidateVersions()
|
||||||
metas = metas.WithName(provider).WithVersion(v)
|
metas = metas.WithName(provider).WithVersion(v)
|
||||||
log.Printf("filtered plugins %#v", metas)
|
log.Printf("[DEBUG] filtered plugins %#v", metas)
|
||||||
if metas.Count() == 0 {
|
if metas.Count() == 0 {
|
||||||
// This should never happen. Suggests that the release archive
|
// This should never happen. Suggests that the release archive
|
||||||
// contains an executable file whose name doesn't match the
|
// contains an executable file whose name doesn't match the
|
||||||
|
@ -287,3 +311,61 @@ func versionsFromNames(names []string) []Version {
|
||||||
|
|
||||||
return versions
|
return versions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getProviderChecksum(name, version string) (string, error) {
|
||||||
|
checksums, err := getPluginSHA256SUMs(providerChecksumURL(name, version))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksumForFile(checksums, providerFileName(name, version)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checksumForFile(sums []byte, name string) string {
|
||||||
|
for _, line := range strings.Split(string(sums), "\n") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) > 1 && parts[1] == name {
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the SHA256SUMS file provided, and verify its signature.
|
||||||
|
func getPluginSHA256SUMs(sumsURL string) ([]byte, error) {
|
||||||
|
sigURL := sumsURL + ".sig"
|
||||||
|
|
||||||
|
sums, err := getFile(sumsURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching checksums: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := getFile(sigURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error fetching checksums signature: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := verifySig(sums, sig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sums, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFile(url string) ([]byte, error) {
|
||||||
|
resp, err := httpClient.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
|
@ -119,8 +119,8 @@ func TestProviderInstallerGet(t *testing.T) {
|
||||||
// attempt to use an incompatible protocol version
|
// attempt to use an incompatible protocol version
|
||||||
i := &ProviderInstaller{
|
i := &ProviderInstaller{
|
||||||
Dir: tmpDir,
|
Dir: tmpDir,
|
||||||
|
|
||||||
PluginProtocolVersion: 5,
|
PluginProtocolVersion: 5,
|
||||||
|
SkipVerify: true,
|
||||||
}
|
}
|
||||||
_, err = i.Get("test", AllVersions)
|
_, err = i.Get("test", AllVersions)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -129,8 +129,8 @@ func TestProviderInstallerGet(t *testing.T) {
|
||||||
|
|
||||||
i = &ProviderInstaller{
|
i = &ProviderInstaller{
|
||||||
Dir: tmpDir,
|
Dir: tmpDir,
|
||||||
|
|
||||||
PluginProtocolVersion: 3,
|
PluginProtocolVersion: 3,
|
||||||
|
SkipVerify: true,
|
||||||
}
|
}
|
||||||
gotMeta, err := i.Get("test", AllVersions)
|
gotMeta, err := i.Get("test", AllVersions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -185,8 +185,8 @@ func TestProviderInstallerPurgeUnused(t *testing.T) {
|
||||||
|
|
||||||
i := &ProviderInstaller{
|
i := &ProviderInstaller{
|
||||||
Dir: tmpDir,
|
Dir: tmpDir,
|
||||||
|
|
||||||
PluginProtocolVersion: 3,
|
PluginProtocolVersion: 3,
|
||||||
|
SkipVerify: true,
|
||||||
}
|
}
|
||||||
purged, err := i.PurgeUnused(map[string]PluginMeta{
|
purged, err := i.PurgeUnused(map[string]PluginMeta{
|
||||||
"test": PluginMeta{
|
"test": PluginMeta{
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify the data using the provided openpgp detached signature and the
|
||||||
|
// embedded hashicorp public key.
|
||||||
|
func verifySig(data, sig []byte) error {
|
||||||
|
el, err := openpgp.ReadArmoredKeyRing(strings.NewReader(hashiPublicKey))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = openpgp.CheckDetachedSignature(el, bytes.NewReader(data), bytes.NewReader(sig))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the public key that signs the checksums file for releases.
|
||||||
|
const hashiPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v1
|
||||||
|
|
||||||
|
mQENBFMORM0BCADBRyKO1MhCirazOSVwcfTr1xUxjPvfxD3hjUwHtjsOy/bT6p9f
|
||||||
|
W2mRPfwnq2JB5As+paL3UGDsSRDnK9KAxQb0NNF4+eVhr/EJ18s3wwXXDMjpIifq
|
||||||
|
fIm2WyH3G+aRLTLPIpscUNKDyxFOUbsmgXAmJ46Re1fn8uKxKRHbfa39aeuEYWFA
|
||||||
|
3drdL1WoUngvED7f+RnKBK2G6ZEpO+LDovQk19xGjiMTtPJrjMjZJ3QXqPvx5wca
|
||||||
|
KSZLr4lMTuoTI/ZXyZy5bD4tShiZz6KcyX27cD70q2iRcEZ0poLKHyEIDAi3TM5k
|
||||||
|
SwbbWBFd5RNPOR0qzrb/0p9ksKK48IIfH2FvABEBAAG0K0hhc2hpQ29ycCBTZWN1
|
||||||
|
cml0eSA8c2VjdXJpdHlAaGFzaGljb3JwLmNvbT6JATgEEwECACIFAlMORM0CGwMG
|
||||||
|
CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEFGFLYc0j/xMyWIIAIPhcVqiQ59n
|
||||||
|
Jc07gjUX0SWBJAxEG1lKxfzS4Xp+57h2xxTpdotGQ1fZwsihaIqow337YHQI3q0i
|
||||||
|
SqV534Ms+j/tU7X8sq11xFJIeEVG8PASRCwmryUwghFKPlHETQ8jJ+Y8+1asRydi
|
||||||
|
psP3B/5Mjhqv/uOK+Vy3zAyIpyDOMtIpOVfjSpCplVRdtSTFWBu9Em7j5I2HMn1w
|
||||||
|
sJZnJgXKpybpibGiiTtmnFLOwibmprSu04rsnP4ncdC2XRD4wIjoyA+4PKgX3sCO
|
||||||
|
klEzKryWYBmLkJOMDdo52LttP3279s7XrkLEE7ia0fXa2c12EQ0f0DQ1tGUvyVEW
|
||||||
|
WmJVccm5bq25AQ0EUw5EzQEIANaPUY04/g7AmYkOMjaCZ6iTp9hB5Rsj/4ee/ln9
|
||||||
|
wArzRO9+3eejLWh53FoN1rO+su7tiXJA5YAzVy6tuolrqjM8DBztPxdLBbEi4V+j
|
||||||
|
2tK0dATdBQBHEh3OJApO2UBtcjaZBT31zrG9K55D+CrcgIVEHAKY8Cb4kLBkb5wM
|
||||||
|
skn+DrASKU0BNIV1qRsxfiUdQHZfSqtp004nrql1lbFMLFEuiY8FZrkkQ9qduixo
|
||||||
|
mTT6f34/oiY+Jam3zCK7RDN/OjuWheIPGj/Qbx9JuNiwgX6yRj7OE1tjUx6d8g9y
|
||||||
|
0H1fmLJbb3WZZbuuGFnK6qrE3bGeY8+AWaJAZ37wpWh1p0cAEQEAAYkBHwQYAQIA
|
||||||
|
CQUCUw5EzQIbDAAKCRBRhS2HNI/8TJntCAClU7TOO/X053eKF1jqNW4A1qpxctVc
|
||||||
|
z8eTcY8Om5O4f6a/rfxfNFKn9Qyja/OG1xWNobETy7MiMXYjaa8uUx5iFy6kMVaP
|
||||||
|
0BXJ59NLZjMARGw6lVTYDTIvzqqqwLxgliSDfSnqUhubGwvykANPO+93BBx89MRG
|
||||||
|
unNoYGXtPlhNFrAsB1VR8+EyKLv2HQtGCPSFBhrjuzH3gxGibNDDdFQLxxuJWepJ
|
||||||
|
EK1UbTS4ms0NgZ2Uknqn1WRU1Ki7rE4sTy68iZtWpKQXZEJa0IGnuI2sSINGcXCJ
|
||||||
|
oEIgXTMyCILo34Fa/C6VCm2WBgz9zZO8/rHIiQm1J5zqz0DrDwKBUM9C
|
||||||
|
=LYpS
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----`
|
Loading…
Reference in New Issue