diff --git a/KubeArmor/cert/cert.go b/KubeArmor/cert/cert.go new file mode 100644 index 0000000000..0eccda893e --- /dev/null +++ b/KubeArmor/cert/cert.go @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +// Package cert is responsible for generating certs dynamically and loading the certs from external sources. + +package cert + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "os" + "path/filepath" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + // ORG kubearmor + KubeArmor_ORG string = "kubearmor" + KubeArmor_CN string = "kubearmor" +) + +var DefaultKubeArmorServerConfig = CertConfig{ + CN: KubeArmor_CN, + Organization: KubeArmor_ORG, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, +} + +var DefaultKubeArmorClientConfig = CertConfig{ + CN: KubeArmor_CN, + Organization: KubeArmor_ORG, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, +} + +var DefaultKubeArmorCAConfig = CertConfig{ + CN: KubeArmor_CN, + Organization: KubeArmor_ORG, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, +} + +type CertConfig struct { + CN string // Common Name + Organization string + DNS []string + IPs []string + KeyUsage x509.KeyUsage + ExtKeyUsage []x509.ExtKeyUsage + NotAfter time.Time +} + +// CertKeyPair type +type CertKeyPair struct { + Crt *x509.Certificate + Key *rsa.PrivateKey +} + +// CertBytes type +type CertBytes struct { + Crt []byte + Key []byte +} + +type CertPath struct { + Base string + CertFile string + KeyFile string // Not Required if CertOnly:true + CertOnly bool // if true read certificate only +} + +func GetCertKeyPairFromCertBytes(certBytes *CertBytes) (*CertKeyPair, error) { + // Parse CA certificate and key + certPem, _ := pem.Decode(certBytes.Crt) + keyPem, _ := pem.Decode(certBytes.Key) + + crt, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + fmt.Println("Error parsing CA certificate:", err) + return nil, err + } + + key, err := x509.ParsePKCS1PrivateKey(keyPem.Bytes) + if err != nil { + fmt.Println("Error parsing CA private key:", err) + return nil, err + } + + return &CertKeyPair{ + Crt: crt, + Key: key, + }, nil +} + +// ReadCertFromFile func reads certificate key pair from the given path +func ReadCertFromFile(certPath *CertPath) (*CertBytes, error) { + var certBytes, keyBytes []byte + certFile := filepath.Clean(filepath.Join(certPath.Base, certPath.CertFile)) + keyFile := filepath.Clean(filepath.Join(certPath.Base, certPath.KeyFile)) + + // Check if certificate file exists + if _, err := os.Stat(certFile); os.IsNotExist(err) { + fmt.Println("certificate file does not exist:", err) + return nil, err + } + + certBytes, err := os.ReadFile(certFile) + if err != nil { + fmt.Println("Error reading CA certificate file:", err) + return nil, err + } + + if !certPath.CertOnly { + // Check if key file exists + if _, err := os.Stat(keyFile); os.IsNotExist(err) { + fmt.Println("CA key file does not exist:", err) + return nil, err + } + + keyBytes, err = os.ReadFile(keyFile) + if err != nil { + fmt.Println("Error reading CA key file:", err) + return nil, err + } + } + + return &CertBytes{ + Crt: certBytes, + Key: keyBytes, + }, nil +} + +// ReadCertFromK8sSecret func reads cert from the k8s tls secret +// it assumes the cert and key file exists with tls.crt and tls.key names respectively +// that is true in case of kubernetes.io/tls secret type, +// https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets +func ReadCertFromK8sSecret(client *kubernetes.Clientset, namespace, secret string) (*CertBytes, error) { + + // get secret + cert, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secret, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return &CertBytes{ + Crt: cert.Data["tls.crt"], + Key: cert.Data["tls.key"], + }, nil +} + +func GenerateCert(cfg *CertConfig) (*CertKeyPair, error) { + // Generate a new RSA private key for the server + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Println("error generating cert private key:", err) + return nil, err + } + + // Create a template for the certificate + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: cfg.CN, Organization: []string{cfg.Organization}}, + NotBefore: time.Now(), + NotAfter: cfg.NotAfter, + KeyUsage: cfg.KeyUsage, + ExtKeyUsage: cfg.ExtKeyUsage, + BasicConstraintsValid: true, + } + + for _, ip := range cfg.IPs { + template.IPAddresses = append(template.IPAddresses, net.ParseIP(ip)) + } + + return &CertKeyPair{Crt: &template, Key: key}, nil +} + +// GenerateSelfSignedCert func generates cert and key signed by provided CA +func GenerateSelfSignedCert(ca *CertKeyPair, cfg *CertConfig) (*CertBytes, error) { + certKeyPair, err := GenerateCert(cfg) + if err != nil { + return nil, err + } + + // Create the certificate signed by the CA + certBytes, err := x509.CreateCertificate(rand.Reader, certKeyPair.Crt, ca.Crt, &certKeyPair.Key.PublicKey, ca.Key) + if err != nil { + fmt.Println("Error creating certificate:", err) + return nil, err + } + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(certKeyPair.Key)}) + + if certPEM == nil || keyPEM == nil { + return nil, fmt.Errorf("error encoding certificate") + } + + return &CertBytes{ + Crt: certPEM, + Key: keyPEM, + }, nil +} diff --git a/KubeArmor/cert/certloader.go b/KubeArmor/cert/certloader.go new file mode 100644 index 0000000000..f4a95624bc --- /dev/null +++ b/KubeArmor/cert/certloader.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +// Package cert is responsible for generating certs dynamically and loading the certs from external sources. +package cert + +import ( + "crypto/tls" + "crypto/x509" + + "k8s.io/client-go/kubernetes" +) + +type CertLoader interface { + GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) +} + +// generate self sign certificate dynamically +type SelfSignedCertLoader struct { + CaCertPath CertPath + CertConfig CertConfig +} + +func (loader *SelfSignedCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) { + // load ca certificate and ca key from given path + caCertBytes, err := ReadCertFromFile(&loader.CaCertPath) + if err != nil { + return nil, nil, err + } + caCert, err := GetCertKeyPairFromCertBytes(caCertBytes) + if err != nil { + return nil, nil, err + } + caCertPool := x509.NewCertPool() + caCertPool.AddCert(caCert.Crt) + // create certificate signed with provided ca certificate + certBytes, err := GenerateSelfSignedCert(caCert, &loader.CertConfig) + if err != nil { + return nil, nil, err + } + cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key) + if err != nil { + return nil, nil, err + } + return &cert, caCertPool, nil +} + +// load certificates provided by external source using file +type ExternalCertLoader struct { + CaCertPath CertPath + CertPath CertPath +} + +func (loader *ExternalCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) { + // load ca certificate from cert path, assuming only ca.crt is present + loader.CaCertPath.CertOnly = true + caCertBytes, err := ReadCertFromFile(&loader.CaCertPath) + if err != nil { + return nil, nil, err + } + caCert, err := GetCertKeyPairFromCertBytes(caCertBytes) + if err != nil { + return nil, nil, err + } + caCertPool := x509.NewCertPool() + caCertPool.AddCert(caCert.Crt) + // load server/client certificate from cert path + certBytes, err := ReadCertFromFile(&loader.CertPath) + if err != nil { + return nil, nil, err + } + cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key) + if err != nil { + return nil, nil, err + } + return &cert, caCertPool, nil +} + +type K8sCertLoader struct { + CertConfig CertConfig + K8sClient *kubernetes.Clientset + Namespace string + Secret string +} + +func (loader *K8sCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) { + // load certificate from k8s secret + caCertBytes, err := ReadCertFromK8sSecret(loader.K8sClient, loader.Namespace, loader.Secret) + if err != nil { + return nil, nil, err + } + caCert, err := GetCertKeyPairFromCertBytes(caCertBytes) + if err != nil { + return nil, nil, err + } + caCertPool := x509.NewCertPool() + caCertPool.AddCert(caCert.Crt) + // create certificate signed with provided ca certificate + certBytes, err := GenerateSelfSignedCert(caCert, &loader.CertConfig) + if err != nil { + return nil, nil, err + } + cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key) + if err != nil { + return nil, nil, err + } + return &cert, caCertPool, nil +} diff --git a/KubeArmor/cert/tls.go b/KubeArmor/cert/tls.go new file mode 100644 index 0000000000..b460c4fa57 --- /dev/null +++ b/KubeArmor/cert/tls.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +// Package cert is responsible for generating certs dynamically and loading the certs from external sources. +package cert + +import ( + "crypto/tls" + + "google.golang.org/grpc/credentials" + "k8s.io/client-go/kubernetes" +) + +const ( + SelfCertProvider string = "self" + ExternalCertProvider string = "external" +) + +type TlsConfig struct { + // Server/Client Certificate Configurations + CertCfg CertConfig + // If CA is Provided Using a K8s Secret + // Namespace, Secret and K8sClient are Required + ReadCACertFromSecret bool + Secret string + Namespace string + K8sClient *kubernetes.Clientset + + CACertPath CertPath + CertPath CertPath + // Source of Client/Server Certificate, + // "self" : Certificates Will be Generated Dynamically + // "external": Certificates Are Provided Using File + CertProvider string +} + +type TlsCredentialManager struct { + CertLoader CertLoader +} + +func NewTlsCredentialManager(cfg *TlsConfig) *TlsCredentialManager { + switch cfg.CertProvider { + case SelfCertProvider: + if cfg.ReadCACertFromSecret { + cl := K8sCertLoader{ + CertConfig: cfg.CertCfg, + K8sClient: cfg.K8sClient, + Namespace: cfg.Namespace, + Secret: cfg.Secret, + } + return &TlsCredentialManager{ + CertLoader: &cl, + } + } + cl := SelfSignedCertLoader{ + CaCertPath: cfg.CACertPath, + CertConfig: cfg.CertCfg, + } + return &TlsCredentialManager{ + CertLoader: &cl, + } + case ExternalCertProvider: + cl := ExternalCertLoader{ + CaCertPath: cfg.CACertPath, + CertPath: cfg.CertPath, + } + return &TlsCredentialManager{ + CertLoader: &cl, + } + } + return nil +} + +func (manager *TlsCredentialManager) CreateTlsClientCredentials() (credentials.TransportCredentials, error) { + clientCert, caCertPool, err := manager.CertLoader.GetCertificateAndCaPool() + if err != nil { + return nil, err + } + // Create tls client credentials + config := &tls.Config{ + Certificates: []tls.Certificate{*clientCert}, + RootCAs: caCertPool, + } + + return credentials.NewTLS(config), nil +} + +func (manager *TlsCredentialManager) CreateTlsServerCredentials() (credentials.TransportCredentials, error) { + serverCert, caCertPool, err := manager.CertLoader.GetCertificateAndCaPool() + if err != nil { + return nil, err + } + // Create tls server credentials + config := &tls.Config{ + Certificates: []tls.Certificate{*serverCert}, + ClientCAs: caCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + + return credentials.NewTLS(config), nil +} + +func GetX509KeyPairFromCertBytes(certBytes *CertBytes) (*tls.Certificate, error) { + cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key) + if err != nil { + return nil, err + } + return &cert, nil +} diff --git a/KubeArmor/config/config.go b/KubeArmor/config/config.go index b6d14e00d8..7f855a11a6 100644 --- a/KubeArmor/config/config.go +++ b/KubeArmor/config/config.go @@ -21,6 +21,9 @@ type KubearmorConfig struct { Host string // Host name to use for feeds GRPC string // gRPC Port to use + TLSEnabled bool // enable tls + TLSCertPath string // tls certification path + TLSCertProvider string // tls certficate provider LogPath string // Log file to use SELinuxProfileDir string // Directory to store SELinux profiles CRISocket string // Container runtime to use @@ -63,6 +66,11 @@ const ( ConfigCluster string = "cluster" ConfigHost string = "host" ConfigGRPC string = "gRPC" + ConfigTLSCertPath string = "tlsCertPath" + ConfigTLSCertProvider string = "tlsCertProvider" + SelfCertProvider string = "self" + ExternalCertProvider string = "external" + ConfigTLS string = "tlsEnabled" ConfigLogPath string = "logPath" ConfigSELinuxProfileDir string = "seLinuxProfileDir" ConfigCRISocket string = "criSocket" @@ -94,6 +102,9 @@ func readCmdLineParams() { hostStr := flag.String(ConfigHost, strings.Split(hostname, ".")[0], "host name") grpcStr := flag.String(ConfigGRPC, "32767", "gRPC port number") + tlsEnabled := flag.Bool(ConfigTLS, false, "enable tls for secure grpc connection") + tlsCertsStr := flag.String(ConfigTLSCertPath, "/var/lib/kubearmor/tls", "path to tls ca certificate files ca.crt, ca.crt") + tlsCertProvider := flag.String(ConfigTLSCertProvider, "self", "source of certificate {self|external}, self: create certificate dynamically, external: provided by some external entity") logStr := flag.String(ConfigLogPath, "none", "log file path, {path|stdout|none}") seLinuxProfileDirStr := flag.String(ConfigSELinuxProfileDir, "/tmp/kubearmor.selinux", "SELinux profile directory") criSocket := flag.String(ConfigCRISocket, "", "path to CRI socket (format: unix:///path/to/file.sock)") @@ -142,6 +153,9 @@ func readCmdLineParams() { viper.SetDefault(ConfigHost, *hostStr) viper.SetDefault(ConfigGRPC, *grpcStr) + viper.SetDefault(ConfigTLS, *tlsEnabled) + viper.SetDefault(ConfigTLSCertPath, *tlsCertsStr) + viper.SetDefault(ConfigTLSCertProvider, *tlsCertProvider) viper.SetDefault(ConfigLogPath, *logStr) viper.SetDefault(ConfigSELinuxProfileDir, *seLinuxProfileDirStr) viper.SetDefault(ConfigCRISocket, *criSocket) @@ -209,6 +223,9 @@ func LoadConfig() error { } GlobalCfg.GRPC = viper.GetString(ConfigGRPC) + GlobalCfg.TLSEnabled = viper.GetBool(ConfigTLS) + GlobalCfg.TLSCertPath = viper.GetString(ConfigTLSCertPath) + GlobalCfg.TLSCertProvider = viper.GetString(ConfigTLSCertProvider) GlobalCfg.LogPath = viper.GetString(ConfigLogPath) GlobalCfg.CRISocket = os.Getenv("CRI_SOCKET") diff --git a/KubeArmor/feeder/feeder.go b/KubeArmor/feeder/feeder.go index 94b8f8a749..7f83c751f1 100644 --- a/KubeArmor/feeder/feeder.go +++ b/KubeArmor/feeder/feeder.go @@ -21,8 +21,10 @@ import ( tp "github.com/kubearmor/KubeArmor/KubeArmor/types" "github.com/google/uuid" + "github.com/kubearmor/KubeArmor/KubeArmor/cert" pb "github.com/kubearmor/KubeArmor/protobuf" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" ) @@ -32,6 +34,16 @@ import ( // Running flag var Running bool +var CACertPath = cert.CertPath{ + Base: cfg.GlobalCfg.TLSCertPath, + CertFile: "ca.crt", + KeyFile: "ca.key", +} +var ServerCertPath = cert.CertPath{ + Base: cfg.GlobalCfg.TLSCertPath, + CertFile: "server.crt", + KeyFile: "server.key", +} // QueueSize const QueueSize = 1000 @@ -303,7 +315,22 @@ func NewFeeder(node *tp.Node, nodeLock **sync.RWMutex) *Feeder { Timeout: 5 * time.Second, } - fd.LogServer = grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)) + if cfg.GlobalCfg.TLSEnabled { + tlsCredentials, err := loadTLSCredentials(node.NodeIP) + if err != nil { + kg.Errf("cannot load TLS credentials: %s", err) + return nil + } + kg.Print("Server started with tls enabled") + // create a log server + fd.LogServer = grpc.NewServer( + grpc.Creds(tlsCredentials), + grpc.KeepaliveEnforcementPolicy(kaep), + grpc.KeepaliveParams(kasp), + ) + } else { + fd.LogServer = grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)) + } pb.RegisterLogServiceServer(fd.LogServer, logService) @@ -711,3 +738,18 @@ func (fd *Feeder) PushLog(log tp.Log) { } } } + +func loadTLSCredentials(ip string) (credentials.TransportCredentials, error) { + // create certificate configurations + serverCertConfig := cert.DefaultKubeArmorServerConfig + serverCertConfig.IPs = []string{ip} + serverCertConfig.NotAfter = time.Now().Add(365 * 24 * time.Hour) //valid for 1 year + // as of now daemonset creates certificates dynamically + tlsConfig := cert.TlsConfig{ + CertCfg: serverCertConfig, + CertProvider: cfg.GlobalCfg.TLSCertProvider, + CACertPath: CACertPath, + } + creds, err := cert.NewTlsCredentialManager(&tlsConfig).CreateTlsServerCredentials() + return creds, err +} diff --git a/tests/go.sum b/tests/go.sum index 9dc42c9940..d32d937556 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -546,6 +546,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -583,6 +584,7 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -608,6 +610,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -681,6 +684,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -740,6 +744,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -771,6 +776,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -810,10 +816,10 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -833,8 +839,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=