mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-25 01:00:16 -05:00
State Encryption Documentation and Partial Implementation (#1227)
Signed-off-by: StephanHCB <sbs_github_u43a@packetloss.de> Signed-off-by: Christian Mesh <christianmesh1@gmail.com> Signed-off-by: Janos <86970079+janosdebugs@users.noreply.github.com> Signed-off-by: James Humphries <james@james-humphries.co.uk> Co-authored-by: StephanHCB <sbs_github_u43a@packetloss.de> Co-authored-by: Janos <86970079+janosdebugs@users.noreply.github.com> Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
73
internal/encryption/method/addr.go
Normal file
73
internal/encryption/method/addr.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package method
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// TODO is there a generalized way to regexp-check names?
|
||||
var addrRe = regexp.MustCompile(`^method\.([a-zA-Z_0-9-]+)\.([a-zA-Z_0-9-]+)$`)
|
||||
var nameRe = regexp.MustCompile("^([a-zA-Z_0-9-]+)$")
|
||||
|
||||
// Addr is a type-alias for method address strings that identify a specific encryption method configuration.
|
||||
// The Addr is an opaque value. Do not perform string manipulation on it outside the functions supplied by the
|
||||
// method package.
|
||||
type Addr string
|
||||
|
||||
// Validate validates the Addr for formal naming conformance, but does not check if the referenced method actually
|
||||
// exists in the configuration.
|
||||
func (a Addr) Validate() hcl.Diagnostics {
|
||||
if !addrRe.MatchString(string(a)) {
|
||||
return hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid encryption method address",
|
||||
Detail: fmt.Sprintf(
|
||||
"The supplied encryption method address does not match the required form of %s",
|
||||
addrRe.String(),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAddr creates a new Addr type from the provider and name supplied. The Addr is a type-alias for encryption method
|
||||
// address strings that identify a specific encryption method configuration. You should treat the value as opaque and
|
||||
// not perform string manipulation on it outside the functions supplied by the method package.
|
||||
func NewAddr(method string, name string) (addr Addr, err hcl.Diagnostics) {
|
||||
if !nameRe.MatchString(method) {
|
||||
err = err.Append(
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "The provided encryption method type is invalid",
|
||||
Detail: fmt.Sprintf(
|
||||
"The supplied encryption method type (%s) does not match the required form of %s.",
|
||||
method,
|
||||
nameRe.String(),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
if !nameRe.MatchString(name) {
|
||||
err = err.Append(
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "The provided encryption method name is invalid",
|
||||
Detail: fmt.Sprintf(
|
||||
"The supplied encryption method name (%s) does not match the required form of %s.",
|
||||
name,
|
||||
nameRe.String(),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
return Addr(fmt.Sprintf("method.%s.%s", method, name)), err
|
||||
}
|
||||
61
internal/encryption/method/aesgcm/aesgcm.go
Normal file
61
internal/encryption/method/aesgcm/aesgcm.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package aesgcm
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
type aesgcm struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
// Inspired by https://bruinsslot.jp/post/golang-crypto/
|
||||
|
||||
func (a aesgcm) Encrypt(data []byte) ([]byte, error) {
|
||||
// TODO change this to the implementation in Stephan's PR.
|
||||
blockCipher, err := aes.NewCipher(a.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (a aesgcm) Decrypt(data []byte) ([]byte, error) {
|
||||
blockCipher, err := aes.NewCipher(a.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
25
internal/encryption/method/aesgcm/config.go
Normal file
25
internal/encryption/method/aesgcm/config.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package aesgcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/encryption/method"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Key []byte `hcl:"cipher"`
|
||||
}
|
||||
|
||||
func (c Config) Build() (method.Method, error) {
|
||||
if len(c.Key) != 32 {
|
||||
return nil, fmt.Errorf("AES-GCM requires a 32-byte key")
|
||||
}
|
||||
return &aesgcm{
|
||||
c.Key,
|
||||
}, nil
|
||||
}
|
||||
24
internal/encryption/method/aesgcm/descriptor.go
Normal file
24
internal/encryption/method/aesgcm/descriptor.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package aesgcm
|
||||
|
||||
import "github.com/opentofu/opentofu/internal/encryption/method"
|
||||
|
||||
// New creates a new descriptor for the AES-GCM encryption method, which requires a 32-byte key.
|
||||
func New() method.Descriptor {
|
||||
return &descriptor{}
|
||||
}
|
||||
|
||||
type descriptor struct {
|
||||
}
|
||||
|
||||
func (f *descriptor) ID() method.ID {
|
||||
return "aes_gcm"
|
||||
}
|
||||
|
||||
func (f *descriptor) ConfigStruct() method.Config {
|
||||
return &Config{}
|
||||
}
|
||||
15
internal/encryption/method/id.go
Normal file
15
internal/encryption/method/id.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package method
|
||||
|
||||
// ID is a type alias to make passing the wrong ID into a method ID harder.
|
||||
type ID string
|
||||
|
||||
// Validate validates the key provider ID for correctness.
|
||||
func (i ID) Validate() error {
|
||||
// TODO implement format checking
|
||||
return nil
|
||||
}
|
||||
37
internal/encryption/method/method.go
Normal file
37
internal/encryption/method/method.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package method
|
||||
|
||||
type Config interface {
|
||||
// Build takes the configuration and builds an encryption method.
|
||||
Build() (Method, error)
|
||||
}
|
||||
|
||||
type Descriptor interface {
|
||||
// ID returns the unique identifier used when parsing HCL or JSON configs.
|
||||
ID() ID
|
||||
|
||||
// ConfigStruct creates a new configuration struct annotated with hcl tags. The Build() receiver on
|
||||
// this struct must be able to build a Method from the configuration.
|
||||
//
|
||||
// Common errors:
|
||||
// - Returning a struct without a pointer
|
||||
// - Returning a non-struct
|
||||
ConfigStruct() Config
|
||||
}
|
||||
|
||||
// Method is a low-level encryption method interface that is responsible for encrypting a binary blob of data. It should
|
||||
// not try to interpret what kind of data it is encrypting.
|
||||
type Method interface {
|
||||
// Encrypt encrypts the specified data with the set configuration. This method should treat any data passed as
|
||||
// opaque and should not try to interpret its contents. The interpretation is the job of the encryption.Encryption
|
||||
// interface.
|
||||
Encrypt(data []byte) ([]byte, error)
|
||||
// Decrypt decrypts the specified data with the set configuration. This method should treat any data passed as
|
||||
// opaque and should not try to interpret its contents. The interpretation is the job of the encryption.Encryption
|
||||
// interface.
|
||||
Decrypt(data []byte) ([]byte, error)
|
||||
}
|
||||
Reference in New Issue
Block a user