Allow expired provider pgp keys, with warning ()

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2023-11-13 09:31:59 -05:00 committed by GitHub
parent 04c62c5726
commit 6366b2dbf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 56 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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 != "" {