Files
opentf/internal/encryption/encryption.go
2025-06-19 10:46:31 +01:00

132 lines
4.0 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package encryption
import (
"context"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/method/unencrypted"
"github.com/opentofu/opentofu/internal/encryption/registry"
)
// Encryption contains the methods for obtaining a StateEncryption or PlanEncryption correctly configured for a specific
// purpose. If no encryption configuration is present, it should return a pass through method that doesn't do anything.
type Encryption interface {
// State produces a StateEncryption overlay for encrypting and decrypting state files for local storage.
State() StateEncryption
// Plan produces a PlanEncryption overlay for encrypting and decrypting plan files.
Plan() PlanEncryption
// RemoteState produces a StateEncryption for reading remote states using the terraform_remote_state data
// source.
RemoteState(string) StateEncryption
}
type encryption struct {
state StateEncryption
plan PlanEncryption
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(ctx context.Context, reg registry.Registry, cfg *config.EncryptionConfig, staticEval *configs.StaticEvaluator) (Encryption, hcl.Diagnostics) {
if cfg == nil {
return Disabled(), nil
}
var diags hcl.Diagnostics
// Early check for unencrypted, so we can warn about it
for _, mc := range cfg.MethodConfigs {
if unencrypted.IsConfig(mc) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Unencrypted method configured",
Detail: "Method unencrypted is present in configuration. This is a security risk and should only be enabled during migrations.",
Subject: cfg.DeclRange.Ptr(),
})
}
}
enc := &encryption{
cfg: cfg,
reg: reg,
remotes: make(map[string]StateEncryption),
}
var encDiags hcl.Diagnostics
if cfg.State != nil {
enc.state, encDiags = newStateEncryption(ctx, enc, cfg.State.AsTargetConfig(), cfg.State.Enforced, "state", staticEval)
diags = append(diags, encDiags...)
} else {
enc.state = StateEncryptionDisabled()
}
if cfg.Plan != nil {
enc.plan, encDiags = newPlanEncryption(ctx, enc, cfg.Plan.AsTargetConfig(), cfg.Plan.Enforced, "plan", staticEval)
diags = append(diags, encDiags...)
} else {
enc.plan = PlanEncryptionDisabled()
}
if cfg.Remote != nil && cfg.Remote.Default != nil {
enc.remoteDefault, encDiags = newStateEncryption(ctx, enc, cfg.Remote.Default, false, "remote.default", staticEval)
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
enc.remotes[remoteTarget.Name], encDiags = newStateEncryption(ctx, enc, remoteTarget.AsTargetConfig(), false, addr, staticEval)
diags = append(diags, encDiags...)
}
}
if diags.HasErrors() {
return nil, diags
}
return enc, diags
}
func (e *encryption) State() StateEncryption {
return e.state
}
func (e *encryption) Plan() PlanEncryption {
return e.plan
}
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) State() StateEncryption { return StateEncryptionDisabled() }
func (e *encryptionDisabled) Plan() PlanEncryption { return PlanEncryptionDisabled() }
func (e *encryptionDisabled) RemoteState(name string) StateEncryption {
return StateEncryptionDisabled()
}