mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Allow expired provider pgp keys, with warning (#848)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
04c62c5726
commit
6366b2dbf6
@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
openpgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
openpgpPacket "github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
tfaddr "github.com/opentofu/registry-address"
|
||||
)
|
||||
|
||||
@ -30,12 +29,7 @@ const (
|
||||
|
||||
const (
|
||||
enforceGPGValidationEnvName = "OPENTOFU_ENFORCE_GPG_VALIDATION"
|
||||
)
|
||||
|
||||
var (
|
||||
// openpgpConfig is only populated during testing, so that a fake clock can be
|
||||
// injected, preventing signature expiration errors.
|
||||
openpgpConfig *openpgpPacket.Config
|
||||
enforceGPGExpirationEnvName = "OPENTOFU_ENFORCE_GPG_EXPIRATION"
|
||||
)
|
||||
|
||||
// PackageAuthenticationResult is returned from a PackageAuthentication
|
||||
@ -360,6 +354,7 @@ type signatureAuthentication struct {
|
||||
Signature []byte
|
||||
Keys []SigningKey
|
||||
ProviderSource *tfaddr.Provider
|
||||
Meta PackageMeta
|
||||
}
|
||||
|
||||
// NewSignatureAuthentication returns a PackageAuthentication implementation
|
||||
@ -378,12 +373,13 @@ type signatureAuthentication struct {
|
||||
//
|
||||
// Any failure in the process of validating the signature will result in an
|
||||
// unauthenticated result.
|
||||
func NewSignatureAuthentication(document, signature []byte, keys []SigningKey, source *tfaddr.Provider) PackageAuthentication {
|
||||
func NewSignatureAuthentication(meta PackageMeta, document, signature []byte, keys []SigningKey, source *tfaddr.Provider) PackageAuthentication {
|
||||
return signatureAuthentication{
|
||||
Document: document,
|
||||
Signature: signature,
|
||||
Keys: keys,
|
||||
ProviderSource: source,
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +398,11 @@ func (s signatureAuthentication) shouldEnforceGPGValidation() (bool, error) {
|
||||
enforceEnvVar, exists := os.LookupEnv(enforceGPGValidationEnvName)
|
||||
return exists && enforceEnvVar == "true", nil
|
||||
}
|
||||
func (s signatureAuthentication) shouldEnforceGPGExpiration() bool {
|
||||
// otherwise if the environment variable is set to true, we should enforce GPG expiration
|
||||
enforceEnvVar, exists := os.LookupEnv(enforceGPGExpirationEnvName)
|
||||
return exists && enforceEnvVar == "true"
|
||||
}
|
||||
|
||||
func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (*PackageAuthenticationResult, error) {
|
||||
shouldValidate, err := s.shouldEnforceGPGValidation()
|
||||
@ -424,6 +425,7 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
|
||||
|
||||
// Find the key that signed the checksum file. This can fail if there is no
|
||||
// valid signature for any of the provided keys.
|
||||
|
||||
_, keyID, err := s.findSigningKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -491,7 +493,13 @@ func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) {
|
||||
return nil, "", fmt.Errorf("error decoding signing key: %w", err)
|
||||
}
|
||||
|
||||
entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature), openpgpConfig)
|
||||
entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature), nil)
|
||||
if !s.shouldEnforceGPGExpiration() && (err == openpgpErrors.ErrSignatureExpired || err == openpgpErrors.ErrKeyExpired) {
|
||||
// Internally openpgp will *only* return the Expired errors if all other checks have succeded
|
||||
// This is currently the best way to work around expired provider keys
|
||||
fmt.Printf("[WARN] Provider %s/%s (%v) gpg key expired, this will fail in future versions of OpenTofu\n", s.Meta.Provider.Namespace, s.Meta.Provider.Type, s.Meta.Provider.Hostname)
|
||||
err = nil
|
||||
}
|
||||
|
||||
// If the signature issuer does not match the key, keep trying the
|
||||
// rest of the provided keys.
|
||||
|
@ -8,31 +8,17 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
openpgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
tfaddr "github.com/opentofu/registry-address"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
openpgpConfig = &packet.Config{
|
||||
Time: func() time.Time {
|
||||
// Scientifically chosen time that satisfies the validity periods of all
|
||||
// of the keys and signatures used.
|
||||
t, _ := time.Parse(time.RFC3339, "2021-04-25T16:00:00-07:00")
|
||||
return t
|
||||
},
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestPackageAuthenticationResult(t *testing.T) {
|
||||
tests := []struct {
|
||||
result *PackageAuthenticationResult
|
||||
@ -256,6 +242,7 @@ func TestMatchingChecksumAuthentication_success(t *testing.T) {
|
||||
auth := NewMatchingChecksumAuthentication(document, filename, wantSHA256Sum)
|
||||
result, err := auth.AuthenticatePackage(location)
|
||||
|
||||
// NOTE: This also tests the expired key ignore logic as they key in the test is expired
|
||||
if result != nil {
|
||||
t.Errorf("wrong result: got %#v, want nil", result)
|
||||
}
|
||||
@ -378,7 +365,7 @@ func TestSignatureAuthentication_success(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
auth := NewSignatureAuthentication([]byte(testShaSumsPlaceholder), signature, test.keys, nil)
|
||||
auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testShaSumsPlaceholder), signature, test.keys, nil)
|
||||
result, err := auth.AuthenticatePackage(location)
|
||||
|
||||
if result == nil || *result != test.result {
|
||||
@ -421,7 +408,7 @@ func TestNewSignatureAuthentication_success(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
auth := NewSignatureAuthentication([]byte(testProviderShaSums), signature, test.keys, nil)
|
||||
auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testProviderShaSums), signature, test.keys, nil)
|
||||
result, err := auth.AuthenticatePackage(location)
|
||||
|
||||
if result == nil || *result != test.result {
|
||||
@ -433,6 +420,42 @@ func TestNewSignatureAuthentication_success(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestNewSignatureAuthentication_expired(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
signature string
|
||||
keys []SigningKey
|
||||
}{
|
||||
"official provider": {
|
||||
testHashicorpSignatureGoodBase64,
|
||||
[]SigningKey{
|
||||
{
|
||||
ASCIIArmor: TestingPublicKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Setenv(enforceGPGExpirationEnvName, "true")
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Location is unused
|
||||
location := PackageLocalArchive("testdata/my-package.zip")
|
||||
|
||||
signature, err := base64.StdEncoding.DecodeString(test.signature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testProviderShaSums), signature, test.keys, nil)
|
||||
_, err = auth.AuthenticatePackage(location)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("wrong err: got %s, want %s", err, openpgpErrors.ErrKeyExpired)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Setenv(enforceGPGExpirationEnvName, "")
|
||||
}
|
||||
|
||||
// Signature authentication can fail for many reasons, most of which are due
|
||||
// to OpenPGP failures from malformed keys or signatures.
|
||||
@ -481,7 +504,7 @@ func TestSignatureAuthentication_failure(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
auth := NewSignatureAuthentication([]byte(testShaSumsPlaceholder), signature, test.keys, nil)
|
||||
auth := NewSignatureAuthentication(PackageMeta{Location: location}, []byte(testShaSumsPlaceholder), signature, test.keys, nil)
|
||||
result, err := auth.AuthenticatePackage(location)
|
||||
|
||||
if result != nil {
|
||||
@ -495,7 +518,7 @@ func TestSignatureAuthentication_failure(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSignatureAuthentication_acceptableHashes(t *testing.T) {
|
||||
auth := NewSignatureAuthentication([]byte(testShaSumsRealistic), nil, nil, nil)
|
||||
auth := NewSignatureAuthentication(PackageMeta{}, []byte(testShaSumsRealistic), nil, nil, nil)
|
||||
authWithHashes, ok := auth.(PackageAuthenticationHashes)
|
||||
if !ok {
|
||||
t.Fatalf("%T does not implement PackageAuthenticationHashes", auth)
|
||||
|
@ -357,7 +357,7 @@ func (c *registryClient) PackageMeta(ctx context.Context, provider addrs.Provide
|
||||
ret.Authentication = PackageAuthenticationAll(
|
||||
NewMatchingChecksumAuthentication(document, body.Filename, checksum),
|
||||
NewArchiveChecksumAuthentication(ret.TargetPlatform, checksum),
|
||||
NewSignatureAuthentication(document, signature, keys, &provider),
|
||||
NewSignatureAuthentication(ret, document, signature, keys, &provider),
|
||||
)
|
||||
|
||||
return ret, nil
|
||||
|
@ -116,6 +116,34 @@ func TestSourcePackageMeta(t *testing.T) {
|
||||
source, baseURL, close := testRegistrySource(t)
|
||||
defer close()
|
||||
|
||||
validMeta := PackageMeta{
|
||||
Provider: addrs.NewProvider(
|
||||
svchost.Hostname("example.com"), "awesomesauce", "happycloud",
|
||||
),
|
||||
Version: versions.MustParseVersion("1.2.0"),
|
||||
ProtocolVersions: VersionList{versions.MustParseVersion("5.0.0")},
|
||||
TargetPlatform: Platform{"linux", "amd64"},
|
||||
Filename: "happycloud_1.2.0.zip",
|
||||
Location: PackageHTTPURL(baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip"),
|
||||
}
|
||||
validMeta.Authentication = PackageAuthenticationAll(
|
||||
NewMatchingChecksumAuthentication(
|
||||
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
|
||||
"happycloud_1.2.0.zip",
|
||||
[32]byte{30: 0xf0, 31: 0x0d},
|
||||
),
|
||||
NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [32]byte{30: 0xf0, 31: 0x0d}),
|
||||
NewSignatureAuthentication(
|
||||
validMeta,
|
||||
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
|
||||
[]byte("GPG signature"),
|
||||
[]SigningKey{
|
||||
{ASCIIArmor: TestingPublicKey},
|
||||
},
|
||||
&tfaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
|
||||
),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
provider string
|
||||
version string
|
||||
@ -130,32 +158,7 @@ func TestSourcePackageMeta(t *testing.T) {
|
||||
"example.com/awesomesauce/happycloud",
|
||||
"1.2.0",
|
||||
"linux", "amd64",
|
||||
PackageMeta{
|
||||
Provider: addrs.NewProvider(
|
||||
svchost.Hostname("example.com"), "awesomesauce", "happycloud",
|
||||
),
|
||||
Version: versions.MustParseVersion("1.2.0"),
|
||||
ProtocolVersions: VersionList{versions.MustParseVersion("5.0.0")},
|
||||
TargetPlatform: Platform{"linux", "amd64"},
|
||||
Filename: "happycloud_1.2.0.zip",
|
||||
Location: PackageHTTPURL(baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip"),
|
||||
Authentication: PackageAuthenticationAll(
|
||||
NewMatchingChecksumAuthentication(
|
||||
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
|
||||
"happycloud_1.2.0.zip",
|
||||
[32]byte{30: 0xf0, 31: 0x0d},
|
||||
),
|
||||
NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [32]byte{30: 0xf0, 31: 0x0d}),
|
||||
NewSignatureAuthentication(
|
||||
[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
|
||||
[]byte("GPG signature"),
|
||||
[]SigningKey{
|
||||
{ASCIIArmor: TestingPublicKey},
|
||||
},
|
||||
&tfaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
|
||||
),
|
||||
),
|
||||
},
|
||||
validMeta,
|
||||
[]Hash{
|
||||
"zh:000000000000000000000000000000000000000000000000000000000000f00d",
|
||||
"zh:000000000000000000000000000000000000000000000000000000000000face",
|
||||
@ -233,7 +236,7 @@ func TestSourcePackageMeta(t *testing.T) {
|
||||
t.Fatalf("wrong error\ngot: <nil>\nwant: %s", test.wantErr)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(test.want, got, cmpOpts); diff != "" {
|
||||
if diff := cmp.Diff(got, test.want, cmpOpts); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(test.wantHashes, got.AcceptableHashes()); diff != "" {
|
||||
|
Loading…
Reference in New Issue
Block a user