mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Initial wiring of encryption through the command package (#1316)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
99b43c98fc
commit
5ab6167bbf
@ -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.
|
||||
//
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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())
|
||||
}
|
||||
|
65
internal/command/meta_encryption.go
Normal file
65
internal/command/meta_encryption.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -337,6 +337,9 @@ var terraformBlockSchema = &hcl.BodySchema{
|
||||
Type: "provider_meta",
|
||||
LabelNames: []string{"provider"},
|
||||
},
|
||||
{
|
||||
Type: "encryption",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
|
101
internal/encryption/enctest/setup.go
Normal file
101
internal/encryption/enctest/setup.go
Normal 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())
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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{}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -153,5 +153,6 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
||||
Operation: operation,
|
||||
StopContext: c.runContext,
|
||||
PlanTimestamp: opts.PlanTimeTimestamp,
|
||||
Encryption: c.encryption,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" }'
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user