feat(secrets): Add unprotected secret implementation (#13998)

This commit is contained in:
Sven Rebhan 2023-12-04 18:42:48 +01:00 committed by GitHub
parent 193479a988
commit d570f015df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 316 additions and 155 deletions

View File

@ -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. " +

View File

@ -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)
}()

View File

@ -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

View File

@ -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 {

View File

@ -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"`

View File

@ -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)
}

10
go.mod
View File

@ -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

18
go.sum
View File

@ -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=