Initial wiring of encryption through the command package (#1316)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-03-07 08:55:57 -05:00 committed by GitHub
parent 99b43c98fc
commit 5ab6167bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 808 additions and 232 deletions

View File

@ -238,6 +238,9 @@ type Operation struct {
// Type is the operation to perform.
Type OperationType
// Encryption is used by enhanced backends for planning and tofu.Context initialization
Encryption encryption.Encryption
// PlanId is an opaque value that backends can use to execute a specific
// plan for an apply operation.
//

View File

@ -106,9 +106,6 @@ func New(enc encryption.StateEncryption) *Local {
// NewWithBackend returns a new local backend initialized with a
// dedicated backend for non-enhanced behavior.
func NewWithBackend(backend backend.Backend, enc encryption.StateEncryption) *Local {
if backend == nil && enc == nil {
panic("either backend or encryption required for backend.Local initialization")
}
return &Local{
Backend: backend,
encryption: enc,

View File

@ -379,6 +379,7 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun
return &backend.Operation{
Type: backend.OperationTypeApply,
Encryption: encryption.Disabled(),
ConfigDir: configDir,
ConfigLoader: configLoader,
StateLocker: clistate.NewNoopLocker(),

View File

@ -78,6 +78,7 @@ func (b *Local) localRun(op *backend.Operation) (*backend.LocalRun, *configload.
}
coreOpts.UIInput = op.UIIn
coreOpts.Hooks = op.Hooks
coreOpts.Encryption = op.Encryption
var ctxDiags tfdiags.Diagnostics
var configSnap *configload.Snapshot

View File

@ -12,7 +12,6 @@ import (
"log"
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/genconfig"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/internal/plans"
@ -173,7 +172,7 @@ func (b *Local) opPlan(
StateFile: plannedStateFile,
Plan: plan,
DependencyLocks: op.DependencyLocks,
}, encryption.PlanEncryptionTODO())
}, op.Encryption.PlanFile())
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,

View File

@ -732,6 +732,7 @@ func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func
return &backend.Operation{
Type: backend.OperationTypePlan,
Encryption: encryption.Disabled(),
ConfigDir: configDir,
ConfigLoader: configLoader,
StateLocker: clistate.NewNoopLocker(),

View File

@ -72,6 +72,7 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
// Copy set options from the operation
opts.UIInput = op.UIIn
opts.Encryption = op.Encryption
// Load the latest state. If we enter contextFromPlanFile below then the
// state snapshot in the plan file must match this, or else it'll return

View File

@ -73,7 +73,7 @@ func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
// Getting the backend implicitly validates the configuration for it,
// but we can only do that if it's all known already.
if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() {
_, _, moreDiags := getBackend(cfg)
_, _, moreDiags := getBackend(cfg, nil) // Don't need the encryption for validation here
diags = diags.Append(moreDiags)
} else {
// Otherwise we'll just type-check the config object itself.
@ -103,10 +103,10 @@ func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
return diags
}
func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
func dataSourceRemoteStateRead(d cty.Value, enc encryption.StateEncryption) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
b, cfg, moreDiags := getBackend(d)
b, cfg, moreDiags := getBackend(d, enc)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return cty.NilVal, diags
@ -184,7 +184,7 @@ func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
return cty.ObjectVal(newState), diags
}
func getBackend(cfg cty.Value) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
func getBackend(cfg cty.Value, enc encryption.StateEncryption) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
backendType := cfg.GetAttr("backend").AsString()
@ -212,7 +212,7 @@ func getBackend(cfg cty.Value) (backend.Backend, cty.Value, tfdiags.Diagnostics)
))
return nil, cty.NilVal, diags
}
b := f(encryption.StateEncryptionTODO())
b := f(enc)
config := cfg.GetAttr("config")
if config.IsNull() {

View File

@ -296,7 +296,7 @@ func TestState_basic(t *testing.T) {
var got cty.Value
if !diags.HasErrors() && config.IsWhollyKnown() {
var moreDiags tfdiags.Diagnostics
got, moreDiags = dataSourceRemoteStateRead(config)
got, moreDiags = dataSourceRemoteStateRead(config, encryption.StateEncryptionDisabled())
diags = diags.Append(moreDiags)
}

View File

@ -8,7 +8,10 @@ package tf
import (
"fmt"
"log"
"strings"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/providers"
)
@ -70,6 +73,10 @@ func (p *Provider) ConfigureProvider(providers.ConfigureProviderRequest) provide
// ReadDataSource returns the data source's current state.
func (p *Provider) ReadDataSource(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
panic("Should not be called directly, special case for terraform_remote_state")
}
func (p *Provider) ReadDataSourceEncrypted(req providers.ReadDataSourceRequest, path addrs.AbsResourceInstance, enc encryption.Encryption) providers.ReadDataSourceResponse {
// call function
var res providers.ReadDataSourceResponse
@ -79,7 +86,23 @@ func (p *Provider) ReadDataSource(req providers.ReadDataSourceRequest) providers
return res
}
newState, diags := dataSourceRemoteStateRead(req.Config)
// These string manipulations are kind of funky
key := path.String()
// data.terraform_remote_state.foo[4] -> foo[4]
// module.submod[1].data.terraform_remote_state.bar -> module.submod[1].bar
key = strings.Replace(key, "data.terraform_remote_state.", "", 1)
// module.submod[1].bar -> submod[1].bar
key = strings.TrimPrefix(key, "module.")
log.Printf("[DEBUG] accessing remote state at %s", key)
newState, diags := dataSourceRemoteStateRead(req.Config, enc.RemoteState(key))
if diags.HasErrors() {
diags = diags.Append(fmt.Errorf("%s: Unable to read remote state", path.String()))
}
res.State = newState
res.Diagnostics = diags

View File

@ -72,6 +72,7 @@ func (b *Cloud) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Ful
// Copy set options from the operation
opts.UIInput = op.UIIn
opts.Encryption = op.Encryption
// Load the latest state. If we enter contextFromPlanFile below then the
// state snapshot in the plan file must match this, or else it'll return

View File

@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/plans/planfile"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@ -66,8 +67,16 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
view.Diagnostics(diags)
return 1
}
// Attempt to load the plan file, if specified
planFile, diags := c.LoadPlanFile(args.PlanPath)
planFile, diags := c.LoadPlanFile(args.PlanPath, enc)
if diags.HasErrors() {
view.Diagnostics(diags)
return 1
@ -99,7 +108,7 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
// Prepare the backend, passing the plan file if present, and the
// backend-specific arguments
be, beDiags := c.PrepareBackend(planFile, args.State, args.ViewType)
be, beDiags := c.PrepareBackend(planFile, args.State, args.ViewType, enc.Backend())
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -107,7 +116,7 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, planFile, args.Operation, args.AutoApprove)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, planFile, args.Operation, args.AutoApprove, enc)
diags = diags.Append(opDiags)
// Collect variable value and add them to the operation request
@ -152,14 +161,14 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
return 0
}
func (c *ApplyCommand) LoadPlanFile(path string) (*planfile.WrappedPlanFile, tfdiags.Diagnostics) {
func (c *ApplyCommand) LoadPlanFile(path string, enc encryption.Encryption) (*planfile.WrappedPlanFile, tfdiags.Diagnostics) {
var planFile *planfile.WrappedPlanFile
var diags tfdiags.Diagnostics
// Try to load plan if path is specified
if path != "" {
var err error
planFile, err = c.PlanFile(path)
planFile, err = c.PlanFile(path, enc.PlanFile())
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -196,7 +205,7 @@ func (c *ApplyCommand) LoadPlanFile(path string) (*planfile.WrappedPlanFile, tfd
return planFile, diags
}
func (c *ApplyCommand) PrepareBackend(planFile *planfile.WrappedPlanFile, args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *ApplyCommand) PrepareBackend(planFile *planfile.WrappedPlanFile, args *arguments.State, viewType arguments.ViewType, enc encryption.StateEncryption) (backend.Enhanced, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// FIXME: we need to apply the state arguments to the meta object here
@ -227,7 +236,7 @@ func (c *ApplyCommand) PrepareBackend(planFile *planfile.WrappedPlanFile, args *
))
return nil, diags
}
be, beDiags = c.BackendForLocalPlan(plan.Backend)
be, beDiags = c.BackendForLocalPlan(plan.Backend, enc)
} else {
// Both new plans and saved cloud plans load their backend from config.
backendConfig, configDiags := c.loadBackendConfig(".")
@ -239,7 +248,7 @@ func (c *ApplyCommand) PrepareBackend(planFile *planfile.WrappedPlanFile, args *
be, beDiags = c.Backend(&BackendOpts{
Config: backendConfig,
ViewType: viewType,
})
}, enc)
}
diags = diags.Append(beDiags)
@ -256,6 +265,7 @@ func (c *ApplyCommand) OperationRequest(
planFile *planfile.WrappedPlanFile,
args *arguments.Operation,
autoApprove bool,
enc encryption.Encryption,
) (*backend.Operation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
@ -265,7 +275,7 @@ func (c *ApplyCommand) OperationRequest(
diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
// Build the operation
opReq := c.Operation(be, viewType)
opReq := c.Operation(be, viewType, enc)
opReq.AutoApprove = autoApprove
opReq.ConfigDir = "."
opReq.PlanMode = args.PlanMode

View File

@ -57,7 +57,7 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor {
b, diags := m.Backend(&BackendOpts{
Config: backendConfig,
})
}, nil) // Don't need state encryption here.
if diags.HasErrors() {
return nil
}

View File

@ -52,6 +52,14 @@ func (c *ConsoleCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
@ -62,7 +70,7 @@ func (c *ConsoleCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
@ -81,7 +89,7 @@ func (c *ConsoleCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Build the operation
opReq := c.Operation(b, arguments.ViewHuman)
opReq := c.Operation(b, arguments.ViewHuman, enc)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.AllowUnsetVariables = true // we'll just evaluate them as unknown

View File

@ -25,6 +25,8 @@ type GraphCommand struct {
}
func (c *GraphCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
var drawCycles bool
var graphTypeStr string
var moduleDepth int
@ -56,18 +58,24 @@ func (c *GraphCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Try to load plan if path is specified
var planFile *planfile.WrappedPlanFile
if planPath != "" {
planFile, err = c.PlanFile(planPath)
planFile, err = c.PlanFile(planPath, enc.PlanFile())
if err != nil {
c.Ui.Error(err.Error())
return 1
}
}
var diags tfdiags.Diagnostics
backendConfig, backendDiags := c.loadBackendConfig(configPath)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
@ -78,7 +86,7 @@ func (c *GraphCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
@ -97,7 +105,7 @@ func (c *GraphCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Build the operation
opReq := c.Operation(b, arguments.ViewHuman)
opReq := c.Operation(b, arguments.ViewHuman, enc)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.PlanFile = planFile

View File

@ -110,6 +110,14 @@ func (c *ImportCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Verify that the given address points to something that exists in config.
// This is to reduce the risk that a typo in the resource address will
// import something that OpenTofu will want to immediately destroy on
@ -167,7 +175,7 @@ func (c *ImportCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: config.Module.Backend,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
@ -186,7 +194,7 @@ func (c *ImportCommand) Run(args []string) int {
}
// Build the operation
opReq := c.Operation(b, arguments.ViewHuman)
opReq := c.Operation(b, arguments.ViewHuman, enc)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {

View File

@ -28,6 +28,7 @@ import (
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/getproviders"
"github.com/opentofu/opentofu/internal/providercache"
"github.com/opentofu/opentofu/internal/states"
@ -190,6 +191,14 @@ func (c *InitCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromModule(rootModEarly)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
var back backend.Backend
// There may be config errors or backend init errors but these will be shown later _after_
@ -199,12 +208,12 @@ func (c *InitCommand) Run(args []string) int {
switch {
case flagCloud && rootModEarly.CloudConfig != nil:
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, flagConfigExtra)
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, flagConfigExtra, enc)
case flagBackend:
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, flagConfigExtra)
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, flagConfigExtra, enc)
default:
// load the previously-stored backend config
back, backDiags = c.Meta.backendFromState(ctx)
back, backDiags = c.Meta.backendFromState(ctx, enc.Backend())
}
if backendOutput {
header = true
@ -413,7 +422,7 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
return true, installAbort, diags
}
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig rawFlags, enc encryption.Encryption) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize cloud backend")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
defer span.End()
@ -436,12 +445,12 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra
Init: true,
}
back, backDiags := c.Backend(opts)
back, backDiags := c.Backend(opts, enc.Backend())
diags = diags.Append(backDiags)
return back, true, diags
}
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig rawFlags, enc encryption.Encryption) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize backend")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
defer span.End()
@ -519,7 +528,7 @@ the backend configuration is present and valid.
Init: true,
}
back, backDiags := c.Backend(opts)
back, backDiags := c.Backend(opts, enc.Backend())
diags = diags.Append(backDiags)
return back, true, diags
}

View File

@ -92,7 +92,7 @@ type BackendWithRemoteTerraformVersion interface {
// A side-effect of this method is the population of m.backendState, recording
// the final resolved backend configuration after dealing with overrides from
// the "tofu init" command line, etc.
func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics) {
func (m *Meta) Backend(opts *BackendOpts, enc encryption.StateEncryption) (backend.Enhanced, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// If no opts are set, then initialize
@ -105,7 +105,7 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
var b backend.Backend
if !opts.ForceLocal {
var backendDiags tfdiags.Diagnostics
b, backendDiags = m.backendFromConfig(opts)
b, backendDiags = m.backendFromConfig(opts, enc)
diags = diags.Append(backendDiags)
if diags.HasErrors() {
@ -182,7 +182,7 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
}
// Build the local backend
local := backendLocal.NewWithBackend(b, encryption.StateEncryptionTODO())
local := backendLocal.NewWithBackend(b, enc)
if err := local.CLIInit(cliOpts); err != nil {
// Local backend isn't allowed to fail. It would be a bug.
panic(err)
@ -305,7 +305,7 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
// The current workspace name is also stored as part of the plan, and so this
// method will check that it matches the currently-selected workspace name
// and produce error diagnostics if not.
func (m *Meta) BackendForLocalPlan(settings plans.Backend) (backend.Enhanced, tfdiags.Diagnostics) {
func (m *Meta) BackendForLocalPlan(settings plans.Backend, enc encryption.StateEncryption) (backend.Enhanced, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
f := backendInit.Backend(settings.Type)
@ -313,7 +313,7 @@ func (m *Meta) BackendForLocalPlan(settings plans.Backend) (backend.Enhanced, tf
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), settings.Type))
return nil, diags
}
b := f(encryption.StateEncryptionTODO())
b := f(enc)
log.Printf("[TRACE] Meta.BackendForLocalPlan: instantiated backend of type %T", b)
schema := b.ConfigSchema()
@ -372,7 +372,7 @@ func (m *Meta) BackendForLocalPlan(settings plans.Backend) (backend.Enhanced, tf
return nil, diags
}
cliOpts.Validation = false // don't validate here in case config contains file(...) calls where the file doesn't exist
local := backendLocal.NewWithBackend(b, encryption.StateEncryptionTODO())
local := backendLocal.NewWithBackend(b, enc)
if err := local.CLIInit(cliOpts); err != nil {
// Local backend should never fail, so this is always a bug.
panic(err)
@ -406,7 +406,7 @@ func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) {
// This prepares the operation. After calling this, the caller is expected
// to modify fields of the operation such as Sequence to specify what will
// be called.
func (m *Meta) Operation(b backend.Backend, vt arguments.ViewType) *backend.Operation {
func (m *Meta) Operation(b backend.Backend, vt arguments.ViewType, enc encryption.Encryption) *backend.Operation {
schema := b.ConfigSchema()
workspace, err := m.Workspace()
if err != nil {
@ -441,6 +441,7 @@ func (m *Meta) Operation(b backend.Backend, vt arguments.ViewType) *backend.Oper
}
return &backend.Operation{
Encryption: enc,
PlanOutBackend: planOutBackend,
Targets: m.targets,
UIIn: m.UIInput(),
@ -494,7 +495,7 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
})
return nil, 0, diags
}
b := bf(encryption.StateEncryptionTODO())
b := bf(nil) // Just using this for config/schema, don't need encryption here
configSchema := b.ConfigSchema()
configBody := c.Config
@ -527,7 +528,7 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
//
// This function may query the user for input unless input is disabled, in
// which case this function will error.
func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) backendFromConfig(opts *BackendOpts, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
// Get the local backend configuration.
c, cHash, diags := m.backendConfig(opts)
if diags.HasErrors() {
@ -626,7 +627,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
return nil, diags
}
return m.backend_c_r_S(c, cHash, sMgr, true, opts)
return m.backend_c_r_S(c, cHash, sMgr, true, opts, enc)
// Configuring a backend for the first time or -reconfigure flag was used
case c != nil && s.Backend.Empty():
@ -649,7 +650,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
}
return nil, diags
}
return m.backend_C_r_s(c, cHash, sMgr, opts)
return m.backend_C_r_s(c, cHash, sMgr, opts, enc)
// Potentially changing a backend configuration
case c != nil && !s.Backend.Empty():
// We are not going to migrate if...
@ -659,7 +660,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
// AND we're not providing any overrides. An override can mean a change overriding an unchanged backend block (indicated by the hash value).
if (uint64(cHash) == s.Backend.Hash) && (!opts.Init || opts.ConfigOverride == nil) {
log.Printf("[TRACE] Meta.Backend: using already-initialized, unchanged %q backend configuration", c.Type)
savedBackend, diags := m.savedBackend(sMgr)
savedBackend, diags := m.savedBackend(sMgr, enc)
// Verify that selected workspace exist. Otherwise prompt user to create one
if opts.Init && savedBackend != nil {
if err := m.selectWorkspace(savedBackend); err != nil {
@ -676,7 +677,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
// don't need to migrate, we update the backend cache hash value.
if !m.backendConfigNeedsMigration(c, s.Backend) {
log.Printf("[TRACE] Meta.Backend: using already-initialized %q backend configuration", c.Type)
savedBackend, moreDiags := m.savedBackend(sMgr)
savedBackend, moreDiags := m.savedBackend(sMgr, enc)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
@ -716,7 +717,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
}
log.Printf("[WARN] backend config has changed since last init")
return m.backend_C_r_S_changed(c, cHash, sMgr, true, opts)
return m.backend_C_r_S_changed(c, cHash, sMgr, true, opts, enc)
default:
diags = diags.Append(fmt.Errorf(
@ -777,7 +778,7 @@ func (m *Meta) determineInitReason(previousBackendType string, currentBackendTyp
// from the backend state. This should be used only when a user runs
// `tofu init -backend=false`. This function returns a local backend if
// there is no backend state or no backend configured.
func (m *Meta) backendFromState(ctx context.Context) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) backendFromState(ctx context.Context, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Get the path to where we store a local cache of backend configuration
// if we're using a remote backend. This may not yet exist which means
@ -792,25 +793,25 @@ func (m *Meta) backendFromState(ctx context.Context) (backend.Backend, tfdiags.D
if s == nil {
// no state, so return a local backend
log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
return backendLocal.New(encryption.StateEncryptionTODO()), diags
return backendLocal.New(enc), diags
}
if s.Backend == nil {
// s.Backend is nil, so return a local backend
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)")
return backendLocal.New(encryption.StateEncryptionTODO()), diags
return backendLocal.New(enc), diags
}
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
//backend init function
if s.Backend.Type == "" {
return backendLocal.New(encryption.StateEncryptionTODO()), diags
return backendLocal.New(enc), diags
}
f := backendInit.Backend(s.Backend.Type)
if f == nil {
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type))
return nil, diags
}
b := f(encryption.StateEncryptionTODO())
b := f(enc)
// The configuration saved in the working directory state file is used
// in this case, since it will contain any additional values that
@ -872,7 +873,7 @@ func (m *Meta) backendFromState(ctx context.Context) (backend.Backend, tfdiags.D
// Unconfiguring a backend (moving from backend => local).
func (m *Meta) backend_c_r_S(
c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
@ -901,14 +902,14 @@ func (m *Meta) backend_c_r_S(
}
// Grab a purely local backend to get the local state if it exists
localB, moreDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true})
localB, moreDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}, enc)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
// Initialize the configured backend
b, moreDiags := m.savedBackend(sMgr)
b, moreDiags := m.savedBackend(sMgr, enc)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
@ -949,7 +950,7 @@ func (m *Meta) backend_c_r_S(
}
// Configuring a backend for the first time.
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON
@ -960,7 +961,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
}
// Grab a purely local backend to get the local state if it exists
localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true})
localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}, enc)
if localBDiags.HasErrors() {
diags = diags.Append(localBDiags)
return nil, diags
@ -1000,7 +1001,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
}
// Get the backend
b, configVal, moreDiags := m.backendInitFromConfig(c)
b, configVal, moreDiags := m.backendInitFromConfig(c, enc)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
@ -1118,7 +1119,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
}
// Changing a previously saved backend.
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON
@ -1161,7 +1162,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
}
// Get the backend
b, configVal, moreDiags := m.backendInitFromConfig(c)
b, configVal, moreDiags := m.backendInitFromConfig(c, enc)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
@ -1175,7 +1176,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
// state lives.
if cloudMode != cloud.ConfigChangeInPlace {
// Grab the existing backend
oldB, oldBDiags := m.savedBackend(sMgr)
oldB, oldBDiags := m.savedBackend(sMgr, enc)
diags = diags.Append(oldBDiags)
if oldBDiags.HasErrors() {
return nil, diags
@ -1256,7 +1257,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
// TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the
// function used by the migration APIs within this file. The other handles 'init -backend=false',
// specifically.
func (m *Meta) savedBackend(sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) {
func (m *Meta) savedBackend(sMgr *clistate.LocalState, enc encryption.StateEncryption) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
s := sMgr.State()
@ -1267,7 +1268,7 @@ func (m *Meta) savedBackend(sMgr *clistate.LocalState) (backend.Backend, tfdiags
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type))
return nil, diags
}
b := f(encryption.StateEncryptionTODO())
b := f(enc)
// The configuration saved in the working directory state file is used
// in this case, since it will contain any additional values that
@ -1355,7 +1356,7 @@ func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.Backend
log.Printf("[TRACE] backendConfigNeedsMigration: no backend of type %q, which migration codepath must handle", c.Type)
return true // let the migration codepath deal with the missing backend
}
b := f(encryption.StateEncryptionTODO())
b := f(nil) // We don't need encryption here as it's only used for config/schema
schema := b.ConfigSchema()
decSpec := schema.NoneRequired().DecoderSpec()
@ -1383,7 +1384,7 @@ func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.Backend
return true
}
func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
func (m *Meta) backendInitFromConfig(c *configs.Backend, enc encryption.StateEncryption) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Get the backend
@ -1392,7 +1393,7 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type))
return nil, cty.NilVal, diags
}
b := f(encryption.StateEncryptionTODO())
b := f(enc)
schema := b.ConfigSchema()
decSpec := schema.NoneRequired().DecoderSpec()

