From cf7adc51c9f53234e469dd9f0cca06eed0230e8b Mon Sep 17 00:00:00 2001 From: Utku Ozdemir Date: Wed, 7 Dec 2022 22:01:16 +0100 Subject: [PATCH] feat: add RedactSecrets method to v1alpha1.Config Add a way to strip away the secrets from a config. Signed-off-by: Utku Ozdemir --- pkg/machinery/config/provider.go | 5 ++ .../types/v1alpha1/v1alpha1_provider.go | 49 ++++++++++++++++ .../v1alpha1/v1alpha1_readonly_provider.go | 5 ++ .../types/v1alpha1/v1alpha1_redact_test.go | 58 +++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 pkg/machinery/config/types/v1alpha1/v1alpha1_redact_test.go diff --git a/pkg/machinery/config/provider.go b/pkg/machinery/config/provider.go index 8d526b710..93e686232 100644 --- a/pkg/machinery/config/provider.go +++ b/pkg/machinery/config/provider.go @@ -19,6 +19,8 @@ import ( ) // Provider defines the configuration consumption interface. +// +//nolint:interfacebloat type Provider interface { // Config parts accessor. Version() string @@ -33,6 +35,9 @@ type Provider interface { // Bytes returns source YAML representation (if available) or does default encoding. Bytes() ([]byte, error) + // RedactSecrets returns a copy of the Provider with all secrets replaced with the given string. + RedactSecrets(string) Provider + // Encode configuration to YAML using the provided options. EncodeString(encoderOptions ...encoder.Option) (string, error) EncodeBytes(encoderOptions ...encoder.Option) ([]byte, error) diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index f253c7b32..cf119098c 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -102,6 +102,55 @@ func (c *Config) Bytes() ([]byte, error) { return c.EncodeBytes() } +// RedactSecrets implements the config.Provider interface. +func (c *Config) RedactSecrets(replacement string) config.Provider { + if c == nil { + return nil + } + + redactBytes := func(b []byte) []byte { + if len(b) == 0 { + return b + } + + return []byte(replacement) + } + + redactStr := func(s string) string { + return string(redactBytes([]byte(s))) + } + + clone := c.DeepCopy() + + if clone.MachineConfig != nil { + clone.MachineConfig.MachineToken = redactStr(clone.MachineConfig.MachineToken) + if clone.MachineConfig.MachineCA != nil { + clone.MachineConfig.MachineCA.Key = redactBytes(clone.MachineConfig.MachineCA.Key) + } + } + + if clone.ClusterConfig != nil { + clone.ClusterConfig.ClusterSecret = redactStr(clone.ClusterConfig.ClusterSecret) + clone.ClusterConfig.BootstrapToken = redactStr(clone.ClusterConfig.BootstrapToken) + clone.ClusterConfig.ClusterAESCBCEncryptionSecret = redactStr(clone.ClusterConfig.ClusterAESCBCEncryptionSecret) + clone.ClusterConfig.ClusterSecretboxEncryptionSecret = redactStr(clone.ClusterConfig.ClusterSecretboxEncryptionSecret) + + if clone.ClusterConfig.ClusterCA != nil { + clone.ClusterConfig.ClusterCA.Key = redactBytes(clone.ClusterConfig.ClusterCA.Key) + } + + if clone.ClusterConfig.ClusterAggregatorCA != nil { + clone.ClusterConfig.ClusterAggregatorCA.Key = redactBytes(clone.ClusterConfig.ClusterAggregatorCA.Key) + } + + if clone.ClusterConfig.EtcdConfig != nil && clone.ClusterConfig.EtcdConfig.RootCA != nil { + clone.ClusterConfig.EtcdConfig.RootCA.Key = redactBytes(clone.ClusterConfig.EtcdConfig.RootCA.Key) + } + } + + return clone +} + // Raw implements the config.Provider interface. func (c *Config) Raw() interface{} { return c diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_readonly_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_readonly_provider.go index 8faa0e298..4ef2cb27f 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_readonly_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_readonly_provider.go @@ -66,6 +66,11 @@ func (r *ReadonlyProvider) Bytes() ([]byte, error) { return r.bytes, nil } +// RedactSecrets implements the config.Provider interface. +func (r *ReadonlyProvider) RedactSecrets(replacement string) config.Provider { + return r.cfg.RedactSecrets(replacement) +} + // EncodeString implements the config.Provider interface. func (r *ReadonlyProvider) EncodeString(encoderOptions ...encoder.Option) (string, error) { return r.cfg.EncodeString(encoderOptions...) diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_redact_test.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_redact_test.go new file mode 100644 index 000000000..613bec7ea --- /dev/null +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_redact_test.go @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package v1alpha1_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1/generate" + "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1/machine" + "github.com/siderolabs/talos/pkg/machinery/constants" +) + +func TestRedactSecrets(t *testing.T) { + bundle, err := generate.NewSecretsBundle(generate.NewClock()) + require.NoError(t, err) + + input, err := generate.NewInput("test", "https://doesntmatter:6443", constants.DefaultKubernetesVersion, bundle) + require.NoError(t, err) + + config, err := generate.Config(machine.TypeControlPlane, input) + if err != nil { + return + } + + require.NotEmpty(t, config.MachineConfig.MachineToken) + require.NotEmpty(t, config.MachineConfig.MachineCA.Key) + require.NotEmpty(t, config.ClusterConfig.ClusterSecret) + require.NotEmpty(t, config.ClusterConfig.BootstrapToken) + require.Empty(t, config.ClusterConfig.ClusterAESCBCEncryptionSecret) + require.NotEmpty(t, config.ClusterConfig.ClusterSecretboxEncryptionSecret) + require.NotEmpty(t, config.ClusterConfig.ClusterCA.Key) + require.NotEmpty(t, config.ClusterConfig.EtcdConfig.RootCA.Key) + + replacement := "**.***" + + configBytesBefore, err := config.Bytes() + require.NoError(t, err) + + redacted := config.RedactSecrets(replacement) + + configBytesAfter, err := config.Bytes() + require.NoError(t, err) + + require.Equal(t, string(configBytesBefore), string(configBytesAfter), "original config is modified") + + require.Equal(t, replacement, redacted.Machine().Security().Token()) + require.Equal(t, replacement, string(redacted.Machine().Security().CA().Key)) + require.Equal(t, replacement, redacted.Cluster().Secret()) + require.Equal(t, "***", redacted.Cluster().Token().Secret()) + require.Equal(t, "", redacted.Cluster().AESCBCEncryptionSecret()) + require.Equal(t, replacement, redacted.Cluster().SecretboxEncryptionSecret()) + require.Equal(t, replacement, string(redacted.Cluster().CA().Key)) + require.Equal(t, replacement, string(redacted.Cluster().Etcd().CA().Key)) +}