From d570f015df16338f831dcca6faa3fde3f0ca6967 Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:42:48 +0100 Subject: [PATCH] feat(secrets): Add unprotected secret implementation (#13998) --- cmd/telegraf/main.go | 5 + cmd/telegraf/pprof.go | 2 +- cmd/telegraf/telegraf.go | 30 ++-- config/secret.go | 8 + config/secret_test.go | 304 ++++++++++++++++++++--------------- config/secret_unprotected.go | 94 +++++++++++ go.mod | 10 +- go.sum | 18 ++- 8 files changed, 316 insertions(+), 155 deletions(-) create mode 100644 config/secret_unprotected.go diff --git a/cmd/telegraf/main.go b/cmd/telegraf/main.go index d037cc77c..c1f5e49a2 100644 --- a/cmd/telegraf/main.go +++ b/cmd/telegraf/main.go @@ -233,6 +233,7 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi debug: cCtx.Bool("debug"), once: cCtx.Bool("once"), quiet: cCtx.Bool("quiet"), + unprotected: cCtx.Bool("unprotected"), } w := WindowFlags{ @@ -314,6 +315,10 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi Name: "quiet", Usage: "run in quiet mode", }, + &cli.BoolFlag{ + Name: "unprotected", + Usage: "do not protect secrets in memory", + }, &cli.BoolFlag{ Name: "test", Usage: "enable test mode: gather metrics, print them out, and exit. " + diff --git a/cmd/telegraf/pprof.go b/cmd/telegraf/pprof.go index 73f7026e9..662cfaa55 100644 --- a/cmd/telegraf/pprof.go +++ b/cmd/telegraf/pprof.go @@ -41,7 +41,7 @@ func (p *PprofServer) Start(address string) { } if err := server.ListenAndServe(); err != nil { - p.err <- fmt.Errorf("E! %w", err) + p.err <- err } close(p.err) }() diff --git a/cmd/telegraf/telegraf.go b/cmd/telegraf/telegraf.go index 9b43c4ad7..7d46ca764 100644 --- a/cmd/telegraf/telegraf.go +++ b/cmd/telegraf/telegraf.go @@ -44,6 +44,7 @@ type GlobalFlags struct { debug bool once bool quiet bool + unprotected bool } type WindowFlags struct { @@ -84,6 +85,12 @@ func (t *Telegraf) Init(pprofErr <-chan error, f Filters, g GlobalFlags, w Windo t.GlobalFlags = g t.WindowFlags = w + // Disable secret protection before performing any other operation + if g.unprotected { + log.Println("W! Running without secret protection!") + config.DisableSecretProtection() + } + // Set global password if g.password != "" { config.Password = config.NewSecret([]byte(g.password)) @@ -150,7 +157,7 @@ func (t *Telegraf) reloadLoop() error { select { case sig := <-signals: if sig == syscall.SIGHUP { - log.Printf("I! Reloading Telegraf config") + log.Println("I! Reloading Telegraf config") <-reload reload <- true } @@ -325,17 +332,18 @@ func (t *Telegraf) runAgent(ctx context.Context, c *config.Config, reloadConfig } // Compute the amount of locked memory needed for the secrets - required := 2 * c.NumberSecrets * uint64(os.Getpagesize()) - available := getLockedMemoryLimit() - if required > available { - required /= 1024 - available /= 1024 - log.Printf("I! Found %d secrets...", c.NumberSecrets) - msg := fmt.Sprintf("Insufficient lockable memory %dkb when %dkb is required.", available, required) - msg += " Please increase the limit for Telegraf in your Operating System!" - log.Printf("W! " + color.RedString(msg)) + if !t.GlobalFlags.unprotected { + required := 3 * c.NumberSecrets * uint64(os.Getpagesize()) + available := getLockedMemoryLimit() + if required > available { + required /= 1024 + available /= 1024 + log.Printf("I! Found %d secrets...", c.NumberSecrets) + msg := fmt.Sprintf("Insufficient lockable memory %dkb when %dkb is required.", available, required) + msg += " Please increase the limit for Telegraf in your Operating System!" + log.Printf("W! " + color.RedString(msg)) + } } - ag := agent.NewAgent(c) // Notify systemd that telegraf is ready diff --git a/config/secret.go b/config/secret.go index a6cb1ca87..77f18bdb1 100644 --- a/config/secret.go +++ b/config/secret.go @@ -37,6 +37,14 @@ type secretImpl interface { Wipe(secret []byte) } +func EnableSecretProtection() { + selectedImpl = &protectedSecretImpl{} +} + +func DisableSecretProtection() { + selectedImpl = &unprotectedSecretImpl{} +} + // secretContainer represents an abstraction of the container holding the // actual secret value type secretContainer interface { diff --git a/config/secret_test.go b/config/secret_test.go index f990e8d92..b17697b9c 100644 --- a/config/secret_test.go +++ b/config/secret_test.go @@ -7,6 +7,7 @@ import ( "github.com/awnumar/memguard" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" @@ -435,129 +436,6 @@ func TestSecretStoreInvalidKeys(t *testing.T) { } } -func TestSecretEqualTo(t *testing.T) { - mysecret := "a wonderful test" - s := NewSecret([]byte(mysecret)) - defer s.Destroy() - - equal, err := s.EqualTo([]byte(mysecret)) - require.NoError(t, err) - require.True(t, equal) - - equal, err = s.EqualTo([]byte("some random text")) - require.NoError(t, err) - require.False(t, equal) -} - -func TestSecretStoreInvalidReference(t *testing.T) { - // Make sure we clean-up our mess - defer func() { unlinkedSecrets = make([]*Secret, 0) }() - - cfg := []byte( - ` -[[inputs.mockup]] - secret = "@{mock:test}" -`) - - c := NewConfig() - require.NoError(t, c.LoadConfigData(cfg)) - require.Len(t, c.Inputs, 1) - - // Create a mockup secretstore - store := &MockupSecretStore{ - Secrets: map[string][]byte{"test": []byte("Arca Jeth")}, - } - require.NoError(t, store.Init()) - c.SecretStores["foo"] = store - err := c.LinkSecrets() - require.EqualError(t, err, `unknown secret-store for "@{mock:test}"`) - - for _, input := range c.Inputs { - plugin := input.Input.(*MockupSecretPlugin) - secret, err := plugin.Secret.Get() - require.EqualError(t, err, `unlinked parts in secret: @{mock:test}`) - require.Empty(t, secret) - } -} - -func TestSecretStoreStaticChanging(t *testing.T) { - defer func() { unlinkedSecrets = make([]*Secret, 0) }() - - cfg := []byte( - ` -[[inputs.mockup]] - secret = "@{mock:secret}" -`) - - c := NewConfig() - err := c.LoadConfigData(cfg) - require.NoError(t, err) - require.Len(t, c.Inputs, 1) - - // Create a mockup secretstore - store := &MockupSecretStore{ - Secrets: map[string][]byte{"secret": []byte("Ood Bnar")}, - Dynamic: false, - } - require.NoError(t, store.Init()) - c.SecretStores["mock"] = store - require.NoError(t, c.LinkSecrets()) - - sequence := []string{"Ood Bnar", "Thon", "Obi-Wan Kenobi", "Arca Jeth"} - plugin := c.Inputs[0].Input.(*MockupSecretPlugin) - secret, err := plugin.Secret.Get() - require.NoError(t, err) - defer secret.Destroy() - - require.EqualValues(t, "Ood Bnar", secret.TemporaryString()) - - for _, v := range sequence { - store.Secrets["secret"] = []byte(v) - secret, err := plugin.Secret.Get() - require.NoError(t, err) - - // The secret should not change as the store is marked non-dyamic! - require.EqualValues(t, "Ood Bnar", secret.TemporaryString()) - secret.Destroy() - } -} - -func TestSecretStoreDynamic(t *testing.T) { - defer func() { unlinkedSecrets = make([]*Secret, 0) }() - - cfg := []byte( - ` -[[inputs.mockup]] - secret = "@{mock:secret}" -`) - - c := NewConfig() - err := c.LoadConfigData(cfg) - require.NoError(t, err) - require.Len(t, c.Inputs, 1) - - // Create a mockup secretstore - store := &MockupSecretStore{ - Secrets: map[string][]byte{"secret": []byte("Ood Bnar")}, - Dynamic: true, - } - require.NoError(t, store.Init()) - c.SecretStores["mock"] = store - require.NoError(t, c.LinkSecrets()) - - sequence := []string{"Ood Bnar", "Thon", "Obi-Wan Kenobi", "Arca Jeth"} - plugin := c.Inputs[0].Input.(*MockupSecretPlugin) - for _, v := range sequence { - store.Secrets["secret"] = []byte(v) - secret, err := plugin.Secret.Get() - require.NoError(t, err) - - // The secret should not change as the store is marked non-dynamic! - require.EqualValues(t, v, secret.TemporaryString()) - secret.Destroy() - } -} - func TestSecretStoreDeclarationMissingID(t *testing.T) { defer func() { unlinkedSecrets = make([]*Secret, 0) }() @@ -604,8 +482,152 @@ func TestSecretStoreDeclarationValidID(t *testing.T) { } } -func TestSecretSet(t *testing.T) { - defer func() { unlinkedSecrets = make([]*Secret, 0) }() +type SecretImplTestSuite struct { + suite.Suite + protected bool +} + +func (tsuite *SecretImplTestSuite) SetupSuite() { + if tsuite.protected { + EnableSecretProtection() + } else { + DisableSecretProtection() + } +} + +func (*SecretImplTestSuite) TearDownSuite() { + EnableSecretProtection() +} + +func (*SecretImplTestSuite) TearDownTest() { + unlinkedSecrets = make([]*Secret, 0) +} + +func (tsuite *SecretImplTestSuite) TestSecretEqualTo() { + t := tsuite.T() + mysecret := "a wonderful test" + s := NewSecret([]byte(mysecret)) + defer s.Destroy() + + equal, err := s.EqualTo([]byte(mysecret)) + require.NoError(t, err) + require.True(t, equal) + + equal, err = s.EqualTo([]byte("some random text")) + require.NoError(t, err) + require.False(t, equal) +} + +func (tsuite *SecretImplTestSuite) TestSecretStoreInvalidReference() { + t := tsuite.T() + + cfg := []byte( + ` +[[inputs.mockup]] + secret = "@{mock:test}" +`) + + c := NewConfig() + require.NoError(t, c.LoadConfigData(cfg)) + require.Len(t, c.Inputs, 1) + + // Create a mockup secretstore + store := &MockupSecretStore{ + Secrets: map[string][]byte{"test": []byte("Arca Jeth")}, + } + require.NoError(t, store.Init()) + c.SecretStores["foo"] = store + err := c.LinkSecrets() + require.EqualError(t, err, `unknown secret-store for "@{mock:test}"`) + + for _, input := range c.Inputs { + plugin := input.Input.(*MockupSecretPlugin) + secret, err := plugin.Secret.Get() + require.EqualError(t, err, `unlinked parts in secret: @{mock:test}`) + require.Empty(t, secret) + } +} + +func (tsuite *SecretImplTestSuite) TestSecretStoreStaticChanging() { + t := tsuite.T() + + cfg := []byte( + ` +[[inputs.mockup]] + secret = "@{mock:secret}" +`) + + c := NewConfig() + err := c.LoadConfigData(cfg) + require.NoError(t, err) + require.Len(t, c.Inputs, 1) + + // Create a mockup secretstore + store := &MockupSecretStore{ + Secrets: map[string][]byte{"secret": []byte("Ood Bnar")}, + Dynamic: false, + } + require.NoError(t, store.Init()) + c.SecretStores["mock"] = store + require.NoError(t, c.LinkSecrets()) + + sequence := []string{"Ood Bnar", "Thon", "Obi-Wan Kenobi", "Arca Jeth"} + plugin := c.Inputs[0].Input.(*MockupSecretPlugin) + secret, err := plugin.Secret.Get() + require.NoError(t, err) + defer secret.Destroy() + + require.EqualValues(t, "Ood Bnar", secret.TemporaryString()) + + for _, v := range sequence { + store.Secrets["secret"] = []byte(v) + secret, err := plugin.Secret.Get() + require.NoError(t, err) + + // The secret should not change as the store is marked non-dyamic! + require.EqualValues(t, "Ood Bnar", secret.TemporaryString()) + secret.Destroy() + } +} + +func (tsuite *SecretImplTestSuite) TestSecretStoreDynamic() { + t := tsuite.T() + + cfg := []byte( + ` +[[inputs.mockup]] + secret = "@{mock:secret}" +`) + + c := NewConfig() + err := c.LoadConfigData(cfg) + require.NoError(t, err) + require.Len(t, c.Inputs, 1) + + // Create a mockup secretstore + store := &MockupSecretStore{ + Secrets: map[string][]byte{"secret": []byte("Ood Bnar")}, + Dynamic: true, + } + require.NoError(t, store.Init()) + c.SecretStores["mock"] = store + require.NoError(t, c.LinkSecrets()) + + sequence := []string{"Ood Bnar", "Thon", "Obi-Wan Kenobi", "Arca Jeth"} + plugin := c.Inputs[0].Input.(*MockupSecretPlugin) + for _, v := range sequence { + store.Secrets["secret"] = []byte(v) + secret, err := plugin.Secret.Get() + require.NoError(t, err) + + // The secret should not change as the store is marked non-dynamic! + require.EqualValues(t, v, secret.TemporaryString()) + secret.Destroy() + } +} + +func (tsuite *SecretImplTestSuite) TestSecretSet() { + t := tsuite.T() cfg := []byte(` [[inputs.mockup]] @@ -630,9 +652,8 @@ func TestSecretSet(t *testing.T) { require.EqualValues(t, "another secret", newsecret.TemporaryString()) } -func TestSecretSetResolve(t *testing.T) { - defer func() { unlinkedSecrets = make([]*Secret, 0) }() - +func (tsuite *SecretImplTestSuite) TestSecretSetResolve() { + t := tsuite.T() cfg := []byte(` [[inputs.mockup]] secret = "@{mock:secret}" @@ -664,8 +685,8 @@ func TestSecretSetResolve(t *testing.T) { require.EqualValues(t, "Ood Bnar is cool", newsecret.TemporaryString()) } -func TestSecretSetResolveInvalid(t *testing.T) { - defer func() { unlinkedSecrets = make([]*Secret, 0) }() +func (tsuite *SecretImplTestSuite) TestSecretSetResolveInvalid() { + t := tsuite.T() cfg := []byte(` [[inputs.mockup]] @@ -695,6 +716,29 @@ func TestSecretSetResolveInvalid(t *testing.T) { require.ErrorContains(t, err, `linking new secrets failed: unlinked part "@{mock:another_secret}"`) } +func TestSecretImplUnprotected(t *testing.T) { + impl := &unprotectedSecretImpl{} + container := impl.Container([]byte("foobar")) + require.NotNil(t, container) + c, ok := container.(*unprotectedSecretContainer) + require.True(t, ok) + require.Equal(t, "foobar", string(c.buf.content)) + buf, err := container.Buffer() + require.NoError(t, err) + require.NotNil(t, buf) + require.Equal(t, []byte("foobar"), buf.Bytes()) + require.Equal(t, "foobar", buf.TemporaryString()) + require.Equal(t, "foobar", buf.String()) +} + +func TestSecretImplTestSuiteUnprotected(t *testing.T) { + suite.Run(t, &SecretImplTestSuite{protected: false}) +} + +func TestSecretImplTestSuiteProtected(t *testing.T) { + suite.Run(t, &SecretImplTestSuite{protected: true}) +} + /*** Mockup (input) plugin for testing to avoid cyclic dependencies ***/ type MockupSecretPlugin struct { Secret Secret `toml:"secret"` diff --git a/config/secret_unprotected.go b/config/secret_unprotected.go new file mode 100644 index 000000000..bb50a7964 --- /dev/null +++ b/config/secret_unprotected.go @@ -0,0 +1,94 @@ +package config + +import ( + "bytes" + "unsafe" +) + +type unprotectedSecretImpl struct{} + +func (*unprotectedSecretImpl) Container(secret []byte) secretContainer { + return &unprotectedSecretContainer{buf: newUnlockedBuffer(secret)} +} + +func (*unprotectedSecretImpl) EmptyBuffer() SecretBuffer { + return &unlockedBuffer{} +} + +func (*unprotectedSecretImpl) Wipe(secret []byte) { + for i := range secret { + secret[i] = 0 + } +} + +type unlockedBuffer struct { + content []byte +} + +func newUnlockedBuffer(secret []byte) *unlockedBuffer { + return &unlockedBuffer{bytes.Clone(secret)} +} + +func (lb *unlockedBuffer) Size() int { + return len(lb.content) +} + +func (lb *unlockedBuffer) Grow(_ int) { + // The underlying byte-buffer will grow dynamically +} + +func (lb *unlockedBuffer) Bytes() []byte { + return lb.content +} + +func (lb *unlockedBuffer) TemporaryString() string { + //nolint:gosec // G103: Valid use of unsafe call to cast underlying bytes to string + return unsafe.String(&lb.content[0], len(lb.content)) +} + +func (lb *unlockedBuffer) String() string { + return string(lb.content) +} + +func (lb *unlockedBuffer) Destroy() { + selectedImpl.Wipe(lb.content) + lb.content = nil +} + +type unprotectedSecretContainer struct { + buf *unlockedBuffer +} + +func (c *unprotectedSecretContainer) Destroy() { + if c.buf == nil { + return + } + + // Wipe the secret from memory + c.buf.Destroy() + c.buf = nil +} + +func (c *unprotectedSecretContainer) Equals(ref []byte) (bool, error) { + if c.buf == nil { + return false, nil + } + + return bytes.Equal(c.buf.content, ref), nil +} + +func (c *unprotectedSecretContainer) Buffer() (SecretBuffer, error) { + if c.buf == nil { + return &unlockedBuffer{}, nil + } + + return newUnlockedBuffer(c.buf.content), nil +} + +func (c *unprotectedSecretContainer) AsBuffer(secret []byte) SecretBuffer { + return &unlockedBuffer{secret} +} + +func (c *unprotectedSecretContainer) Replace(secret []byte) { + c.buf = newUnlockedBuffer(secret) +} diff --git a/go.mod b/go.mod index 62123d11a..296296eff 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/apache/thrift v0.18.1 github.com/aristanetworks/goarista v0.0.0-20190325233358-a123909ec740 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/awnumar/memguard v0.22.3 + github.com/awnumar/memguard v0.22.4-0.20231204102859-fce56aae03b8 github.com/aws/aws-sdk-go-v2 v1.23.1 github.com/aws/aws-sdk-go-v2/config v1.19.1 github.com/aws/aws-sdk-go-v2/credentials v1.13.43 @@ -196,13 +196,13 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 go.opentelemetry.io/otel/sdk/metric v1.21.0 go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.16.0 golang.org/x/mod v0.14.0 golang.org/x/net v0.17.0 golang.org/x/oauth2 v0.13.0 golang.org/x/sync v0.5.0 - golang.org/x/sys v0.14.0 - golang.org/x/term v0.13.0 + golang.org/x/sys v0.15.0 + golang.org/x/term v0.15.0 golang.org/x/text v0.14.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211230205640-daad0b7ba671 gonum.org/v1/gonum v0.14.0 @@ -259,7 +259,7 @@ require ( github.com/apache/arrow/go/v12 v12.0.1 // indirect github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/awnumar/memcall v0.1.2 // indirect + github.com/awnumar/memcall v0.2.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.2.0 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.70 // indirect diff --git a/go.sum b/go.sum index 82cee408e..376d35774 100644 --- a/go.sum +++ b/go.sum @@ -815,10 +815,10 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/awnumar/memcall v0.1.2 h1:7gOfDTL+BJ6nnbtAp9+HQzUFjtP1hEseRQq8eP055QY= -github.com/awnumar/memcall v0.1.2/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= -github.com/awnumar/memguard v0.22.3 h1:b4sgUXtbUjhrGELPbuC62wU+BsPQy+8lkWed9Z+pj0Y= -github.com/awnumar/memguard v0.22.3/go.mod h1:mmGunnffnLHlxE5rRgQc3j+uwPZ27eYb61ccr8Clz2Y= +github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI= +github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= +github.com/awnumar/memguard v0.22.4-0.20231204102859-fce56aae03b8 h1:PxGpgmbeAdajdtUIMRDUSisWzTIlhQlqQcSEXkljBBk= +github.com/awnumar/memguard v0.22.4-0.20231204102859-fce56aae03b8/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE= github.com/aws/aws-sdk-go v1.19.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= @@ -2372,8 +2372,9 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2711,8 +2712,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2724,8 +2725,9 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=