mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 07:33:32 -06:00
Documentation updates for 1.7.0-alpha1 (state encryption) (#1396)
Signed-off-by: Janos <86970079+janosdebugs@users.noreply.github.com>
This commit is contained in:
parent
12e7b4a440
commit
19a994ee7f
@ -307,35 +307,3 @@ type Config struct {
|
||||
Key string `hcl:"key"`
|
||||
}
|
||||
```
|
||||
|
||||
### OpenTofu integration
|
||||
|
||||
Currently, the OpenTofu code is in large parts procedural and has globally scoped state. Launching a second instance of OpenTofu is impossible. As it is a much-requested feature to embed OpenTofu as a library, this will need to change in the future. Therefore, the integration of the library should do its best to not introduce more global variables if possible.
|
||||
|
||||
The implementation may avail itself of either a singleton, or choose to pass the Encryption interface along several function calls. Both have benefits and drawbacks. However, singletons make parallelized testing very difficult and should therefore be avoided in tests as much as possible.
|
||||
|
||||
#### Singleton
|
||||
|
||||
A singleton takes an otherwise instance-based struct and stores it in a global variable. As such, it can carry information across otherwise procedural code.
|
||||
|
||||
**Pros:**
|
||||
|
||||
* Simpler access to the singleton from calling code
|
||||
* Less refactoring as the singleton is either available or not.
|
||||
|
||||
**Cons:**
|
||||
|
||||
* Harder to trace when/where the configuration is initialized.
|
||||
* Easy to introduce new code paths that access the singleton before it is available.
|
||||
|
||||
#### Passed Instance
|
||||
|
||||
This approach requires changing large parts of the OpenTofu code in order to pass along the `Encryption` object. This can cause additional bugs and seduce the inexperienced coder into introducing a super-object (also often referred to as context) to hold everything. Instead, a more granular approach would be desirable, but may not be possible given the state of the code.
|
||||
|
||||
**Pros:**
|
||||
|
||||
* Easy to trace where a given encryption instance comes from
|
||||
* Hard to introduce new code paths without passing the correct encryption interface.
|
||||
|
||||
Cons:
|
||||
* More in-depth refactoring is required / more of the codebase edited in this work
|
||||
|
14
internal/encryption/README.md
Normal file
14
internal/encryption/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# OpenTofu State and Plan encryption
|
||||
|
||||
> [!WARNING]
|
||||
> This file is not an end-user documentation, it is intended for developers. Please follow the user documentation on the OpenTofu website unless you want to work on the encryption code.
|
||||
|
||||
This folder contains the code for state and plan encryption. For a quick example on how to use this package, please take a look at the [example_test.go](example_test.go) file.
|
||||
|
||||
## Structure
|
||||
|
||||
The current folder contains the top level API. It requires a registry for holding the available key providers and encryption methods, which is located in the [registry](registry/) folder. The key providers are located in the [keyprovider](keyprovider/) folder, while the encryption methods are located in the [method](method) folder. You can also find the configuration struct and its related functions in the [config](config) folder.
|
||||
|
||||
## Further reading
|
||||
|
||||
For a detailed design document on state encryption, please read [this document](../../docs/state_encryption.md).
|
26
internal/encryption/method/README.md
Normal file
26
internal/encryption/method/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Encryption methods
|
||||
|
||||
> [!WARNING]
|
||||
> This file is not an end-user documentation, it is intended for developers. Please follow the user documentation on the OpenTofu website unless you want to work on the encryption code.
|
||||
|
||||
This folder contains the implementations for the encryption methods used in OpenTofu. Encryption methods determine how exactly data is encrypted, but they do not determine what exactly is encrypted.
|
||||
|
||||
## Implementing a method
|
||||
|
||||
When you implement a method, take a look at the [aesgcm](aesgcm) method as a template.
|
||||
|
||||
### Implementing the descriptor
|
||||
|
||||
The descriptor is very simple, you need to implement the [`Descriptor`](descriptor.go) interface in a type. (It does not have to be a struct.) However, make sure that the `ConfigStruct` always returns a struct with `hcl` tags on it. For more information on the `hcl` tags, see the [gohcl documentation](https://godocs.io/github.com/hashicorp/hcl/v2/gohcl).
|
||||
|
||||
### The config struct
|
||||
|
||||
Next, you need to create a config structure. This structure should hold all the fields you expect a user to fill out. **This must be a struct, and you must add `hcl` tags to each field you expect the user to fill out.**
|
||||
|
||||
If the config structure needs input from key providers, it should declare one HCL-tagged field with the type of [`keyprovider.Output`](../keyprovider/output.go) to receive the encryption and decryption key. Note, that the decryption key is not always available.
|
||||
|
||||
Additionally, you must implement the `Build` function described in the [`Config` interface](config.go). You can take a look at [aesgcm/config.go](static/config.go) for an example on implementing this.
|
||||
|
||||
### The method
|
||||
|
||||
The heart of your method is... well, your method. It has the `Encrypt()` and `Decrypt()` methods, which should perform the named tasks. If no decryption key is available, the method should refuse to decrypt data. The method should under no circumstances pass through unencrypted data if it fails to decrypt the data.
|
21
internal/encryption/method/config.go
Normal file
21
internal/encryption/method/config.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package method
|
||||
|
||||
// Config describes a configuration struct for setting up an encryption Method. You should always implement this
|
||||
// interface with a struct, and you should tag the fields with HCL tags so the encryption implementation can read
|
||||
// the .tf code into it. For example:
|
||||
//
|
||||
// type MyConfig struct {
|
||||
// Key string `hcl:"key"`
|
||||
// }
|
||||
//
|
||||
// func (m MyConfig) Build() (Method, error) { ... }
|
||||
type Config interface {
|
||||
// Build takes the configuration and builds an encryption method.
|
||||
// TODO this may be better changed to return hcl.Diagnostics so warnings can be issued?
|
||||
Build() (Method, error)
|
||||
}
|
20
internal/encryption/method/descriptor.go
Normal file
20
internal/encryption/method/descriptor.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package method
|
||||
|
||||
// Descriptor contains the details on an encryption method and produces a configuration structure with default values.
|
||||
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
|
||||
}
|
@ -5,34 +5,6 @@
|
||||
|
||||
package method
|
||||
|
||||
// Config describes a configuration struct for setting up an encryption Method. You should always implement this
|
||||
// interface with a struct, and you should tag the fields with HCL tags so the encryption implementation can read
|
||||
// the .tf code into it. For example:
|
||||
//
|
||||
// type MyConfig struct {
|
||||
// Key string `hcl:"key"`
|
||||
// }
|
||||
//
|
||||
// func (m MyConfig) Build() (Method, error) { ... }
|
||||
type Config interface {
|
||||
// Build takes the configuration and builds an encryption method.
|
||||
// TODO this may be better changed to return hcl.Diagnostics so warnings can be issued?
|
||||
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 {
|
||||
|
@ -142,7 +142,6 @@ Similar to the initial setup above, migrating to unencrypted state and plan file
|
||||
|
||||
You can also configure an encryption setup for projects using the `terraform_remote_state` data source. This can be the same encryption setup as your main configuration, but you can also define a separate set of keys and methods. The configuration syntax looks as follows:
|
||||
|
||||
|
||||
<CodeBlock language="hcl">{RemoteState}</CodeBlock>
|
||||
|
||||
For specific remote states, you can use the following syntax:
|
||||
|
@ -10,22 +10,17 @@ terraform {
|
||||
keys = key_provider.some_key_provider.some_name
|
||||
}
|
||||
|
||||
statefile {
|
||||
# Encryption/decryption for local state files
|
||||
state {
|
||||
# Encryption/decryption for state data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
planfile {
|
||||
# Encryption/decryption for local plan files
|
||||
plan {
|
||||
# Encryption/decryption for plan data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
backend {
|
||||
# Encryption/decryption method for backends
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
remote {
|
||||
remote_state_data_sources {
|
||||
# See below
|
||||
}
|
||||
}
|
||||
|
@ -10,22 +10,17 @@ terraform {
|
||||
keys = key_provider.some_key_provider.some_name
|
||||
}
|
||||
|
||||
statefile {
|
||||
# Encryption/decryption for local state files
|
||||
state {
|
||||
# Encryption/decryption for state data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
planfile {
|
||||
# Encryption/decryption for local plan files
|
||||
plan {
|
||||
# Encryption/decryption for plan data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
backend {
|
||||
# Encryption/decryption method for backends
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
remote {
|
||||
remote_state_data_sources {
|
||||
# See below
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,17 @@ terraform {
|
||||
keys = key_provider.some_key_provider.some_name
|
||||
}
|
||||
|
||||
statefile {
|
||||
# Encryption/decryption for local state files
|
||||
state {
|
||||
# Encryption/decryption for state data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
planfile {
|
||||
# Encryption/decryption for local plan files
|
||||
plan {
|
||||
# Encryption/decryption for plan data
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
backend {
|
||||
# Encryption/decryption method for backends
|
||||
method = method.some_method.some_method_name
|
||||
}
|
||||
|
||||
remote {
|
||||
remote_state_data_sources {
|
||||
# See below
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,10 @@
|
||||
terraform {
|
||||
encryption {
|
||||
statefile {
|
||||
enforce = true
|
||||
state {
|
||||
enforced = true
|
||||
}
|
||||
planfile {
|
||||
enforce = true
|
||||
}
|
||||
backend {
|
||||
enforce = true
|
||||
plan {
|
||||
enforced = true
|
||||
}
|
||||
}
|
||||
}
|
@ -2,21 +2,14 @@ terraform {
|
||||
encryption {
|
||||
# Methods and key providers here.
|
||||
|
||||
statefile {
|
||||
state {
|
||||
method = method.some_method.new_method
|
||||
fallback {
|
||||
method = method.some_method.old_method
|
||||
}
|
||||
}
|
||||
|
||||
planfile {
|
||||
method = method.some_method.new_method
|
||||
fallback {
|
||||
method = method.some_method.old_method
|
||||
}
|
||||
}
|
||||
|
||||
backend {
|
||||
plan {
|
||||
method = method.some_method.new_method
|
||||
fallback {
|
||||
method = method.some_method.old_method
|
||||
|
@ -2,7 +2,7 @@ terraform {
|
||||
encryption {
|
||||
# Methods and key providers here.
|
||||
|
||||
statefile {
|
||||
state {
|
||||
method = method.some_method.new_method
|
||||
fallback {
|
||||
# The empty fallback block allows reading unencrypted state files.
|
||||
|
@ -2,7 +2,7 @@ terraform {
|
||||
encryption {
|
||||
# Methods and key providers here.
|
||||
|
||||
statefile {
|
||||
state {
|
||||
# The empty block allows writing unencrypted state files
|
||||
# unless the enforced setting is set to true.
|
||||
fallback {
|
||||
|
@ -2,11 +2,11 @@ terraform {
|
||||
encryption {
|
||||
# Key provider and method configuration here
|
||||
|
||||
remote {
|
||||
remote_state_data_sources {
|
||||
default {
|
||||
method = method.my_method.my_name
|
||||
}
|
||||
terraform_remote_state "my_state" {
|
||||
remote_state_data_source "my_state" {
|
||||
method = method.my_method.my_other_name
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user