View File

@ -41,7 +41,7 @@ func TestMetaBackend_emptyDir(t *testing.T) {
// Get the backend
m := testMetaBackend(t, nil)
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -110,7 +110,7 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) {
// Get the backend
m := testMetaBackend(t, nil)
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -181,7 +181,7 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) {
m.statePath = statePath
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -233,7 +233,7 @@ func TestMetaBackend_configureInterpolation(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
_, err := m.Backend(&BackendOpts{Init: true})
_, err := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if err == nil {
t.Fatal("should error")
}
@ -249,7 +249,7 @@ func TestMetaBackend_configureNew(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -320,7 +320,7 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
m.migrateState = false
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -395,7 +395,7 @@ func TestMetaBackend_configureNewWithoutCopy(t *testing.T) {
m.input = false
// init the backend
_, diags := m.Backend(&BackendOpts{Init: true})
_, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -443,7 +443,7 @@ func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -486,7 +486,7 @@ func TestMetaBackend_configureNewWithStateExisting(t *testing.T) {
m.forceInitCopy = true
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -558,7 +558,7 @@ func TestMetaBackend_configureNewWithStateExistingNoMigrate(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -623,7 +623,7 @@ func TestMetaBackend_configuredUnchanged(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -669,7 +669,7 @@ func TestMetaBackend_configuredChange(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -755,7 +755,7 @@ func TestMetaBackend_reconfigureChange(t *testing.T) {
m.reconfigure = true
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -802,7 +802,7 @@ func TestMetaBackend_initSelectedWorkspaceDoesNotExist(t *testing.T) {
})()
// Get the backend
_, diags := m.Backend(&BackendOpts{Init: true})
_, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -844,7 +844,7 @@ func TestMetaBackend_initSelectedWorkspaceDoesNotExistAutoSelect(t *testing.T) {
}
// Get the backend
_, diags := m.Backend(&BackendOpts{Init: true})
_, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -873,7 +873,7 @@ func TestMetaBackend_initSelectedWorkspaceDoesNotExistInputFalse(t *testing.T) {
m.input = false
// Get the backend
_, diags := m.Backend(&BackendOpts{Init: true})
_, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
// Should fail immediately
if got, want := diags.ErrWithWarnings().Error(), `Currently selected workspace "bar" does not exist`; !strings.Contains(got, want) {
@ -895,7 +895,7 @@ func TestMetaBackend_configuredChangeCopy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -948,7 +948,7 @@ func TestMetaBackend_configuredChangeCopy_singleState(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1002,7 +1002,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleDefault(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1056,7 +1056,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1130,7 +1130,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T)
}
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1185,7 +1185,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1283,7 +1283,7 @@ func TestMetaBackend_configuredChangeCopy_multiToNoDefaultWithDefault(t *testing
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1357,7 +1357,7 @@ func TestMetaBackend_configuredChangeCopy_multiToNoDefaultWithoutDefault(t *test
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1423,7 +1423,7 @@ func TestMetaBackend_configuredUnset(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1485,7 +1485,7 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.Backend(&BackendOpts{Init: true})
b, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1553,7 +1553,7 @@ func TestMetaBackend_planLocal(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.BackendForLocalPlan(backendConfig)
b, diags := m.BackendForLocalPlan(backendConfig, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1654,7 +1654,7 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) {
m.stateOutPath = statePath
// Get the backend
b, diags := m.BackendForLocalPlan(plannedBackend)
b, diags := m.BackendForLocalPlan(plannedBackend, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1743,7 +1743,7 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
m := testMetaBackend(t, nil)
// Get the backend
b, diags := m.BackendForLocalPlan(backendConfig)
b, diags := m.BackendForLocalPlan(backendConfig, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1828,7 +1828,7 @@ func TestMetaBackend_configureWithExtra(t *testing.T) {
_, diags := m.Backend(&BackendOpts{
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1844,7 +1844,7 @@ func TestMetaBackend_configureWithExtra(t *testing.T) {
_, err = m.Backend(&BackendOpts{
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
}, encryption.StateEncryptionDisabled())
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@ -1871,7 +1871,7 @@ func TestMetaBackend_localDoesNotDeleteLocal(t *testing.T) {
m := testMetaBackend(t, nil)
m.forceInitCopy = true
// init the backend
_, diags := m.Backend(&BackendOpts{Init: true})
_, diags := m.Backend(&BackendOpts{Init: true}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1894,7 +1894,7 @@ func TestMetaBackend_configToExtra(t *testing.T) {
m := testMetaBackend(t, nil)
_, err := m.Backend(&BackendOpts{
Init: true,
})
}, encryption.StateEncryptionDisabled())
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@ -1916,7 +1916,7 @@ func TestMetaBackend_configToExtra(t *testing.T) {
_, diags := m.Backend(&BackendOpts{
ConfigOverride: configs.SynthBody("synth", extras),
Init: true,
})
}, encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}
@ -1943,7 +1943,7 @@ func TestBackendFromState(t *testing.T) {
// them to match just for this test.
wd.OverrideDataDir(".")
stateBackend, diags := m.backendFromState(context.Background())
stateBackend, diags := m.backendFromState(context.Background(), encryption.StateEncryptionDisabled())
if diags.HasErrors() {
t.Fatal(diags.Err())
}

View File

@ -0,0 +1,65 @@
package command
import (
"fmt"
"os"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
"github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry"
"github.com/opentofu/opentofu/internal/tfdiags"
)
const encryptionConfigEnvName = "TF_ENCRYPTION"
func (m *Meta) Encryption() (encryption.Encryption, tfdiags.Diagnostics) {
path, err := os.Getwd()
if err != nil {
return nil, tfdiags.Diagnostics{}.Append(fmt.Errorf("Error getting pwd: %w", err))
}
return m.EncryptionFromPath(path)
}
func (m *Meta) EncryptionFromPath(path string) (encryption.Encryption, tfdiags.Diagnostics) {
// This is not ideal, but given how fragmented the command package is, loading the root module here is our best option
// See other meta commands like version check which do that same.
module, diags := m.loadSingleModule(path)
if diags.HasErrors() {
return nil, diags
}
enc, encDiags := m.EncryptionFromModule(module)
diags = diags.Append(encDiags)
return enc, diags
}
func (m *Meta) EncryptionFromModule(module *configs.Module) (encryption.Encryption, tfdiags.Diagnostics) {
reg := lockingencryptionregistry.New()
if err := reg.RegisterKeyProvider(static.New()); err != nil {
panic(err)
}
if err := reg.RegisterMethod(aesgcm.New()); err != nil {
panic(err)
}
cfg := module.Encryption
var diags tfdiags.Diagnostics
env := os.Getenv(encryptionConfigEnvName)
if len(env) != 0 {
envCfg, envDiags := config.LoadConfigFromString(encryptionConfigEnvName, env)
diags = diags.Append(envDiags)
if envDiags.HasErrors() {
return nil, diags
}
cfg = cfg.Merge(envCfg)
}
enc, encDiags := encryption.New(reg, cfg)
diags = diags.Append(encDiags)
return enc, diags
}

View File

@ -38,7 +38,7 @@ func (m *Meta) Input() bool {
//
// Error will be non-nil if path refers to something which looks like a plan
// file and loading the file fails.
func (m *Meta) PlanFile(path string) (*planfile.WrappedPlanFile, error) {
func (m *Meta) PlanFile(path string, enc encryption.PlanEncryption) (*planfile.WrappedPlanFile, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
@ -49,5 +49,5 @@ func (m *Meta) PlanFile(path string) (*planfile.WrappedPlanFile, error) {
return nil, nil
}
return planfile.OpenWrapped(path, encryption.PlanEncryptionTODO())
return planfile.OpenWrapped(path, enc)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@ -36,8 +37,16 @@ func (c *OutputCommand) Run(rawArgs []string) int {
view := views.NewOutput(args.ViewType, c.View)
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.View.Diagnostics(diags)
return 1
}
// Fetch data from state
outputs, diags := c.Outputs(args.StatePath)
outputs, diags := c.Outputs(args.StatePath, enc)
if diags.HasErrors() {
view.Diagnostics(diags)
return 1
@ -56,7 +65,7 @@ func (c *OutputCommand) Run(rawArgs []string) int {
return 0
}
func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValue, tfdiags.Diagnostics) {
func (c *OutputCommand) Outputs(statePath string, enc encryption.Encryption) (map[string]*states.OutputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Allow state path override
@ -65,7 +74,7 @@ func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValu
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if diags.HasErrors() {
return nil, diags

View File

@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@ -68,8 +69,16 @@ func (c *PlanCommand) Run(rawArgs []string) int {
diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
view.Diagnostics(diags)
return 1
}
// Prepare the backend with the backend-specific arguments
be, beDiags := c.PrepareBackend(args.State, args.ViewType)
be, beDiags := c.PrepareBackend(args.State, args.ViewType, enc)
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -77,7 +86,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, args.OutPath, args.GenerateConfigPath)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, args.OutPath, args.GenerateConfigPath, enc)
diags = diags.Append(opDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -115,7 +124,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
return op.Result.ExitStatus()
}
func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType, enc encryption.Encryption) (backend.Enhanced, tfdiags.Diagnostics) {
// FIXME: we need to apply the state arguments to the meta object here
// because they are later used when initializing the backend. Carving a
// path to pass these arguments to the functions that need them is
@ -131,7 +140,7 @@ func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.V
be, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
ViewType: viewType,
})
}, enc.Backend())
diags = diags.Append(beDiags)
if beDiags.HasErrors() {
return nil, diags
@ -147,11 +156,12 @@ func (c *PlanCommand) OperationRequest(
args *arguments.Operation,
planOutPath string,
generateConfigOut string,
enc encryption.Encryption,
) (*backend.Operation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Build the operation
opReq := c.Operation(be, viewType)
opReq := c.Operation(be, viewType, enc)
opReq.ConfigDir = "."
opReq.PlanMode = args.PlanMode
opReq.Hooks = view.Hooks()

View File

@ -82,10 +82,18 @@ func (c *ProvidersCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: config.Module.Backend,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -58,7 +58,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, nil) // Encryption not needed here
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
@ -84,7 +84,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
}
// Build the operation
opReq := c.Operation(b, arguments.ViewJSON)
opReq := c.Operation(b, arguments.ViewJSON, nil) // Encryption not needed here
opReq.ConfigDir = cwd
opReq.ConfigLoader, err = c.initConfigLoader()
opReq.AllowUnsetVariables = true

View File

@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@ -68,8 +69,16 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
// object state for now.
c.Meta.parallelism = args.Operation.Parallelism
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Prepare the backend with the backend-specific arguments
be, beDiags := c.PrepareBackend(args.State, args.ViewType)
be, beDiags := c.PrepareBackend(args.State, args.ViewType, enc)
diags = diags.Append(beDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -77,7 +86,7 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
}
// Build the operation request
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation)
opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, enc)
diags = diags.Append(opDiags)
if diags.HasErrors() {
view.Diagnostics(diags)
@ -112,7 +121,7 @@ func (c *RefreshCommand) Run(rawArgs []string) int {
return op.Result.ExitStatus()
}
func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) {
func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType, enc encryption.Encryption) (backend.Enhanced, tfdiags.Diagnostics) {
// FIXME: we need to apply the state arguments to the meta object here
// because they are later used when initializing the backend. Carving a
// path to pass these arguments to the functions that need them is
@ -128,7 +137,7 @@ func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType argument
be, beDiags := c.Backend(&BackendOpts{
Config: backendConfig,
ViewType: viewType,
})
}, enc.Backend())
diags = diags.Append(beDiags)
if beDiags.HasErrors() {
return nil, diags
@ -137,12 +146,12 @@ func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType argument
return be, diags
}
func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refresh, viewType arguments.ViewType, args *arguments.Operation,
func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refresh, viewType arguments.ViewType, args *arguments.Operation, enc encryption.Encryption,
) (*backend.Operation, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Build the operation
opReq := c.Operation(be, viewType)
opReq := c.Operation(be, viewType, enc)
opReq.ConfigDir = "."
opReq.Hooks = view.Hooks()
opReq.Targets = args.Targets

View File

@ -78,8 +78,16 @@ func (c *ShowCommand) Run(rawArgs []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Get the data we need to display
plan, jsonPlan, stateFile, config, schemas, showDiags := c.show(args.Path)
plan, jsonPlan, stateFile, config, schemas, showDiags := c.show(args.Path, enc)
diags = diags.Append(showDiags)
if showDiags.HasErrors() {
view.Diagnostics(diags)
@ -111,7 +119,7 @@ func (c *ShowCommand) Synopsis() string {
return "Show the current state or a saved plan"
}
func (c *ShowCommand) show(path string) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, *tofu.Schemas, tfdiags.Diagnostics) {
func (c *ShowCommand) show(path string, enc encryption.Encryption) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, *tofu.Schemas, tfdiags.Diagnostics) {
var diags, showDiags, migrateDiags tfdiags.Diagnostics
var plan *plans.Plan
var jsonPlan *cloudplan.RemotePlanJSON
@ -122,7 +130,7 @@ func (c *ShowCommand) show(path string) (*plans.Plan, *cloudplan.RemotePlanJSON,
// No plan file or state file argument provided,
// so get the latest state snapshot
if path == "" {
stateFile, showDiags = c.showFromLatestStateSnapshot()
stateFile, showDiags = c.showFromLatestStateSnapshot(enc)
diags = diags.Append(showDiags)
if showDiags.HasErrors() {
return plan, jsonPlan, stateFile, config, schemas, diags
@ -133,7 +141,7 @@ func (c *ShowCommand) show(path string) (*plans.Plan, *cloudplan.RemotePlanJSON,
// so try to load the argument as a plan file first.
// If that fails, try to load it as a statefile.
if path != "" {
plan, jsonPlan, stateFile, config, showDiags = c.showFromPath(path)
plan, jsonPlan, stateFile, config, showDiags = c.showFromPath(path, enc)
diags = diags.Append(showDiags)
if showDiags.HasErrors() {
return plan, jsonPlan, stateFile, config, schemas, diags
@ -158,11 +166,11 @@ func (c *ShowCommand) show(path string) (*plans.Plan, *cloudplan.RemotePlanJSON,
return plan, jsonPlan, stateFile, config, schemas, diags
}
func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Diagnostics) {
func (c *ShowCommand) showFromLatestStateSnapshot(enc encryption.Encryption) (*statefile.File, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
return nil, diags
@ -186,7 +194,7 @@ func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Di
return stateFile, diags
}
func (c *ShowCommand) showFromPath(path string) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, tfdiags.Diagnostics) {
func (c *ShowCommand) showFromPath(path string, enc encryption.Encryption) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
var planErr, stateErr error
var plan *plans.Plan
@ -198,9 +206,9 @@ func (c *ShowCommand) showFromPath(path string) (*plans.Plan, *cloudplan.RemoteP
// state file. First, try to get a plan and associated data from a local
// plan file. If that fails, try to get a json plan from the path argument.
// If that fails, try to get the statefile from the path argument.
plan, jsonPlan, stateFile, config, planErr = c.getPlanFromPath(path)
plan, jsonPlan, stateFile, config, planErr = c.getPlanFromPath(path, enc)
if planErr != nil {
stateFile, stateErr = getStateFromPath(path)
stateFile, stateErr = getStateFromPath(path, enc)
if stateErr != nil {
// To avoid spamming the user with irrelevant errors, first check to
// see if one of our errors happens to know for a fact what file
@ -266,14 +274,14 @@ func (c *ShowCommand) showFromPath(path string) (*plans.Plan, *cloudplan.RemoteP
// yield a json plan, and cloud plans do not yield real plan/state/config
// structs. An error generally suggests that the given path is either a
// directory or a statefile.
func (c *ShowCommand) getPlanFromPath(path string) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, error) {
func (c *ShowCommand) getPlanFromPath(path string, enc encryption.Encryption) (*plans.Plan, *cloudplan.RemotePlanJSON, *statefile.File, *configs.Config, error) {
var err error
var plan *plans.Plan
var jsonPlan *cloudplan.RemotePlanJSON
var stateFile *statefile.File
var config *configs.Config
pf, err := planfile.OpenWrapped(path, encryption.PlanEncryptionTODO())
pf, err := planfile.OpenWrapped(path, enc.PlanFile())
if err != nil {
return nil, nil, nil, nil, err
}
@ -282,15 +290,15 @@ func (c *ShowCommand) getPlanFromPath(path string) (*plans.Plan, *cloudplan.Remo
plan, stateFile, config, err = getDataFromPlanfileReader(lp)
} else if cp, ok := pf.Cloud(); ok {
redacted := c.viewType != arguments.ViewJSON
jsonPlan, err = c.getDataFromCloudPlan(cp, redacted)
jsonPlan, err = c.getDataFromCloudPlan(cp, redacted, enc)
}
return plan, jsonPlan, stateFile, config, err
}
func (c *ShowCommand) getDataFromCloudPlan(plan *cloudplan.SavedPlanBookmark, redacted bool) (*cloudplan.RemotePlanJSON, error) {
func (c *ShowCommand) getDataFromCloudPlan(plan *cloudplan.SavedPlanBookmark, redacted bool, enc encryption.Encryption) (*cloudplan.RemotePlanJSON, error) {
// Set up the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
return nil, errUnusable(backendDiags.Err(), "cloud plan")
}
@ -331,7 +339,7 @@ func getDataFromPlanfileReader(planReader *planfile.Reader) (*plans.Plan, *state
}
// getStateFromPath returns a statefile if the user-supplied path points to a statefile.
func getStateFromPath(path string) (*statefile.File, error) {
func getStateFromPath(path string, enc encryption.Encryption) (*statefile.File, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Error loading statefile: %w", err)
@ -339,7 +347,7 @@ func getStateFromPath(path string) (*statefile.File, error) {
defer file.Close()
var stateFile *statefile.File
stateFile, err = statefile.Read(file, encryption.StateEncryptionTODO()) // Should we use encryption -> statefile config here?
stateFile, err = statefile.Read(file, enc.StateFile())
if err != nil {
return nil, fmt.Errorf("Error reading %s as a statefile: %w", path, err)
}

View File

@ -39,8 +39,15 @@ func (c *StateListCommand) Run(args []string) int {
c.Meta.statePath = statePath
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1

View File

@ -28,17 +28,17 @@ type StateMeta struct {
// the backend, but changes the way that backups are done. This configures
// backups to be timestamped rather than just the original state path plus a
// backup path.
func (c *StateMeta) State() (statemgr.Full, error) {
func (c *StateMeta) State(enc encryption.Encryption) (statemgr.Full, error) {
var realState statemgr.Full
backupPath := c.backupPath
stateOutPath := c.statePath
// use the specified state
if c.statePath != "" {
realState = statemgr.NewFilesystem(c.statePath, encryption.StateEncryptionTODO())
realState = statemgr.NewFilesystem(c.statePath, encryption.StateEncryptionDisabled()) // User specified state file should not be encrypted
} else {
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
return nil, backendDiags.Err()
}
@ -62,7 +62,7 @@ func (c *StateMeta) State() (statemgr.Full, error) {
}
// Get a local backend
localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true})
localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true}, enc.Backend())
if backendDiags.HasErrors() {
// This should never fail
panic(backendDiags.Err())

View File

@ -69,8 +69,15 @@ func (c *StateMvCommand) Run(args []string) int {
setLegacyLocalBackendOptions = append(setLegacyLocalBackendOptions, "-backup-out")
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
if len(setLegacyLocalBackendOptions) > 0 {
currentBackend, diags := c.backendFromConfig(&BackendOpts{})
currentBackend, diags := c.backendFromConfig(&BackendOpts{}, enc.Backend())
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
@ -93,7 +100,7 @@ func (c *StateMvCommand) Run(args []string) int {
}
// Read the from state
stateFromMgr, err := c.State()
stateFromMgr, err := c.State(enc)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
@ -131,7 +138,7 @@ func (c *StateMvCommand) Run(args []string) int {
c.statePath = statePathOut
c.backupPath = backupPathOut
stateToMgr, err = c.State()
stateToMgr, err = c.State(enc)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
@ -392,7 +399,7 @@ func (c *StateMvCommand) Run(args []string) int {
return 0 // This is as far as we go in dry-run mode
}
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -34,8 +34,15 @@ func (c *StatePullCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
@ -65,7 +72,7 @@ func (c *StatePullCommand) Run(args []string) int {
if stateFile != nil { // we produce no output if the statefile is nil
var buf bytes.Buffer
err = statefile.Write(stateFile, &buf, encryption.StateEncryptionTODO())
err = statefile.Write(stateFile, &buf, encryption.StateEncryptionDisabled()) // Don't encrypt to stdout
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1

View File

@ -52,6 +52,13 @@ func (c *StatePushCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Determine our reader for the input state. This is the filepath
// or stdin if "-" is given.
var r io.Reader = os.Stdin
@ -69,7 +76,7 @@ func (c *StatePushCommand) Run(args []string) int {
}
// Read the state
srcStateFile, err := statefile.Read(r, encryption.StateEncryptionTODO()) // Should we use encryption -> statefile config here?
srcStateFile, err := statefile.Read(r, encryption.StateEncryptionDisabled()) // Assume the given statefile is not encrypted
if c, ok := r.(io.Closer); ok {
// Close the reader if possible right now since we're done with it.
c.Close()
@ -80,7 +87,7 @@ func (c *StatePushCommand) Run(args []string) int {
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1

View File

@ -78,8 +78,16 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Initialize the state manager as configured
stateMgr, err := c.State()
stateMgr, err := c.State(enc)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
@ -167,7 +175,7 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
resource.ProviderConfig.Provider = to
}
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -49,8 +49,15 @@ func (c *StateRmCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Get the state
stateMgr, err := c.State()
stateMgr, err := c.State(enc)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
return 1
@ -117,7 +124,7 @@ func (c *StateRmCommand) Run(args []string) int {
return 0 // This is as far as we go in dry-run mode
}
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -50,8 +50,15 @@ func (c *StateShowCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
@ -82,7 +89,7 @@ func (c *StateShowCommand) Run(args []string) int {
}
// Build the operation (required to get the schemas)
opReq := c.Operation(b, arguments.ViewHuman)
opReq := c.Operation(b, arguments.ViewHuman, enc)
opReq.AllowUnsetVariables = true
opReq.ConfigDir = cwd

View File

@ -11,6 +11,7 @@ import (
"sort"
"testing"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/states/statemgr"
)
@ -32,7 +33,7 @@ func testStateBackups(t *testing.T, dir string) []string {
func TestStateDefaultBackupExtension(t *testing.T) {
testCwd(t)
s, err := (&StateMeta{}).State()
s, err := (&StateMeta{}).State(encryption.Disabled())
if err != nil {
t.Fatal(err)
}

View File

@ -67,8 +67,15 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
if encDiags.HasErrors() {
c.showDiagnostics(encDiags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -23,6 +23,7 @@ import (
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/internal/moduletest"
"github.com/opentofu/opentofu/internal/plans"
@ -212,6 +213,9 @@ func (c *TestCommand) Run(rawArgs []string) int {
return 1
}
// Don't use encryption during testing
opts.Encryption = encryption.Disabled()
// Print out all the diagnostics we have from the setup. These will just be
// warnings, and we want them out of the way before we start the actual
// testing.

View File

@ -64,7 +64,7 @@ func (c *UnlockCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, nil) // Should not be needed for an unlock
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -57,8 +57,16 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.Encryption()
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
b, backendDiags := c.Backend(nil, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -63,10 +63,18 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -50,7 +50,7 @@ func (c *WorkspaceListCommand) Run(args []string) int {
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -79,10 +79,18 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
@ -151,7 +159,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
return 1
}
stateFile, err := statefile.Read(f, encryption.StateEncryptionTODO()) // Should we use encryption -> statefile config here?
stateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled()) // Assume given statefile is not encrypted
if err != nil {
c.Ui.Error(err.Error())
return 1

View File

@ -60,10 +60,18 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
return 1
}
// Load the encryption configuration
enc, encDiags := c.EncryptionFromPath(configPath)
diags = diags.Append(encDiags)
if encDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(&BackendOpts{
Config: backendConfig,
})
}, enc.Backend())
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)

View File

@ -337,6 +337,9 @@ var terraformBlockSchema = &hcl.BodySchema{
Type: "provider_meta",
LabelNames: []string{"provider"},
},
{
Type: "encryption",
},
},
}

View File

@ -46,6 +46,17 @@ type basedata struct {
Version string `json:"encryption_version"` // This is both a sigil for a valid encrypted payload and a future compatability field
}
func IsEncryptionPayload(data []byte) (bool, error) {
es := basedata{}
err := json.Unmarshal(data, &es)
if err != nil {
return false, err
}
// This could be extended with full version checking later on
return es.Version != "", nil
}
func (s *baseEncryption) encrypt(data []byte) ([]byte, error) {
// No configuration provided, don't do anything
if s.target == nil {
@ -100,19 +111,29 @@ func (s *baseEncryption) decrypt(data []byte, validator func([]byte) error) ([]b
es := basedata{}
err := json.Unmarshal(data, &es)
if err != nil {
return nil, fmt.Errorf("invalid data format for decryption: %w", err)
}
if len(es.Version) == 0 {
if len(es.Version) == 0 || err != nil {
// Not a valid payload, might be already decrypted
err = validator(data)
if err != nil {
verr := validator(data)
if verr != nil {
// Nope, just bad input
return nil, fmt.Errorf("unable to determine data structure during decryption: %w", err)
// Return the outer json error if we have one
if err != nil {
return nil, fmt.Errorf("invalid data format for decryption: %w, %w", err, verr)
}
// Must have been invalid json payload
return nil, fmt.Errorf("unable to determine data structure during decryption: %w", verr)
}
// Yep, it's already decrypted
return data, nil
for target := s.target; target != nil; target = target.Fallback {
if target.Fallback == nil {
// fallback allowed
return data, nil
}
}
return data, fmt.Errorf("decrypted payload provided without fallback specified")
}
if es.Version != encryptionVersion {

View File

@ -20,7 +20,7 @@ type EncryptionConfig struct {
Backend *EnforcableTargetConfig `hcl:"backend,block"`
StateFile *EnforcableTargetConfig `hcl:"statefile,block"`
PlanFile *EnforcableTargetConfig `hcl:"planfile,block"`
Remote *RemoteConfig `hcl:"remote_data_source,block"`
Remote *RemoteConfig `hcl:"remote,block"`
// Not preserved through merge operations
DeclRange hcl.Range
@ -61,7 +61,7 @@ func (m MethodConfig) Addr() (method.Addr, hcl.Diagnostics) {
// sources.
type RemoteConfig struct {
Default *TargetConfig `hcl:"default,block"`
Targets []NamedTargetConfig `hcl:"remote_data_source,block"`
Targets []NamedTargetConfig `hcl:"remote_state,block"`
}
// TargetConfig describes the target.encryption.state, target.encryption.plan, etc blocks.

View File

@ -15,54 +15,116 @@ import (
// purpose. If no encryption configuration is present, it should return a pass through method that doesn't do anything.
type Encryption interface {
// StateFile produces a StateEncryption overlay for encrypting and decrypting state files for local storage.
StateFile() (StateEncryption, hcl.Diagnostics)
StateFile() StateEncryption
// PlanFile produces a PlanEncryption overlay for encrypting and decrypting plan files.
PlanFile() (PlanEncryption, hcl.Diagnostics)
PlanFile() PlanEncryption
// Backend produces a StateEncryption overlay for storing state files on remote backends, such as an S3 bucket.
Backend() (StateEncryption, hcl.Diagnostics)
Backend() StateEncryption
// RemoteState produces a ReadOnlyStateEncryption for reading remote states using the terraform_remote_state data
// RemoteState produces a StateEncryption for reading remote states using the terraform_remote_state data
// source.
RemoteState(string) (ReadOnlyStateEncryption, hcl.Diagnostics)
RemoteState(string) StateEncryption
}
type encryption struct {
statefile StateEncryption
planfile PlanEncryption
backend StateEncryption
remoteDefault StateEncryption
remotes map[string]StateEncryption
// Inputs
cfg *config.EncryptionConfig
reg registry.Registry
}
// New creates a new Encryption provider from the given configuration and registry.
func New(reg registry.Registry, cfg *config.EncryptionConfig) Encryption {
return &encryption{
func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.Diagnostics) {
if cfg == nil {
return Disabled(), nil
}
enc := &encryption{
cfg: cfg,
reg: reg,
remotes: make(map[string]StateEncryption),
}
}
var diags hcl.Diagnostics
var encDiags hcl.Diagnostics
func (e *encryption) StateFile() (StateEncryption, hcl.Diagnostics) {
return newStateEncryption(e, e.cfg.StateFile.AsTargetConfig(), e.cfg.StateFile.Enforced, "statefile")
}
if cfg.StateFile != nil {
enc.statefile, encDiags = newStateEncryption(enc, cfg.StateFile.AsTargetConfig(), cfg.StateFile.Enforced, "statefile")
diags = append(diags, encDiags...)
} else {
enc.statefile = StateEncryptionDisabled()
}
func (e *encryption) PlanFile() (PlanEncryption, hcl.Diagnostics) {
return newPlanEncryption(e, e.cfg.PlanFile.AsTargetConfig(), e.cfg.PlanFile.Enforced, "planfile")
}
if cfg.PlanFile != nil {
enc.planfile, encDiags = newPlanEncryption(enc, cfg.PlanFile.AsTargetConfig(), cfg.PlanFile.Enforced, "planfile")
diags = append(diags, encDiags...)
} else {
enc.planfile = PlanEncryptionDisabled()
}
func (e *encryption) Backend() (StateEncryption, hcl.Diagnostics) {
return newStateEncryption(e, e.cfg.StateFile.AsTargetConfig(), e.cfg.StateFile.Enforced, "backend")
}
if cfg.Backend != nil {
enc.backend, encDiags = newStateEncryption(enc, cfg.Backend.AsTargetConfig(), cfg.Backend.Enforced, "backend")
diags = append(diags, encDiags...)
} else {
enc.backend = StateEncryptionDisabled()
}
func (e *encryption) RemoteState(name string) (ReadOnlyStateEncryption, hcl.Diagnostics) {
for _, remoteTarget := range e.cfg.Remote.Targets {
if remoteTarget.Name == name {
if cfg.Remote != nil && cfg.Remote.Default != nil {
enc.remoteDefault, encDiags = newStateEncryption(enc, cfg.Remote.Default, false, "remote.default")
diags = append(diags, encDiags...)
} else {
enc.remoteDefault = StateEncryptionDisabled()
}
if cfg.Remote != nil {
for _, remoteTarget := range cfg.Remote.Targets {
// TODO the addr here should be generated in one place.
addr := "remote.remote_state_datasource." + remoteTarget.Name
return newStateEncryption(
e, remoteTarget.AsTargetConfig(), false, addr,
)
enc.remotes[remoteTarget.Name], encDiags = newStateEncryption(enc, remoteTarget.AsTargetConfig(), false, addr)
diags = append(diags, encDiags...)
}
}
return newStateEncryption(e, e.cfg.Remote.Default, false, "remote.default")
if diags.HasErrors() {
return nil, diags
}
return enc, diags
}
func (e *encryption) StateFile() StateEncryption {
return e.statefile
}
func (e *encryption) PlanFile() PlanEncryption {
return e.planfile
}
func (e *encryption) Backend() StateEncryption {
return e.backend
}
func (e *encryption) RemoteState(name string) StateEncryption {
if enc, ok := e.remotes[name]; ok {
return enc
}
return e.remoteDefault
}
// Mostly used in tests
type encryptionDisabled struct{}
func Disabled() Encryption {
return &encryptionDisabled{}
}
func (e *encryptionDisabled) StateFile() StateEncryption { return StateEncryptionDisabled() }
func (e *encryptionDisabled) PlanFile() PlanEncryption { return PlanEncryptionDisabled() }
func (e *encryptionDisabled) Backend() StateEncryption { return StateEncryptionDisabled() }
func (e *encryptionDisabled) RemoteState(name string) StateEncryption {
return StateEncryptionDisabled()
}

View File

@ -0,0 +1,101 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package enctest
// This package is used for supplying a fully configured encryption instance for use in unit and integration tests
import (
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
"github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry"
)
// TODO docstrings once this stabilizes
func EncryptionDirect(configData string) encryption.Encryption {
reg := lockingencryptionregistry.New()
if err := reg.RegisterKeyProvider(static.New()); err != nil {
panic(err)
}
if err := reg.RegisterMethod(aesgcm.New()); err != nil {
panic(err)
}
cfg, diags := config.LoadConfigFromString("Test Config Source", configData)
handleDiags(diags)
enc, diags := encryption.New(reg, cfg)
handleDiags(diags)
return enc
}
func EncryptionRequired() encryption.Encryption {
return EncryptionDirect(`
key_provider "static" "basic" {
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
}
method "aes_gcm" "example" {
keys = key_provider.static.basic
}
statefile {
method = method.aes_gcm.example
}
planfile {
method = method.aes_gcm.example
}
backend {
method = method.aes_gcm.example
}
remote {
default {
method = method.aes_gcm.example
}
}
`)
}
func EncryptionWithFallback() encryption.Encryption {
return EncryptionDirect(`
key_provider "static" "basic" {
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
}
method "aes_gcm" "example" {
keys = key_provider.static.basic
}
statefile {
method = method.aes_gcm.example
fallback {}
}
planfile {
method = method.aes_gcm.example
fallback {}
}
backend {
method = method.aes_gcm.example
fallback {}
}
remote {
default {
method = method.aes_gcm.example
fallback {}
}
}
`)
}
func handleDiags(diags hcl.Diagnostics) {
for _, d := range diags {
println(d.Error())
}
if diags.HasErrors() {
panic(diags.Error())
}
}

View File

@ -62,11 +62,11 @@ func Example() {
cfg := config.MergeConfigs(cfgA, cfgB)
// Construct the encryption object
enc := encryption.New(reg, cfg)
sfe, diags := enc.StateFile()
enc, diags := encryption.New(reg, cfg)
handleDiags(diags)
sfe := enc.StateFile()
// Encrypt the data, for this example we will be using the string "test",
// but in a real world scenario this would be the plan file.
sourceData := []byte("test")

View File

@ -61,8 +61,8 @@ func (p planEncryption) EncryptPlan(data []byte) ([]byte, error) {
func (p planEncryption) DecryptPlan(data []byte) ([]byte, error) {
return p.base.decrypt(data, func(data []byte) error {
// Check magic bytes
if len(data) < 4 || string(data[:4]) != "PK" {
return fmt.Errorf("Invalid plan file")
if len(data) < 2 || string(data[:2]) != "PK" {
return fmt.Errorf("Invalid plan file %v", string(data[:2]))
}
return nil
})
@ -80,8 +80,3 @@ func (s *planDisabled) EncryptPlan(plainPlan []byte) ([]byte, error) {
func (s *planDisabled) DecryptPlan(encryptedPlan []byte) ([]byte, error) {
return encryptedPlan, nil
}
// TODO REMOVEME once plan encryption is fully integrated into the codebase
func PlanEncryptionTODO() PlanEncryption {
return &planDisabled{}
}

View File

@ -13,10 +13,8 @@ import (
"github.com/opentofu/opentofu/internal/encryption/config"
)
const StateEncryptionMarkerField = "encryption"
// ReadOnlyStateEncryption is an encryption layer for reading encrypted state files.
type ReadOnlyStateEncryption interface {
// StateEncryption describes the interface for encrypting state files.
type StateEncryption interface {
// DecryptState decrypts a potentially encrypted state file and returns a valid JSON-serialized state file.
//
// When implementing this function:
@ -34,11 +32,6 @@ type ReadOnlyStateEncryption interface {
// and all encryption-related matters. After the function returns, use the returned byte array as a normal state
// file.
DecryptState([]byte) ([]byte, error)
}
// StateEncryption describes the interface for encrypting state files.
type StateEncryption interface {
ReadOnlyStateEncryption
// EncryptState encrypts a state file and returns the encrypted form.
//
@ -76,7 +69,7 @@ func (s *stateEncryption) EncryptState(plainState []byte) ([]byte, error) {
func (s *stateEncryption) DecryptState(encryptedState []byte) ([]byte, error) {
return s.base.decrypt(encryptedState, func(data []byte) error {
tmp := struct {
FormatVersion string `json:"format_version"`
FormatVersion string `json:"terraform_version"`
}{}
err := json.Unmarshal(data, &tmp)
if err != nil {
@ -103,8 +96,3 @@ func (s *stateDisabled) EncryptState(plainState []byte) ([]byte, error) {
func (s *stateDisabled) DecryptState(encryptedState []byte) ([]byte, error) {
return encryptedState, nil
}
// TODO REMOVEME once state encryption is fully integrated into the codebase
func StateEncryptionTODO() StateEncryption {
return &stateDisabled{}
}

View File

@ -76,8 +76,10 @@ func Read(r io.Reader, enc encryption.StateEncryption) (*File, error) {
return nil, ErrNoState
}
decrypted, decDiags := enc.DecryptState(src)
diags = diags.Append(decDiags)
decrypted, err := enc.DecryptState(src)
if err != nil {
return nil, err
}
state, err := readState(decrypted)
if err != nil {
@ -191,6 +193,23 @@ func sniffJSONStateVersion(src []byte) (uint64, tfdiags.Diagnostics) {
}
if sniff.Version == nil {
encrypted, err := encryption.IsEncryptionPayload(src)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
unsupportedFormat,
fmt.Sprintf("The state file can not be checked for presense of encryption: %s", err.Error()),
))
return 0, diags
}
if encrypted {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
unsupportedFormat,
"This state file is encrypted and can not be read without an encryption configuration",
))
return 0, diags
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
unsupportedFormat,

View File

@ -3,11 +3,13 @@
package statefile
import (
"bytes"
"errors"
"os"
"testing"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/enctest"
)
func TestReadErrNoState_emptyFile(t *testing.T) {
@ -34,3 +36,20 @@ func TestReadErrNoState_nilFile(t *testing.T) {
t.Fatalf("expected ErrNoState, got %T", err)
}
}
func TestReadEmptyWithEncryption(t *testing.T) {
payload := bytes.NewBufferString("")
_, err := Read(payload, enctest.EncryptionRequired().Backend())
if !errors.Is(err, ErrNoState) {
t.Fatalf("expected ErrNoState, got %T", err)
}
}
func TestReadEmptyJsonWithEncryption(t *testing.T) {
payload := bytes.NewBufferString("{}")
_, err := Read(payload, enctest.EncryptionRequired().Backend())
if err == nil || err.Error() != "unable to determine data structure during decryption: Given payload is not a state file" {
t.Fatalf("expected encryption error, got %v", err)
}
}

View File

@ -15,6 +15,7 @@ import (
"github.com/go-test/deep"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/enctest"
)
func TestRoundtrip(t *testing.T) {
@ -79,3 +80,48 @@ func TestRoundtrip(t *testing.T) {
})
}
}
func TestRoundtripEncryption(t *testing.T) {
const path = "testdata/roundtrip/v4-modules.out.tfstate"
enc := enctest.EncryptionWithFallback().Backend()
unencryptedInput, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer unencryptedInput.Close()
// Read unencrypted using fallback
originalState, err := Read(unencryptedInput, enc)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Write encrypted
var encrypted bytes.Buffer
err = Write(originalState, &encrypted, enc)
if err != nil {
t.Fatal(err)
}
// Make sure it is encrypted / not readable
encryptedCopy := encrypted
_, err = Read(&encryptedCopy, encryption.StateEncryptionDisabled())
if err == nil || err.Error() != "Unsupported state file format: This state file is encrypted and can not be read without an encryption configuration" {
t.Fatalf("expected written state file to be encrypted!")
}
// Read encrypted
newState, err := Read(&encrypted, enc)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Compare before/after encryption workflow
problems := deep.Equal(newState, originalState)
sort.Strings(problems)
for _, problem := range problems {
t.Error(problem)
}
}

View File

@ -14,6 +14,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
@ -43,6 +44,7 @@ type ContextOpts struct {
Parallelism int
Providers map[addrs.Provider]providers.Factory
Provisioners map[string]provisioners.Factory
Encryption encryption.Encryption
UIInput UIInput
}
@ -89,6 +91,8 @@ type Context struct {
runCond *sync.Cond
runContext context.Context
runContextCancel context.CancelFunc
encryption encryption.Encryption
}
// (additional methods on Context can be found in context_*.go files.)
@ -144,6 +148,8 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) {
parallelSem: NewSemaphore(par),
providerInputConfig: make(map[string]map[string]cty.Value),
sh: sh,
encryption: opts.Encryption,
}, diags
}

View File

@ -261,6 +261,8 @@ func testContext2(t *testing.T, opts *ContextOpts) *Context {
t.Fatalf("failed to create test context\n\n%s\n", diags.Err())
}
ctx.encryption = encryption.Disabled()
return ctx
}

View File

@ -153,5 +153,6 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
Operation: operation,
StopContext: c.runContext,
PlanTimestamp: opts.PlanTimeTimestamp,
Encryption: c.encryption,
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/checks"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
@ -214,4 +215,7 @@ type EvalContext interface {
// WithPath returns a copy of the context with the internal path set to the
// path argument.
WithPath(path addrs.ModuleInstance) EvalContext
// Returns the currently configured encryption setup
GetEncryption() encryption.Encryption
}

View File

@ -17,6 +17,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/checks"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
@ -76,6 +77,7 @@ type BuiltinEvalContext struct {
InstanceExpanderValue *instances.Expander
MoveResultsValue refactoring.MoveResults
ImportResolverValue *ImportResolver
Encryption encryption.Encryption
}
// BuiltinEvalContext implements EvalContext
@ -515,3 +517,7 @@ func (ctx *BuiltinEvalContext) MoveResults() refactoring.MoveResults {
func (ctx *BuiltinEvalContext) ImportResolver() *ImportResolver {
return ctx.ImportResolverValue
}
func (ctx *BuiltinEvalContext) GetEncryption() encryption.Encryption {
return ctx.Encryption
}

View File

@ -11,6 +11,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/checks"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
@ -412,3 +413,7 @@ func (c *MockEvalContext) InstanceExpander() *instances.Expander {
c.InstanceExpanderCalled = true
return c.InstanceExpanderExpander
}
func (c *MockEvalContext) GetEncryption() encryption.Encryption {
return encryption.Disabled()
}

View File

@ -16,6 +16,7 @@ import (
"github.com/opentofu/opentofu/internal/checks"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
@ -45,6 +46,7 @@ type ContextGraphWalker struct {
RootVariableValues InputValues
Config *configs.Config
PlanTimestamp time.Time
Encryption encryption.Encryption
// This is an output. Do not set this, nor read it while a graph walk
// is in progress.
@ -117,6 +119,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
Evaluator: evaluator,
VariableValues: w.variableValues,
VariableValuesLock: &w.variableValuesLock,
Encryption: w.Encryption,
}
return ctx

View File

@ -17,6 +17,7 @@ import (
"github.com/opentofu/opentofu/internal/checks"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/plans/objchange"
@ -1420,6 +1421,10 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChangesPath [
return ret, nil
}
type ProviderWithEncryption interface {
ReadDataSourceEncrypted(req providers.ReadDataSourceRequest, path addrs.AbsResourceInstance, enc encryption.Encryption) providers.ReadDataSourceResponse
}
// readDataSource handles everything needed to call ReadDataSource on the provider.
// A previously evaluated configVal can be passed in, or a new one is generated
// from the resource configuration.
@ -1474,11 +1479,18 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
return newVal, diags
}
resp := provider.ReadDataSource(providers.ReadDataSourceRequest{
req := providers.ReadDataSourceRequest{
TypeName: n.Addr.ContainingResource().Resource.Type,
Config: configVal,
ProviderMeta: metaConfigVal,
})
}
var resp providers.ReadDataSourceResponse
if tfp, ok := provider.(ProviderWithEncryption); ok {
// Special case for terraform_remote_state
resp = tfp.ReadDataSourceEncrypted(req, n.Addr, ctx.GetEncryption())
} else {
resp = provider.ReadDataSource(req)
}
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() {
return newVal, diags

View File

@ -169,3 +169,12 @@ For more details on `.terraformignore`, please see [Excluding Files from Upload
The CLI integration with cloud backends lets you use them on the command line. The integration requires including a `cloud` block in your OpenTofu configuration. You can define its arguments directly in your configuration file or supply them through environment variables, which can be useful for non-interactive workflows like Continuous Integration (CI).
Refer to [Cloud Backend Settings](/docs/cli/cloud/settings#environment-variables) for a full list of `cloud` block environment variables.
## TF_ENCRYPTION
The `TF_ENCRYPTION` environment variable is an alternate method of specifying the contents of the `terraform { encryption {} }` block. If provided, it will be parsed as either HCL or JSON and override configuration present in the .tf config files.
```shell
# Add/Override encryption key_provider.static.mykp
export TF_ENCRYPTION='key_provider "static" "mykp" { key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169" }'
```