Fix #1407: Pass through metadata fields in state encryption (#1417)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-03-28 11:14:08 -04:00 committed by GitHub
parent 641751f163
commit 979bf5ce3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 57 additions and 9 deletions

View File

@ -87,7 +87,7 @@ func IsEncryptionPayload(data []byte) (bool, error) {
return es.Version != "", nil
}
func (s *baseEncryption) encrypt(data []byte) ([]byte, error) {
func (s *baseEncryption) encrypt(data []byte, enhance func(basedata) interface{}) ([]byte, error) {
// No configuration provided, don't do anything
if s.target == nil {
return data, nil
@ -117,7 +117,7 @@ func (s *baseEncryption) encrypt(data []byte) ([]byte, error) {
Meta: s.encMeta,
Data: encd,
}
jsond, err := json.Marshal(es)
jsond, err := json.Marshal(enhance(es))
if err != nil {
return nil, fmt.Errorf("unable to encode encrypted data as json: %w", err)
}

View File

@ -63,15 +63,15 @@ func Example() {
sfe := enc.State()
// Encrypt the data, for this example we will be using the string "test",
// Encrypt the data, for this example we will be using the string `{"serial": 42, "lineage": "magic"}`,
// but in a real world scenario this would be the plan file.
sourceData := []byte("test")
sourceData := []byte(`{"serial": 42, "lineage": "magic"}`)
encrypted, err := sfe.EncryptState(sourceData)
if err != nil {
panic(err)
}
if string(encrypted) == "test" {
if string(encrypted) == `{"serial": 42, "lineage": "magic"}` {
panic("The data has not been encrypted!")
}
@ -82,7 +82,7 @@ func Example() {
}
fmt.Printf("%s\n", decryptedState)
// Output: test
// Output: {"serial": 42, "lineage": "magic"}
}
func handleDiags(diags hcl.Diagnostics) {

View File

@ -55,7 +55,7 @@ func newPlanEncryption(enc *encryption, target *config.TargetConfig, enforced bo
}
func (p planEncryption) EncryptPlan(data []byte) ([]byte, error) {
return p.base.encrypt(data)
return p.base.encrypt(data, func(base basedata) interface{} { return base })
}
func (p planEncryption) DecryptPlan(data []byte) ([]byte, error) {

View File

@ -62,12 +62,32 @@ func newStateEncryption(enc *encryption, target *config.TargetConfig, enforced b
return &stateEncryption{base}, diags
}
type statedata struct {
Serial *int `json:"serial"`
Lineage string `json:"lineage"`
}
func (s *stateEncryption) EncryptState(plainState []byte) ([]byte, error) {
return s.base.encrypt(plainState)
var passthrough statedata
err := json.Unmarshal(plainState, &passthrough)
if err != nil {
return nil, err
}
return s.base.encrypt(plainState, func(base basedata) interface{} {
// Merge together the base encryption data and the passthrough fields
return struct {
statedata
basedata
}{
statedata: passthrough,
basedata: base,
}
})
}
func (s *stateEncryption) DecryptState(encryptedState []byte) ([]byte, error) {
return s.base.decrypt(encryptedState, func(data []byte) error {
decryptedState, err := s.base.decrypt(encryptedState, func(data []byte) error {
tmp := struct {
FormatVersion string `json:"terraform_version"`
}{}
@ -82,6 +102,34 @@ func (s *stateEncryption) DecryptState(encryptedState []byte) ([]byte, error) {
// Probably a state file
return nil
})
if err != nil {
return nil, err
}
// Make sure that the state passthrough fields match
var encrypted statedata
err = json.Unmarshal(encryptedState, &encrypted)
if err != nil {
return nil, err
}
var state statedata
err = json.Unmarshal(decryptedState, &state)
if err != nil {
return nil, err
}
// TODO make encrypted.Serial non-optional. This is only for supporting alpha1 states!
if encrypted.Serial != nil && state.Serial != nil && *state.Serial != *encrypted.Serial {
return nil, fmt.Errorf("invalid state metadata, serial field mismatch %v vs %v", *encrypted.Serial, *state.Serial)
}
// TODO make encrypted.Lineage non-optional. This is only for supporting alpha1 states!
if encrypted.Lineage != "" && state.Lineage != encrypted.Lineage {
return nil, fmt.Errorf("invalid state metadata, linage field mismatch %v vs %v", encrypted.Lineage, state.Lineage)
}
return decryptedState, nil
}
func StateEncryptionDisabled() StateEncryption {