Reworked gpg key expiry warning to only fire if ALL keys are expired (#2476)

Signed-off-by: James Humphries <James@james-humphries.co.uk>
Co-authored-by: AbstractionFactory <179820029+abstractionfactory@users.noreply.github.com>
This commit is contained in:
James Humphries 2025-02-20 13:33:39 +00:00 committed by GitHub
parent 9a86d86f4d
commit d2ae0b21ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -12,7 +12,6 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"log" "log"
"os" "os"
"strings" "strings"
@ -490,42 +489,57 @@ func (s signatureAuthentication) AcceptableHashes() []Hash {
// Note: currently the registry only returns one key, but this may change in // Note: currently the registry only returns one key, but this may change in
// the future. // the future.
func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) { func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) {
var expiredKey *SigningKey
var expiredKeyID string
for _, key := range s.Keys { for _, key := range s.Keys {
keyCopy := key
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor)) keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor))
if err != nil { if err != nil {
return nil, "", fmt.Errorf("error decoding signing key: %w", err) return nil, "", fmt.Errorf("error decoding signing key: %w", err)
} }
entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature), nil) entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature), nil)
if !s.shouldEnforceGPGExpiration() && (errors.Is(err, openpgpErrors.ErrKeyExpired) || errors.Is(err, openpgpErrors.ErrSignatureExpired)) {
// Internally openpgp will *only* return the Expired errors if all other checks have succeeded
// 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.
if errors.Is(err, openpgpErrors.ErrUnknownIssuer) { if errors.Is(err, openpgpErrors.ErrUnknownIssuer) {
continue continue
} }
// Any other signature error is terminal.
if err != nil { if err != nil {
return nil, "", fmt.Errorf("error checking signature: %w", err) // If in enforcing mode (or if the error isnt related to expiry) return immediately.
if !errors.Is(err, openpgpErrors.ErrKeyExpired) && !errors.Is(err, openpgpErrors.ErrSignatureExpired) {
return nil, "", fmt.Errorf("error checking signature: %w", err)
}
// Else if it's an expired key then save it for later incase we don't find a nonexpired key.
if expiredKey == nil {
expiredKey = &keyCopy
if entity != nil && entity.PrimaryKey != nil {
expiredKeyID = entity.PrimaryKey.KeyIdString()
} else {
expiredKeyID = "n/a" //nolint:goconst // This is a placeholder value
}
}
continue
} }
// Success! This key verified without an error.
keyID := "n/a" keyID := "n/a"
if entity.PrimaryKey != nil { if entity.PrimaryKey != nil {
keyID = entity.PrimaryKey.KeyIdString() keyID = entity.PrimaryKey.KeyIdString()
} }
log.Printf("[DEBUG] Provider signed by %s", entityString(entity)) log.Printf("[DEBUG] Provider signed by %s", entityString(entity))
return &key, keyID, nil return &key, keyID, nil
} }
// If none of the provided keys issued the signature, this package is // Warn only once when ALL keys are expired.
// unsigned. This is currently a terminal authentication error. if expiredKey != nil && !s.shouldEnforceGPGExpiration() {
//nolint:forbidigo // This is a warning message and is fine to be handled this way
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)
return expiredKey, expiredKeyID, nil
}
// If we got here, no candidate was acceptable.
return nil, "", ErrUnknownIssuer return nil, "", ErrUnknownIssuer
} }