From 88a1bac45ffd543f01da0fc08575f4f752719a91 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 11 Apr 2024 15:46:40 -0300 Subject: [PATCH 01/41] Agent: implements creation of self-signed TLS certificates Those certificates will grant gRPC a secure channel In my tests locally Defender didn't complain about the certificates. It's advisable to test in an environment similar to corporate to make sure it works as expected. Clients only need the root CA certificate to trust the server certificate, with the caveat that the server name must be aliased to the CN declared in the certificate. --- common/certs/certs.go | 152 ++++++++++++++++++ common/certs/certs_test.go | 116 +++++++++++++ common/consts.go | 6 + common/go.mod | 2 +- go.work.sum | 122 +++++++++++++- windows-agent/internal/proservices/certs.go | 75 +++++++++ .../internal/proservices/certs_test.go | 47 ++++++ 7 files changed, 517 insertions(+), 3 deletions(-) create mode 100644 common/certs/certs.go create mode 100644 common/certs/certs_test.go create mode 100644 windows-agent/internal/proservices/certs.go create mode 100644 windows-agent/internal/proservices/certs_test.go diff --git a/common/certs/certs.go b/common/certs/certs.go new file mode 100644 index 000000000..001376840 --- /dev/null +++ b/common/certs/certs.go @@ -0,0 +1,152 @@ +// Package certs provides functions to create certificates suitable for mTLS communication. +// In production only the agent should create those certificates, but placing this in the common module facilities other components's tests. +package certs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/ubuntu/decorate" +) + +// Heavily inspired in: +// - https://go.dev/src/crypto/tls/generate_cert.go +// - https://github.com/grpc/grpc-go/blob/master/examples/features/encryption/mTLS +// - and https://gist.github.com/annanay25/43e3846e21b30818d8dcd5f9987e852d. + +// CreateTLSCertificateSignedBy creates a certificate and key pair usable for authentication signed by the root certificate authority (root CA) certificate and key provided and write them into destDir in the PEM format. +func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACert *x509.Certificate, rootCAKey *ecdsa.PrivateKey, destDir string) (tlsCert *tls.Certificate, err error) { + decorate.OnError(&err, "could not create root signed certificate pair for %s:", name) + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate random key: %v", err) + } + + certTmpl := template(certCN, serial) + + // Customizing the usage for client and server certificates: + // I only got the certificates signed by the root one when I manually set the AuthorityKeyId, + // even though x509.CreateCertificate documentation says it will use it, if present. + certTmpl.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment + certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} + certTmpl.AuthorityKeyId = rootCACert.SubjectKeyId + + cert, der, err := createCert(certTmpl, rootCACert, &key.PublicKey, rootCAKey) + if err != nil { + return nil, err + } + + // Verify the certificate against the root certificate. + caCertPool := x509.NewCertPool() + caCertPool.AddCert(rootCACert) + if _, err = cert.Verify(x509.VerifyOptions{Roots: caCertPool}); err != nil { + return nil, fmt.Errorf("certificate verification failed: %v", err) + } + + if err = writeCert(filepath.Join(destDir, name+"_cert.pem"), der); err != nil { + return nil, err + } + if err = writeKey(filepath.Join(destDir, name+"_key.pem"), key); err != nil { + return nil, err + } + + return &tls.Certificate{ + Certificate: [][]byte{der}, + PrivateKey: key, + Leaf: cert, + }, nil +} + +// CreateRootCA creates a new root certificate authority (CA) certificate and private key pair with the serial number and common name provided. +// Only the cert is written into destDir in the PEM format. Being a CA, the certificate and private key returned can be used to sign other certificates. +func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCert *x509.Certificate, rootKey *ecdsa.PrivateKey, err error) { + // generate a new key-pair for the root certificate based on the P256 elliptic curve + rootKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate random key: %v", err) + } + + rootCertTmpl := template(commonName, serialNo) + + // this cert will be the CA that we will use to sign the server cert + rootCertTmpl.IsCA = true + rootCertTmpl.Subject.CommonName = commonName + " CA" + rootCertTmpl.KeyUsage = x509.KeyUsageCertSign + + rootCert, rootDER, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) + if err != nil { + return nil, nil, err + } + + // Write the CA certificate to disk. + // Notice that we don't write the private key to disk. Only the caller of this function can create other certificates signed by this root CA. + if err = writeCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { + return nil, nil, err + } + + return rootCert, rootKey, nil +} + +// createCert invokes x509.CreateCertificate and returns the certificate and it's DER as bytes for serialization. +func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (cert *x509.Certificate, certDER []byte, err error) { + decorate.OnError(&err, "could not create certificate:") + + certDER, err = x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv) + if err != nil { + return nil, nil, err + } + + // parse the resulting certificate so we can use it again + cert, err = x509.ParseCertificate(certDER) + + return cert, certDER, err +} + +// template is a helper function to create a cert template with a serial number and other required fields filled in for UP4W specific use case. +func template(commonName string, serial *big.Int) *x509.Certificate { + return &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{Organization: []string{commonName}, CommonName: commonName}, + DNSNames: []string{commonName, "localhost", "127.0.0.1"}, + SignatureAlgorithm: x509.ECDSAWithSHA256, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 30), // arbitrarily chosen expiration in a month + BasicConstraintsValid: true, + } +} + +// writeCert writes a certificate to disk in PEM format to the given filename. +func writeCert(filename string, DER []byte) error { + certOut, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to open %q for writing: %v", filename, err) + } + defer certOut.Close() + return pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: DER}) +} + +// writeKey writes a private key to disk in PEM format to the given filename. +func writeKey(filename string, priv *ecdsa.PrivateKey) error { + keyOut, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("failed to open %q for writing: %v", filename, err) + } + defer keyOut.Close() + p, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return fmt.Errorf("failed to marshal private key: %v", err) + } + + return pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: p}) +} diff --git a/common/certs/certs_test.go b/common/certs/certs_test.go new file mode 100644 index 000000000..01730a305 --- /dev/null +++ b/common/certs/certs_test.go @@ -0,0 +1,116 @@ +package certs_test + +import ( + "math/big" + "os" + "path/filepath" + "testing" + + "github.com/canonical/ubuntu-pro-for-wsl/common/certs" + "github.com/stretchr/testify/require" +) + +func TestCreateTLSCertificateSignedBy(t *testing.T) { + t.Parallel() + + testcases := map[string]struct { + missingSerialNumber bool + + rootIsNotCA bool + breakCertPem bool + breakKeyPem bool + + wantErr bool + }{ + "Success": {}, + + "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, + "Error when the signing certificate is not an authority": {rootIsNotCA: true, wantErr: true}, + "Error when the cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, + "Error when the key.pem file cannot be written": {breakKeyPem: true, wantErr: true}, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", new(big.Int).SetInt64(1), t.TempDir()) + require.NoError(t, err, "Setup: failed to generate root CA cert") + if tc.rootIsNotCA { + rootCert.IsCA = false + rootCert.AuthorityKeyId = nil + } + + var testSerial *big.Int + if !tc.missingSerialNumber { + testSerial = new(big.Int).SetInt64(1) + } + + dir := t.TempDir() + + if tc.breakCertPem { + require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") + } + + if tc.breakKeyPem { + require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700), "Setup: failed to create a directory that should break key.pem") + } + + tlsCert, err := certs.CreateTLSCertificateSignedBy("server", "test-server-cn", testSerial, rootCert, rootKey, dir) + + if tc.wantErr { + require.Error(t, err, "CreateTLSCertificateSignedBy should have failed") + return + } + require.NoError(t, err, "CreateTLSCertificateSignedBy failed") + require.NotNil(t, tlsCert, "CreateTLSCertificateSignedBy returned a nil certificate") + require.FileExists(t, filepath.Join(dir, "server_cert.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") + }) + } +} + +func TestCreateRooCA(t *testing.T) { + t.Parallel() + + testcases := map[string]struct { + missingSerialNumber bool + rootIsNotCA bool + breakCertPem bool + breakKeyPem bool + + wantErr bool + }{ + "Success": {}, + + "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, + "Error when the ca_cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + var testSerial *big.Int + if !tc.missingSerialNumber { + testSerial = new(big.Int).SetInt64(1) + } + + dir := t.TempDir() + + if tc.breakCertPem { + require.NoError(t, os.MkdirAll(filepath.Join(dir, "ca_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") + } + + rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", testSerial, dir) + + if tc.wantErr { + require.Error(t, err, "CreateRootCA should have failed") + return + } + require.NoError(t, err, "CreateRootCA failed") + require.NotNil(t, rootCert, "CreateRootCA didn't return a certificate") + require.NotNil(t, rootKey, "CreateRootCA didn't return a private key") + require.FileExists(t, filepath.Join(dir, "ca_cert.pem"), "CreateRootCA failed to write the certificate to disk") + }) + } +} diff --git a/common/consts.go b/common/consts.go index 3523ab86b..7cef3d5d7 100644 --- a/common/consts.go +++ b/common/consts.go @@ -20,4 +20,10 @@ const ( // // TODO: Replace with real product ID. MsStoreProductID = "9P25B50XMKXT" + + // CertificatesDir is the agent's public subdirectory where the certificates are stored. + CertificatesDir = "certs" + + // GRPCServerNameOverride is the name to override the server name in when configuring TLS for local clients. + GRPCServerNameOverride = "UP4W" ) diff --git a/common/go.mod b/common/go.mod index 5ad683b9c..f010b5609 100644 --- a/common/go.mod +++ b/common/go.mod @@ -10,6 +10,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/snapcore/go-gettext v0.0.0-20201130093759-38740d1bd3d2 github.com/stretchr/testify v1.9.0 + github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c github.com/ubuntu/gowsl v0.0.0-20240313091109-66e05bce56e0 google.golang.org/grpc v1.63.2 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 @@ -22,7 +23,6 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.work.sum b/go.work.sum index 8157708f9..b5f8bcf76 100644 --- a/go.work.sum +++ b/go.work.sum @@ -11,11 +11,13 @@ cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEI cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnOJxbo/2mTI+Kgg68= cloud.google.com/go/accessapproval v1.7.4/go.mod h1:/aTEh45LzplQgFYdQdwPMR9YdX0UlhBmvB84uAmQKUc= +cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0= cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/accesscontextmanager v1.8.1/go.mod h1:JFJHfvuaTC+++1iL1coPiG1eu5D24db2wXCDWDjIrxo= cloud.google.com/go/accesscontextmanager v1.8.4/go.mod h1:ParU+WbMpD34s5JFEnGAnPBYAgUHozaTmDJU7aCU9+M= +cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q= cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= @@ -23,6 +25,7 @@ cloud.google.com/go/aiplatform v1.45.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4 cloud.google.com/go/aiplatform v1.48.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= cloud.google.com/go/aiplatform v1.52.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= @@ -30,62 +33,75 @@ cloud.google.com/go/analytics v0.21.2/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= cloud.google.com/go/apigateway v1.6.1/go.mod h1:ufAS3wpbRjqfZrzpvLC2oh0MFlpRJm2E/ts25yyqmXA= cloud.google.com/go/apigateway v1.6.4/go.mod h1:0EpJlVGH5HwAN4VF4Iec8TAzGN1aQgbxAWGJsnPCGGY= +cloud.google.com/go/apigateway v1.6.5/go.mod h1:6wCwvYRckRQogyDDltpANi3zsCDl6kWi0b4Je+w2UiI= cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= cloud.google.com/go/apigeeconnect v1.6.1/go.mod h1:C4awq7x0JpLtrlQCr8AzVIzAaYgngRqWf9S5Uhg+wWs= cloud.google.com/go/apigeeconnect v1.6.4/go.mod h1:CapQCWZ8TCjnU0d7PobxhpOdVz/OVJ2Hr/Zcuu1xFx0= +cloud.google.com/go/apigeeconnect v1.6.5/go.mod h1:MEKm3AiT7s11PqTfKE3KZluZA9O91FNysvd3E6SJ6Ow= cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= cloud.google.com/go/apigeeregistry v0.7.1/go.mod h1:1XgyjZye4Mqtw7T9TsY4NW10U7BojBvG4RMD+vRDrIw= cloud.google.com/go/apigeeregistry v0.8.2/go.mod h1:h4v11TDGdeXJDJvImtgK2AFVvMIgGWjSb0HRnBSjcX8= +cloud.google.com/go/apigeeregistry v0.8.3/go.mod h1:aInOWnqF4yMQx8kTjDqHNXjZGh/mxeNlAf52YqtASUs= cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/appengine v1.8.1/go.mod h1:6NJXGLVhZCN9aQ/AEDvmfzKEfoYBlfB80/BHiKVputY= cloud.google.com/go/appengine v1.8.4/go.mod h1:TZ24v+wXBujtkK77CXCpjZbnuTvsFNT41MUaZ28D6vg= +cloud.google.com/go/appengine v1.8.5/go.mod h1:uHBgNoGLTS5di7BvU25NFDuKa82v0qQLjyMJLuPQrVo= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/area120 v0.8.1/go.mod h1:BVfZpGpB7KFVNxPiQBuHkX6Ed0rS51xIgmGyjrAfzsg= cloud.google.com/go/area120 v0.8.4/go.mod h1:jfawXjxf29wyBXr48+W+GyX/f8fflxp642D/bb9v68M= +cloud.google.com/go/area120 v0.8.5/go.mod h1:BcoFCbDLZjsfe4EkCnEq1LKvHSK0Ew/zk5UFu6GMyA0= cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/artifactregistry v1.14.1/go.mod h1:nxVdG19jTaSTu7yA7+VbWL346r3rIdkZ142BSQqhn5E= cloud.google.com/go/artifactregistry v1.14.6/go.mod h1:np9LSFotNWHcjnOgh8UVK0RFPCTUGbO0ve3384xyHfE= +cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM= cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/asset v1.14.1/go.mod h1:4bEJ3dnHCqWCDbWJ/6Vn7GVI9LerSi7Rfdi03hd+WTQ= cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav6+7Q+c5QyJoL18Lry0= cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= +cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk= cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= cloud.google.com/go/automl v1.13.1/go.mod h1:1aowgAHWYZU27MybSCFiukPO7xnyawv7pt3zK4bheQE= cloud.google.com/go/automl v1.13.4/go.mod h1:ULqwX/OLZ4hBVfKQaMtxMSTlPx0GqGbWN8uA/1EqCP8= +cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y= cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= cloud.google.com/go/baremetalsolution v1.1.1/go.mod h1:D1AV6xwOksJMV4OSlWHtWuFNZZYujJknMAP4Qa27QIA= cloud.google.com/go/baremetalsolution v1.2.3/go.mod h1:/UAQ5xG3faDdy180rCUv47e0jvpp3BFxT+Cl0PFjw5g= +cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY= cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= cloud.google.com/go/batch v1.3.1/go.mod h1:VguXeQKXIYaeeIYbuozUmBR13AfL4SJP7IltNPS+A4A= cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/beyondcorp v0.6.1/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= cloud.google.com/go/beyondcorp v1.0.3/go.mod h1:HcBvnEd7eYr+HGDd5ZbuVmBYX019C6CEXBonXbCVwJo= +cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc= cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= @@ -93,27 +109,32 @@ cloud.google.com/go/bigquery v1.52.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6Pm cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= +cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc= cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/billing v1.16.0/go.mod h1:y8vx09JSSJG02k5QxbycNRrN7FGZB6F3CAcgum7jvGA= cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE= cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= cloud.google.com/go/binaryauthorization v1.6.1/go.mod h1:TKt4pa8xhowwffiBmbrbcxijJRZED4zrqnwZ1lKH51U= cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8IgoofDFRp5UMzaNk1UqI= cloud.google.com/go/certificatemanager v1.7.4/go.mod h1:FHAylPe/6IIKuaRmHbjbdLhGhVQ+CWHSD5Jq0k4+cCE= +cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM= cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= cloud.google.com/go/channel v1.16.0/go.mod h1:eN/q1PFSl5gyu0dYdmxNXscY/4Fi7ABmeHCJNf/oHmc= cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= @@ -121,16 +142,19 @@ cloud.google.com/go/cloudbuild v1.10.1/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2 cloud.google.com/go/cloudbuild v1.13.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= +cloud.google.com/go/cloudbuild v1.15.1/go.mod h1:gIofXZSu+XD2Uy+qkOrGKEx45zd7s28u/k8f99qKals= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/clouddms v1.6.1/go.mod h1:Ygo1vL52Ov4TBZQquhz5fiw2CQ58gvu+PlS6PVXCpZI= cloud.google.com/go/clouddms v1.7.3/go.mod h1:fkN2HQQNUYInAU3NQ3vRLkV2iWs8lIdmBKOx4nrL6Hc= +cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFotwdE79DieY= cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= cloud.google.com/go/cloudtasks v1.11.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= cloud.google.com/go/cloudtasks v1.12.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0= +cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= @@ -144,6 +168,7 @@ cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= @@ -153,6 +178,7 @@ cloud.google.com/go/contactcenterinsights v1.9.1/go.mod h1:bsg/R7zGLYMVxFFzfh9oo cloud.google.com/go/contactcenterinsights v1.10.0/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= @@ -160,11 +186,13 @@ cloud.google.com/go/container v1.22.2/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq cloud.google.com/go/container v1.24.0/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/containeranalysis v0.10.1/go.mod h1:Ya2jiILITMY68ZLPaogjmOMNkwsDrWBSTyBubGXO7j0= cloud.google.com/go/containeranalysis v0.11.3/go.mod h1:kMeST7yWFQMGjiG9K7Eov+fPNQcGhb8mXj/UcTiWw9U= +cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE= cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= @@ -172,23 +200,28 @@ cloud.google.com/go/datacatalog v1.14.1/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQ cloud.google.com/go/datacatalog v1.16.0/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= +cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataflow v0.9.1/go.mod h1:Wp7s32QjYuQDWqJPFFlnBKhkAtiFpMTdg00qGbnIHVw= cloud.google.com/go/dataflow v0.9.4/go.mod h1:4G8vAkHYCSzU8b/kmsoR2lWyHJD85oMJPHMtan40K8w= +cloud.google.com/go/dataflow v0.9.5/go.mod h1:udl6oi8pfUHnL0z6UN9Lf9chGqzDMVqcYTcZ1aPnCZQ= cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= cloud.google.com/go/dataform v0.8.1/go.mod h1:3BhPSiw8xmppbgzeBbmDvmSWlwouuJkXsXsb8UBih9M= cloud.google.com/go/dataform v0.9.1/go.mod h1:pWTg+zGQ7i16pyn0bS1ruqIE91SdL2FDMvEYu/8oQxs= +cloud.google.com/go/dataform v0.9.2/go.mod h1:S8cQUwPNWXo7m/g3DhWHsLBoufRNn9EgFrMgne2j7cI= cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datafusion v1.7.1/go.mod h1:KpoTBbFmoToDExJUso/fcCiguGDk7MEzOWXUsJo0wsI= cloud.google.com/go/datafusion v1.7.4/go.mod h1:BBs78WTOLYkT4GVZIXQCZT3GFpkpDN4aBY4NDX/jVlM= +cloud.google.com/go/datafusion v1.7.5/go.mod h1:bYH53Oa5UiqahfbNK9YuYKteeD4RbQSNMx7JF7peGHc= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= cloud.google.com/go/datalabeling v0.8.1/go.mod h1:XS62LBSVPbYR54GfYQsPXZjTW8UxCK2fkDciSrpRFdY= cloud.google.com/go/datalabeling v0.8.4/go.mod h1:Z1z3E6LHtffBGrNUkKwbwbDxTiXEApLzIgmymj8A3S8= +cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s= cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= @@ -196,15 +229,18 @@ cloud.google.com/go/dataplex v1.8.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MP cloud.google.com/go/dataplex v1.9.0/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= cloud.google.com/go/dataplex v1.11.1/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= cloud.google.com/go/dataproc/v2 v2.0.1/go.mod h1:7Ez3KRHdFGcfY7GcevBbvozX+zyWGcwLJvvAMwCaoZ4= cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= +cloud.google.com/go/dataproc/v2 v2.4.0/go.mod h1:3B1Ht2aRB8VZIteGxQS/iNSJGzt9+CA0WGnDVMEm7Z4= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/dataqna v0.8.1/go.mod h1:zxZM0Bl6liMePWsHA8RMGAfmTG34vJMapbHAxQ5+WA8= cloud.google.com/go/dataqna v0.8.4/go.mod h1:mySRKjKg5Lz784P6sCov3p1QD+RZQONRMRjzGNcFd0c= +cloud.google.com/go/dataqna v0.8.5/go.mod h1:vgihg1mz6n7pb5q2YJF7KlXve6tCglInd6XO0JGOlWM= cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastore v1.12.1/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= @@ -216,6 +252,7 @@ cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5 cloud.google.com/go/datastream v1.9.1/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= cloud.google.com/go/datastream v1.10.0/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= cloud.google.com/go/datastream v1.10.3/go.mod h1:YR0USzgjhqA/Id0Ycu1VvZe8hEWwrkjuXrGbzeDOSEA= +cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo= cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= @@ -223,6 +260,7 @@ cloud.google.com/go/deploy v1.11.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCN cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= +cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50= cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= @@ -230,10 +268,12 @@ cloud.google.com/go/dialogflow v1.38.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4 cloud.google.com/go/dialogflow v1.40.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= +cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42VT5KOI= cloud.google.com/go/dlp v1.11.1/go.mod h1:/PA2EnioBeXTL/0hInwgj0rfsQb3lpE3R8XUJxqUNKI= +cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w= cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= @@ -241,32 +281,38 @@ cloud.google.com/go/documentai v1.20.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe cloud.google.com/go/documentai v1.22.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66KaCSDYfE= cloud.google.com/go/domains v0.9.4/go.mod h1:27jmJGShuXYdUNjyDG0SodTfT5RwLi7xmH334Gvi3fY= +cloud.google.com/go/domains v0.9.5/go.mod h1:dBzlxgepazdFhvG7u23XMhmMKBjrkoUNaw0A8AQB55Y= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= cloud.google.com/go/edgecontainer v1.1.1/go.mod h1:O5bYcS//7MELQZs3+7mabRqoWQhXCzenBu0R8bz2rwk= cloud.google.com/go/edgecontainer v1.1.4/go.mod h1:AvFdVuZuVGdgaE5YvlL1faAoa1ndRR/5XhXZvPBHbsE= +cloud.google.com/go/edgecontainer v1.1.5/go.mod h1:rgcjrba3DEDEQAidT4yuzaKWTbkTI5zAMu3yy6ZWS0M= cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= cloud.google.com/go/essentialcontacts v1.6.2/go.mod h1:T2tB6tX+TRak7i88Fb2N9Ok3PvY3UNbUsMag9/BARh4= cloud.google.com/go/essentialcontacts v1.6.5/go.mod h1:jjYbPzw0x+yglXC890l6ECJWdYeZ5dlYACTFL0U/VuM= +cloud.google.com/go/essentialcontacts v1.6.6/go.mod h1:XbqHJGaiH0v2UvtuucfOzFXN+rpL/aU5BCZLn4DYl1Q= cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= cloud.google.com/go/eventarc v1.12.1/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= cloud.google.com/go/eventarc v1.13.0/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= cloud.google.com/go/eventarc v1.13.3/go.mod h1:RWH10IAZIRcj1s/vClXkBgMHwh59ts7hSWcqD3kaclg= +cloud.google.com/go/eventarc v1.13.4/go.mod h1:zV5sFVoAa9orc/52Q+OuYUG9xL2IIZTbbuTHC6JSY8s= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp8oN7i7FjV4= cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/filestore v1.8.1/go.mod h1:MbN9KcaM47DRTIuLfQhJEsjaocVebNtNQhSLhKCF5GM= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= @@ -277,32 +323,38 @@ cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1Yb cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/functions v1.15.1/go.mod h1:P5yNWUTkyU+LvW/S9O6V+V423VZooALQlqoXdoPz5AE= cloud.google.com/go/functions v1.15.4/go.mod h1:CAsTc3VlRMVvx+XqXxKqVevguqJpnVip4DdonFsX28I= +cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k= cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= cloud.google.com/go/gkebackup v1.3.0/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= cloud.google.com/go/gkebackup v1.3.4/go.mod h1:gLVlbM8h/nHIs09ns1qx3q3eaXcGSELgNu1DWXYz1HI= +cloud.google.com/go/gkebackup v1.3.5/go.mod h1:KJ77KkNN7Wm1LdMopOelV6OodM01pMuK2/5Zt1t4Tvc= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkeconnect v0.8.1/go.mod h1:KWiK1g9sDLZqhxB2xEuPV8V9NYzrqTUmQR9shJHpOZw= cloud.google.com/go/gkeconnect v0.8.4/go.mod h1:84hZz4UMlDCKl8ifVW8layK4WHlMAFeq8vbzjU0yJkw= +cloud.google.com/go/gkeconnect v0.8.5/go.mod h1:LC/rS7+CuJ5fgIbXv8tCD/mdfnlAadTaUufgOkmijuk= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= cloud.google.com/go/gkehub v0.14.1/go.mod h1:VEXKIJZ2avzrbd7u+zeMtW00Y8ddk/4V9511C9CQGTY= cloud.google.com/go/gkehub v0.14.4/go.mod h1:Xispfu2MqnnFt8rV/2/3o73SK1snL8s9dYJ9G2oQMfc= +cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA= cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/gkemulticloud v0.6.1/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= cloud.google.com/go/gsuiteaddons v1.6.1/go.mod h1:CodrdOqRZcLp5WOwejHWYBjZvfY0kOphkAKpF/3qdZY= cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs7rF4U29KHM/pE= +cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= @@ -310,34 +362,41 @@ cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCta cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= cloud.google.com/go/iap v1.8.1/go.mod h1:sJCbeqg3mvWLqjZNsI6dfAtbbV1DL2Rl7e1mTyXYREQ= cloud.google.com/go/iap v1.9.3/go.mod h1:DTdutSZBqkkOm2HEOTBzhZxh2mwwxshfD/h3yofAiCw= +cloud.google.com/go/iap v1.9.4/go.mod h1:vO4mSq0xNf/Pu6E5paORLASBwEmphXEjgCFg7aeNu1w= cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= cloud.google.com/go/ids v1.4.1/go.mod h1:np41ed8YMU8zOgv53MMMoCntLTn2lF+SUzlM+O3u/jw= cloud.google.com/go/ids v1.4.4/go.mod h1:z+WUc2eEl6S/1aZWzwtVNWoSZslgzPxAboS0lZX0HjI= +cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1UuBo= cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= cloud.google.com/go/iot v1.7.1/go.mod h1:46Mgw7ev1k9KqK1ao0ayW9h0lI+3hxeanz+L1zmbbbk= cloud.google.com/go/iot v1.7.4/go.mod h1:3TWqDVvsddYBG++nHSZmluoCAVGr1hAcabbWZNKEZLk= +cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= +cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/language v1.10.1/go.mod h1:CPp94nsdVNiQEt1CNjF5WkTcisLiHPyIbMhvR8H2AW0= cloud.google.com/go/language v1.12.2/go.mod h1:9idWapzr/JKXBBQ4lWqVX/hcadxB194ry20m/bTrhWc= +cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= cloud.google.com/go/lifesciences v0.9.1/go.mod h1:hACAOd1fFbCGLr/+weUKRAJas82Y4vrL3O5326N//Wc= cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqNj+Nia7hF0Z7JA= +cloud.google.com/go/lifesciences v0.9.5/go.mod h1:OdBm0n7C0Osh5yZB7j9BXyrMnTRGBJIZonUMxo5CzPw= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= @@ -348,97 +407,117 @@ cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPk cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= cloud.google.com/go/managedidentities v1.6.1/go.mod h1:h/irGhTN2SkZ64F43tfGPMbHnypMbu4RB3yl8YcuEak= cloud.google.com/go/managedidentities v1.6.4/go.mod h1:WgyaECfHmF00t/1Uk8Oun3CQ2PGUtjc3e9Alh79wyiM= +cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI= cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= +cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ1C1vkapubj0T2aGig= cloud.google.com/go/mediatranslation v0.8.4/go.mod h1:9WstgtNVAdN53m6TQa5GjIjLqKQPXe74hwSCxUP6nj4= +cloud.google.com/go/mediatranslation v0.8.5/go.mod h1:y7kTHYIPCIfgyLbKncgqouXJtLsU+26hZhHEEy80fSs= cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/memcache v1.10.1/go.mod h1:47YRQIarv4I3QS5+hoETgKO40InqzLP6kpNLvyXuyaA= cloud.google.com/go/memcache v1.10.4/go.mod h1:v/d8PuC8d1gD6Yn5+I3INzLR01IDn0N4Ym56RgikSI0= +cloud.google.com/go/memcache v1.10.5/go.mod h1:/FcblbNd0FdMsx4natdj+2GWzTq+cjZvMa1I+9QsuMA= cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= cloud.google.com/go/metastore v1.11.1/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= cloud.google.com/go/metastore v1.12.0/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= cloud.google.com/go/metastore v1.13.3/go.mod h1:K+wdjXdtkdk7AQg4+sXS8bRrQa9gcOr+foOMF2tqINE= +cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/monitoring v1.15.1/go.mod h1:lADlSAlFdbqQuwwpaImhsJXu1QSdd3ojypXrFSMr2rM= cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= cloud.google.com/go/networkconnectivity v1.12.1/go.mod h1:PelxSWYM7Sh9/guf8CFhi6vIqf19Ir/sbfZRUwXh92E= cloud.google.com/go/networkconnectivity v1.14.3/go.mod h1:4aoeFdrJpYEXNvrnfyD5kIzs8YtHg945Og4koAjHQek= +cloud.google.com/go/networkconnectivity v1.14.4/go.mod h1:PU12q++/IMnDJAB+3r+tJtuCXCfwfN+C6Niyj6ji1Po= cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networkmanagement v1.8.0/go.mod h1:Ho/BUGmtyEqrttTgWEe7m+8vDdK74ibQc+Be0q7Fof0= cloud.google.com/go/networkmanagement v1.9.3/go.mod h1:y7WMO1bRLaP5h3Obm4tey+NquUvB93Co1oh4wpL+XcU= +cloud.google.com/go/networkmanagement v1.9.4/go.mod h1:daWJAl0KTFytFL7ar33I6R/oNBH8eEOX/rBNHrC/8TA= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/networksecurity v0.9.1/go.mod h1:MCMdxOKQ30wsBI1eI659f9kEp4wuuAueoC9AJKSPWZQ= cloud.google.com/go/networksecurity v0.9.4/go.mod h1:E9CeMZ2zDsNBkr8axKSYm8XyTqNhiCHf1JO/Vb8mD1w= +cloud.google.com/go/networksecurity v0.9.5/go.mod h1:KNkjH/RsylSGyyZ8wXpue8xpCEK+bTtvof8SBfIhMG8= cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= cloud.google.com/go/notebooks v1.9.1/go.mod h1:zqG9/gk05JrzgBt4ghLzEepPHNwE5jgPcHZRKhlC1A8= cloud.google.com/go/notebooks v1.11.2/go.mod h1:z0tlHI/lREXC8BS2mIsUeR3agM1AkgLiS+Isov3SS70= +cloud.google.com/go/notebooks v1.11.3/go.mod h1:0wQyI2dQC3AZyQqWnRsp+yA+kY4gC7ZIVP4Qg3AQcgo= cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= cloud.google.com/go/optimization v1.4.1/go.mod h1:j64vZQP7h9bO49m2rVaTVoNM0vEBEN5eKPUPbZyXOrk= cloud.google.com/go/optimization v1.6.2/go.mod h1:mWNZ7B9/EyMCcwNl1frUGEuY6CPijSkz88Fz2vwKPOY= +cloud.google.com/go/optimization v1.6.3/go.mod h1:8ve3svp3W6NFcAEFr4SfJxrldzhUl4VMUJmhrqVKtYA= cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= cloud.google.com/go/orchestration v1.8.1/go.mod h1:4sluRF3wgbYVRqz7zJ1/EUNc90TTprliq9477fGobD8= cloud.google.com/go/orchestration v1.8.4/go.mod h1:d0lywZSVYtIoSZXb0iFjv9SaL13PGyVOKDxqGxEf/qI= +cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8= cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK755wKhIIUCE= cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I= cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/osconfig v1.12.1/go.mod h1:4CjBxND0gswz2gfYRCUoUzCm9zCABp91EeTtWXyz0tE= cloud.google.com/go/osconfig v1.12.4/go.mod h1:B1qEwJ/jzqSRslvdOCI8Kdnp0gSng0xW4LOnIebQomA= +cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8= cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/oslogin v1.10.1/go.mod h1:x692z7yAue5nE7CsSnoG0aaMbNoRJRXO4sn73R+ZqAs= cloud.google.com/go/oslogin v1.12.2/go.mod h1:CQ3V8Jvw4Qo4WRhNPF0o+HAM4DiLuE27Ul9CX9g2QdY= cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= +cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= cloud.google.com/go/phishingprotection v0.8.1/go.mod h1:AxonW7GovcA8qdEk13NfHq9hNx5KPtfxXNeUxTDxB6I= cloud.google.com/go/phishingprotection v0.8.4/go.mod h1:6b3kNPAc2AQ6jZfFHioZKg9MQNybDg4ixFd4RPZZ2nE= +cloud.google.com/go/phishingprotection v0.8.5/go.mod h1:g1smd68F7mF1hgQPuYn3z8HDbNre8L6Z0b7XMYFmX7I= cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/policytroubleshooter v1.7.1/go.mod h1:0NaT5v3Ag1M7U5r0GfDCpUFkWd9YqpubBWsQlhanRv0= cloud.google.com/go/policytroubleshooter v1.8.0/go.mod h1:tmn5Ir5EToWe384EuboTcVQT7nTag2+DuH3uHmKd1HU= cloud.google.com/go/policytroubleshooter v1.10.2/go.mod h1:m4uF3f6LseVEnMV6nknlN2vYGRb+75ylQwJdnOXfnv0= +cloud.google.com/go/policytroubleshooter v1.10.3/go.mod h1:+ZqG3agHT7WPb4EBIRqUv4OyIwRTZvsVDHZ8GlZaoxk= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/privatecatalog v0.9.1/go.mod h1:0XlDXW2unJXdf9zFz968Hp35gl/bhF4twwpXZAW50JA= cloud.google.com/go/privatecatalog v0.9.4/go.mod h1:SOjm93f+5hp/U3PqMZAHTtBtluqLygrDrVO8X8tYtG0= +cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk= cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= +cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -449,58 +528,70 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdD cloud.google.com/go/recaptchaenterprise/v2 v2.7.2/go.mod h1:kR0KjsJS7Jt1YSyWFkseQ756D45kaYNTlDPPaRAvDBU= cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcNz3gLizXVIDI/o3G1DLcrE= cloud.google.com/go/recommendationengine v0.8.4/go.mod h1:GEteCf1PATl5v5ZsQ60sTClUE0phbWmo3rQ1Js8louU= +cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ= cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/recommender v1.10.1/go.mod h1:XFvrE4Suqn5Cq0Lf+mCP6oBHD/yRMA8XxP5sb7Q7gpA= cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0= cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= cloud.google.com/go/redis v1.13.1/go.mod h1:VP7DGLpE91M6bcsDdMuyCm2hIpB6Vp2hI090Mfd1tcg= cloud.google.com/go/redis v1.14.1/go.mod h1:MbmBxN8bEnQI4doZPC1BzADU4HGocHBk2de3SbgOkqs= +cloud.google.com/go/redis v1.14.2/go.mod h1:g0Lu7RRRz46ENdFKQ2EcQZBAJ2PtJHJLuiiRuEXwyQw= cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8= cloud.google.com/go/resourcemanager v1.9.4/go.mod h1:N1dhP9RFvo3lUfwtfLWVxfUWq8+KUQ+XLlHLH3BoFJ0= +cloud.google.com/go/resourcemanager v1.9.5/go.mod h1:hep6KjelHA+ToEjOfO3garMKi/CLYwTqeAw7YiEI9x8= cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/resourcesettings v1.6.1/go.mod h1:M7mk9PIZrC5Fgsu1kZJci6mpgN8o0IUzVx3eJU3y4Jw= cloud.google.com/go/resourcesettings v1.6.4/go.mod h1:pYTTkWdv2lmQcjsthbZLNBP4QW140cs7wqA3DuqErVI= +cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I= cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= cloud.google.com/go/retail v1.14.1/go.mod h1:y3Wv3Vr2k54dLNIrCzenyKG8g8dhvhncT2NcNjb/6gE= cloud.google.com/go/retail v1.14.4/go.mod h1:l/N7cMtY78yRnJqp5JW8emy7MB1nz8E4t2yfOmklYfg= +cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE= cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/run v1.2.0/go.mod h1:36V1IlDzQ0XxbQjUx6IYbw8H3TJnWvhii963WW3B/bo= cloud.google.com/go/run v1.3.3/go.mod h1:WSM5pGyJ7cfYyYbONVQBN4buz42zFqwG67Q3ch07iK4= +cloud.google.com/go/run v1.3.4/go.mod h1:FGieuZvQ3tj1e9GnzXqrMABSuir38AJg5xhiYq+SF3o= cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhObZ54PxgR2Oo= cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= +cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/secretmanager v1.11.1/go.mod h1:znq9JlXgTNdBeQk9TBW/FnR/W4uChEKGeqQWAJ8SXFw= cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= +cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4= cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4AUBj76h9fXA= cloud.google.com/go/security v1.15.4/go.mod h1:oN7C2uIZKhxCLiAAijKUCuHLZbIt/ghYEo8MqwD/Ty4= +cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc= cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY172h4S8aXyI9/hsQ= cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= @@ -509,6 +600,7 @@ cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8v cloud.google.com/go/servicedirectory v1.10.1/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= cloud.google.com/go/servicedirectory v1.11.3/go.mod h1:LV+cHkomRLr67YoQy3Xq2tUXBGOs5z5bPofdq7qtiAw= +cloud.google.com/go/servicedirectory v1.11.4/go.mod h1:Bz2T9t+/Ehg6x+Y7Ycq5xiShYLD96NfEsWNHyitj1qM= cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= @@ -517,12 +609,14 @@ cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZg cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= cloud.google.com/go/shell v1.7.1/go.mod h1:u1RaM+huXFaTojTbW4g9P5emOrrmLE69KrxqQahKn4g= cloud.google.com/go/shell v1.7.4/go.mod h1:yLeXB8eKLxw0dpEmXQ/FjriYrBijNsONpwnWsdPqlKM= +cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE= cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSqW4uH9lfUI= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= +cloud.google.com/go/spanner v1.57.0/go.mod h1:aXQ5QDdhPRIqVhYmnkAdwPYvj/DRN0FguclhEWw+jOo= cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= @@ -530,6 +624,7 @@ cloud.google.com/go/speech v1.17.1/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= +cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= @@ -541,23 +636,28 @@ cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdl cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/storagetransfer v1.10.0/go.mod h1:DM4sTlSmGiNczmV6iZyceIh2dbs+7z2Ayg6YAiQlYfA= cloud.google.com/go/storagetransfer v1.10.3/go.mod h1:Up8LY2p6X68SZ+WToswpQbQHnJpOty/ACcMafuey8gc= +cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs= cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= cloud.google.com/go/talent v1.6.2/go.mod h1:CbGvmKCG61mkdjcqTcLOkb2ZN1SrQI8MDyma2l7VD24= cloud.google.com/go/talent v1.6.5/go.mod h1:Mf5cma696HmE+P2BWJ/ZwYqeJXEeU0UqjHFXVLadEDI= +cloud.google.com/go/talent v1.6.6/go.mod h1:y/WQDKrhVz12WagoarpAIyKKMeKGKHWPoReZ0g8tseQ= cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= cloud.google.com/go/texttospeech v1.7.1/go.mod h1:m7QfG5IXxeneGqTapXNxv2ItxP/FS0hCZBwXYqucgSk= cloud.google.com/go/texttospeech v1.7.4/go.mod h1:vgv0002WvR4liGuSd5BJbWy4nDn5Ozco0uJymY5+U74= +cloud.google.com/go/texttospeech v1.7.5/go.mod h1:tzpCuNWPwrNJnEa4Pu5taALuZL4QRRLcb+K9pbhXT6M= cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= cloud.google.com/go/tpu v1.6.1/go.mod h1:sOdcHVIgDEEOKuqUoi6Fq53MKHJAtOwtz0GuKsWSH3E= cloud.google.com/go/tpu v1.6.4/go.mod h1:NAm9q3Rq2wIlGnOhpYICNI7+bpBebMJbh0yyp3aNw1Y= +cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXdOPAQTYk= cloud.google.com/go/trace v1.10.4/go.mod h1:Nso99EDIK8Mj5/zmB+iGr9dosS/bzWCJ8wGmE6TXNWY= +cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= @@ -565,48 +665,58 @@ cloud.google.com/go/translate v1.8.1/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNW cloud.google.com/go/translate v1.8.2/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/video v1.17.1/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= cloud.google.com/go/video v1.19.0/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= cloud.google.com/go/video v1.20.3/go.mod h1:TnH/mNZKVHeNtpamsSPygSR0iHtvrR/cW1/GDjN5+GU= +cloud.google.com/go/video v1.20.4/go.mod h1:LyUVjyW+Bwj7dh3UJnUGZfyqjEto9DnrvTe1f/+QrW0= cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/videointelligence v1.11.1/go.mod h1:76xn/8InyQHarjTWsBR058SmlPCwQjgcvoW0aZykOvo= cloud.google.com/go/videointelligence v1.11.4/go.mod h1:kPBMAYsTPFiQxMLmmjpcZUMklJp3nC9+ipJJtprccD8= +cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I= cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= cloud.google.com/go/vision/v2 v2.7.2/go.mod h1:jKa8oSYBWhYiXarHPvP4USxYANYUEdEsQrloLjrSwJU= cloud.google.com/go/vision/v2 v2.7.5/go.mod h1:GcviprJLFfK9OLf0z8Gm6lQb6ZFUulvpZws+mm6yPLM= +cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU= cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= cloud.google.com/go/vmmigration v1.7.1/go.mod h1:WD+5z7a/IpZ5bKK//YmT9E047AD+rjycCAvyMxGJbro= cloud.google.com/go/vmmigration v1.7.4/go.mod h1:yBXCmiLaB99hEl/G9ZooNx2GyzgsjKnw5fWcINRgD70= +cloud.google.com/go/vmmigration v1.7.5/go.mod h1:pkvO6huVnVWzkFioxSghZxIGcsstDvYiVCxQ9ZH3eYI= cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= cloud.google.com/go/vmwareengine v0.4.1/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= cloud.google.com/go/vmwareengine v1.0.0/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= cloud.google.com/go/vmwareengine v1.0.3/go.mod h1:QSpdZ1stlbfKtyt6Iu19M6XRxjmXO+vb5a/R6Fvy2y4= +cloud.google.com/go/vmwareengine v1.1.1/go.mod h1:nMpdsIVkUrSaX8UvmnBhzVzG7PPvNYc5BszcvIVudYs= cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/vpcaccess v1.7.1/go.mod h1:FogoD46/ZU+JUBX9D606X21EnxiszYi2tArQwLY4SXs= cloud.google.com/go/vpcaccess v1.7.4/go.mod h1:lA0KTvhtEOb/VOdnH/gwPuOzGgM+CWsmGu6bb4IoMKk= +cloud.google.com/go/vpcaccess v1.7.5/go.mod h1:slc5ZRvvjP78c2dnL7m4l4R9GwL3wDLcpIWz6P/ziig= cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= cloud.google.com/go/webrisk v1.9.1/go.mod h1:4GCmXKcOa2BZcZPn6DCEvE7HypmEJcJkr4mtM+sqYPc= cloud.google.com/go/webrisk v1.9.4/go.mod h1:w7m4Ib4C+OseSr2GL66m0zMBywdrVNTDKsdEsfMl7X0= +cloud.google.com/go/webrisk v1.9.5/go.mod h1:aako0Fzep1Q714cPEM5E+mtYX8/jsfegAuS8aivxy3U= cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv5/rGpFCsBOvPbYgszpg= cloud.google.com/go/websecurityscanner v1.6.4/go.mod h1:mUiyMQ+dGpPPRkHgknIZeCzSHJ45+fY4F52nZFDHm2o= +cloud.google.com/go/websecurityscanner v1.6.5/go.mod h1:QR+DWaxAz2pWooylsBF854/Ijvuoa3FCyS1zBa1rAVQ= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= +cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -675,6 +785,7 @@ github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -891,8 +1002,7 @@ golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= @@ -907,6 +1017,7 @@ golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -1023,6 +1134,9 @@ google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -1037,6 +1151,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go. google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:qDbnxtViX5J6CvFbxeNUSzKgVlDLJ/6L+caxye9+Flo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f/go.mod h1:iIgEblxoG4klcXsG0d9cpoxJ4xndv6+1FkDROCHhPRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= @@ -1050,6 +1166,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -1068,6 +1185,7 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go new file mode 100644 index 000000000..cc26ecb2b --- /dev/null +++ b/windows-agent/internal/proservices/certs.go @@ -0,0 +1,75 @@ +// Package proservices is in charge of managing the GRPC services and all business-logic side. +package proservices + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "fmt" + "path/filepath" + + "github.com/canonical/ubuntu-pro-for-wsl/common" + "github.com/ubuntu/decorate" +) + +type Certs struct { + RootCA *x509.Certificate + ServerCert tls.Certificate +} + +func (c Certs) ServerTLSConfig() *tls.Config { + ca := x509.NewCertPool() + ca.AddCert(c.RootCA) + return &tls.Config{ + Certificates: []tls.Certificate{c.ServerCert}, + ClientCAs: ca, + ClientAuth: tls.RequireAndVerifyClientCert, + } +} + +// NewTLSCertificates creates a root CA and a server self-signed certificates and writes them into destDir. +func NewTLSCertificates(destDir string) (certs Certs, err error) { + decorate.OnError(&err, "could not create TLS credentials:") + + // generate a new key-pair for the root certificate based on the P256 elliptic curve + rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return Certs{}, fmt.Errorf("failed to generate random key: %v", err) + } + + rootCertTmpl, err := common.CertTemplate(common.GRPCServerNameOverride) + if err != nil { + return Certs{}, fmt.Errorf("failed to create root cert template: %v", err) + } + + // this cert will be the CA that we will use to sign the server cert + rootCertTmpl.IsCA = true + rootCertTmpl.Subject.CommonName = "UP4W CA" + rootCertTmpl.KeyUsage = x509.KeyUsageCertSign + + rootCert, rootDER, err := common.CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) + if err != nil { + return Certs{}, err + } + + // Write the CA certificate to disk. + if err = common.WriteCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { + return Certs{}, err + } + + // Create and write the server and client certificates signed by the root certificate created above. + serverCert, err := common.CreateRootSignedTLSCertificate("server", common.GRPCServerNameOverride, rootCert, rootKey, destDir) + if err != nil { + return Certs{}, err + } + // We won't store the TLS client certificate, because only the agent should acess this function and it's not interested in the client TLS certificate. + // But we still need to write them to disk, so clients can construct their TLS configs from there. + _, err = common.CreateRootSignedTLSCertificate("client", common.GRPCServerNameOverride, rootCert, rootKey, destDir) + if err != nil { + return Certs{}, err + } + + return Certs{RootCA: rootCert, ServerCert: *serverCert}, nil +} diff --git a/windows-agent/internal/proservices/certs_test.go b/windows-agent/internal/proservices/certs_test.go new file mode 100644 index 000000000..bfa680de5 --- /dev/null +++ b/windows-agent/internal/proservices/certs_test.go @@ -0,0 +1,47 @@ +package proservices_test + +import ( + "os" + + "path/filepath" + "testing" + + ps "github.com/canonical/ubuntu-pro-for-wsl/windows-agent/internal/proservices" + "github.com/stretchr/testify/require" +) + +func TestNewTLSCertificates(t *testing.T) { + + testcases := map[string]struct { + breakDestDir bool + breakServer bool + + wantErr bool + }{ + "Success": {}, + "Error when the destination directory cannot be written into": {breakDestDir: true, wantErr: true}, + "Error when the server private key cannot be written": {breakServer: true, wantErr: true}, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + if tc.breakDestDir { + dir = filepath.Join(dir, "inexistent") + } + + if tc.breakServer { + err := os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700) + require.NoError(t, err, "Setup: could not write directory that should break writing the server key") + } + + _, err := ps.NewTLSCertificates(dir) + if tc.wantErr { + require.Error(t, err, "NewTLSCertificates should have failed") + return + } + require.NoError(t, err, "NewTLSCertificates failed") + }) + } +} From fdfcb03c668c6c5bfb012c0843e216b116b4a8ad Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 11 Apr 2024 23:38:15 -0300 Subject: [PATCH 02/41] Agent: Uses the certificate feat in proservices gRPC over TLS with mutual authentication. Clients must read their certificates from disk as demonstrated in the TestRegisterGRPCServices test. The server (agent) has facilitated access to the TLS certificates in memory because it is the creator of the certificates. New certificates will be unconditionally generated at agent startup. Insecure connections are no longer supported. Testing locally succeeded with the caveat of overriding the server name in the client configuration. Corporate environments may be subject to additional restrictions about signed certificates by untrusted CAs. --- windows-agent/internal/proservices/certs.go | 37 ++--- .../internal/proservices/certs_test.go | 14 +- .../internal/proservices/proservices.go | 21 ++- .../internal/proservices/proservices_test.go | 135 ++++++++++++++++-- 4 files changed, 158 insertions(+), 49 deletions(-) diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index cc26ecb2b..d818c1f8f 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -2,23 +2,24 @@ package proservices import ( - "crypto/ecdsa" - "crypto/elliptic" "crypto/rand" "crypto/tls" "crypto/x509" "fmt" - "path/filepath" + "math/big" "github.com/canonical/ubuntu-pro-for-wsl/common" + "github.com/canonical/ubuntu-pro-for-wsl/common/certs" "github.com/ubuntu/decorate" ) +// Certs conveniently holds the root CA and server certificates to make it easy to create a TLS config. type Certs struct { RootCA *x509.Certificate ServerCert tls.Certificate } +// ServerTLSConfig returns a TLS config for servers that require and verify client certificates. func (c Certs) ServerTLSConfig() *tls.Config { ca := x509.NewCertPool() ca.AddCert(c.RootCA) @@ -26,47 +27,33 @@ func (c Certs) ServerTLSConfig() *tls.Config { Certificates: []tls.Certificate{c.ServerCert}, ClientCAs: ca, ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, } } // NewTLSCertificates creates a root CA and a server self-signed certificates and writes them into destDir. -func NewTLSCertificates(destDir string) (certs Certs, err error) { +func NewTLSCertificates(destDir string) (c Certs, err error) { decorate.OnError(&err, "could not create TLS credentials:") - // generate a new key-pair for the root certificate based on the P256 elliptic curve - rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + // generates a pseudo-random serial number for the root CA certificate. + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { - return Certs{}, fmt.Errorf("failed to generate random key: %v", err) + return Certs{}, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) } - rootCertTmpl, err := common.CertTemplate(common.GRPCServerNameOverride) + rootCert, rootKey, err := certs.CreateRootCA(common.GRPCServerNameOverride, serial, destDir) if err != nil { - return Certs{}, fmt.Errorf("failed to create root cert template: %v", err) - } - - // this cert will be the CA that we will use to sign the server cert - rootCertTmpl.IsCA = true - rootCertTmpl.Subject.CommonName = "UP4W CA" - rootCertTmpl.KeyUsage = x509.KeyUsageCertSign - - rootCert, rootDER, err := common.CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) - if err != nil { - return Certs{}, err - } - - // Write the CA certificate to disk. - if err = common.WriteCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { return Certs{}, err } // Create and write the server and client certificates signed by the root certificate created above. - serverCert, err := common.CreateRootSignedTLSCertificate("server", common.GRPCServerNameOverride, rootCert, rootKey, destDir) + serverCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) if err != nil { return Certs{}, err } // We won't store the TLS client certificate, because only the agent should acess this function and it's not interested in the client TLS certificate. // But we still need to write them to disk, so clients can construct their TLS configs from there. - _, err = common.CreateRootSignedTLSCertificate("client", common.GRPCServerNameOverride, rootCert, rootKey, destDir) + _, err = certs.CreateTLSCertificateSignedBy("client", common.GRPCServerNameOverride, serial.Lsh(serial, 3), rootCert, rootKey, destDir) if err != nil { return Certs{}, err } diff --git a/windows-agent/internal/proservices/certs_test.go b/windows-agent/internal/proservices/certs_test.go index bfa680de5..006c6f1eb 100644 --- a/windows-agent/internal/proservices/certs_test.go +++ b/windows-agent/internal/proservices/certs_test.go @@ -2,7 +2,6 @@ package proservices_test import ( "os" - "path/filepath" "testing" @@ -11,16 +10,17 @@ import ( ) func TestNewTLSCertificates(t *testing.T) { - + t.Parallel() testcases := map[string]struct { breakDestDir bool - breakServer bool + breakKeyFile string wantErr bool }{ "Success": {}, "Error when the destination directory cannot be written into": {breakDestDir: true, wantErr: true}, - "Error when the server private key cannot be written": {breakServer: true, wantErr: true}, + "Error when the server private key cannot be written": {breakKeyFile: "server_key.pem", wantErr: true}, + "Error when the client private key cannot be written": {breakKeyFile: "client_key.pem", wantErr: true}, } for name, tc := range testcases { @@ -31,9 +31,9 @@ func TestNewTLSCertificates(t *testing.T) { dir = filepath.Join(dir, "inexistent") } - if tc.breakServer { - err := os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700) - require.NoError(t, err, "Setup: could not write directory that should break writing the server key") + if tc.breakKeyFile != "" { + err := os.MkdirAll(filepath.Join(dir, tc.breakKeyFile), 0700) + require.NoError(t, err, "Setup: could not write directory that should break %s", tc.breakKeyFile) } _, err := ps.NewTLSCertificates(dir) diff --git a/windows-agent/internal/proservices/proservices.go b/windows-agent/internal/proservices/proservices.go index 172cb0cf5..d198412cf 100644 --- a/windows-agent/internal/proservices/proservices.go +++ b/windows-agent/internal/proservices/proservices.go @@ -3,8 +3,12 @@ package proservices import ( "context" + "fmt" + "os" + "path/filepath" agent_api "github.com/canonical/ubuntu-pro-for-wsl/agentapi/go" + "github.com/canonical/ubuntu-pro-for-wsl/common" "github.com/canonical/ubuntu-pro-for-wsl/common/grpc/interceptorschain" "github.com/canonical/ubuntu-pro-for-wsl/common/grpc/logconnections" log "github.com/canonical/ubuntu-pro-for-wsl/common/grpc/logstreamer" @@ -18,6 +22,7 @@ import ( "github.com/sirupsen/logrus" wsl "github.com/ubuntu/gowsl" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // Manager is the orchestrator of GRPC API services and business logic. @@ -27,6 +32,8 @@ type Manager struct { landscapeService *landscape.Service registryWatcher *registrywatcher.Service db *database.DistroDB + + creds credentials.TransportCredentials } // options are the configurable functional options for the daemon. @@ -111,7 +118,17 @@ func New(ctx context.Context, publicDir, privateDir string, args ...Option) (s M log.Warningf(ctx, err.Error()) } - return s, nil + destDir := filepath.Join(publicDir, common.CertificatesDir) + if err := os.MkdirAll(destDir, 0700); err != nil { + return s, fmt.Errorf("failed to create certificates directory: %s", err) + } + certs, err := NewTLSCertificates(destDir) + if err != nil { + return s, fmt.Errorf("failed to create certificates: %s", err) + } + + s.creds = credentials.NewTLS(certs.ServerTLSConfig()) + return s, err } // Stop deallocates resources in the services. @@ -140,7 +157,7 @@ func (m Manager) RegisterGRPCServices(ctx context.Context) *grpc.Server { interceptorschain.StreamServer( log.StreamServerInterceptor(logrus.StandardLogger()), logconnections.StreamServerInterceptor(), - ))) + )), grpc.Creds(m.creds)) agent_api.RegisterUIServer(grpcServer, &m.uiService) agent_api.RegisterWSLInstanceServer(grpcServer, m.wslInstanceService) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index 0c1cf21a0..6a2075f92 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -2,15 +2,24 @@ package proservices_test import ( "context" + "crypto/tls" + "crypto/x509" + "net" "os" "path/filepath" "testing" + "time" + agentapi "github.com/canonical/ubuntu-pro-for-wsl/agentapi/go" + "github.com/canonical/ubuntu-pro-for-wsl/common" "github.com/canonical/ubuntu-pro-for-wsl/windows-agent/internal/consts" "github.com/canonical/ubuntu-pro-for-wsl/windows-agent/internal/proservices" "github.com/canonical/ubuntu-pro-for-wsl/windows-agent/internal/proservices/registrywatcher/registry" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" ) func TestMain(m *testing.M) { @@ -24,15 +33,19 @@ func TestNew(t *testing.T) { t.Parallel() testCases := map[string]struct { - breakConfig bool - breakNewDistroDB bool + breakConfig bool + breakNewDistroDB bool + breakCertificatesDir bool + breakCA bool wantErr bool }{ "Success when the subscription stays empty": {}, "Success when the config cannot check if it is read-only": {breakConfig: true}, - "Error when database cannot create its dump file": {breakNewDistroDB: true, wantErr: true}, + "Error when database cannot create its dump file": {breakNewDistroDB: true, wantErr: true}, + "Error when certificates directory cannot be created": {breakCertificatesDir: true, wantErr: true}, + "Error when CA certificate cannot be created": {breakCA: true, wantErr: true}, } for name, tc := range testCases { @@ -53,6 +66,14 @@ func TestNew(t *testing.T) { err := os.MkdirAll(dbFile, 0600) require.NoError(t, err, "Setup: could not write directory where database wants to put a file") } + if tc.breakCertificatesDir { + f, err := os.Create(filepath.Join(publicDir, common.CertificatesDir)) + require.NoError(t, err, "Setup: could not create the file that should break writing the certificates") + f.Close() + } + if tc.breakCA { + require.NoError(t, os.MkdirAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem"), 0700), "Setup: could break ca_cert.pem") + } s, err := proservices.New(ctx, publicDir, privateDir, proservices.WithRegistry(reg)) if err == nil { @@ -71,21 +92,105 @@ func TestNew(t *testing.T) { func TestRegisterGRPCServices(t *testing.T) { t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + testCases := map[string]struct { + insecureClient bool + + wantErr bool + }{ + "Success": {}, - ps, err := proservices.New(ctx, t.TempDir(), t.TempDir(), proservices.WithRegistry(registry.NewMock())) - require.NoError(t, err, "Setup: New should return no error") - defer ps.Stop(ctx) + "Error with insecure requests": {insecureClient: true, wantErr: true}, + } - server := ps.RegisterGRPCServices(context.Background()) - info := server.GetServiceInfo() + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + publicDir := t.TempDir() + + ps, err := proservices.New(ctx, publicDir, t.TempDir(), proservices.WithRegistry(registry.NewMock())) + require.NoError(t, err, "Setup: New should return no error") + defer ps.Stop(ctx) + + server := ps.RegisterGRPCServices(context.Background()) + info := server.GetServiceInfo() + + _, ok := info["agentapi.UI"] + require.True(t, ok, "UI service should be registered after calling RegisterGRPCServices") + + _, ok = info["agentapi.WSLInstance"] + require.True(t, ok, "WSLInstance service should be registered after calling RegisterGRPCServices") + + require.Lenf(t, info, 2, "Info should contain exactly two elements") + + // Run the server configured by RegisterGRPCServices. + var cfg net.ListenConfig + lis, err := cfg.Listen(ctx, "tcp", "localhost:0") + require.NoError(t, err, "Setup: could not create a listener") + defer lis.Close() + + go func() { + err := server.Serve(lis) + if err != nil { + t.Logf("Serve exited with error: %v", err) + } + }() + defer server.Stop() + + // Create a client connection to the server. + addr := lis.Addr().String() + var creds credentials.TransportCredentials + if tc.insecureClient { + creds = insecure.NewCredentials() + } else { + creds = loadClientCertificates(t, filepath.Join(publicDir, common.CertificatesDir)) + } + conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds)) + require.NoError(t, err, "Setup: could not create a client connection") + defer conn.Close() + c := agentapi.NewUIClient(conn) - _, ok := info["agentapi.UI"] - require.True(t, ok, "UI service should be registered after calling RegisterGRPCServices") + // Test the client connection. + ctx, cancel = context.WithTimeout(context.Background(), 200*time.Hour) + defer cancel() + _, err = c.Ping(ctx, &agentapi.Empty{}) - _, ok = info["agentapi.WSLInstance"] - require.True(t, ok, "WSLInstance service should be registered after calling RegisterGRPCServices") + if tc.wantErr { + require.Error(t, err, "Clients should fail to call any RPC") + return + } + require.NoError(t, err, "Clients should succeed in calling any RPC") + }) + } +} + +func loadClientCertificates(t *testing.T, certsDir string) credentials.TransportCredentials { + t.Helper() + + cert, err := tls.LoadX509KeyPair(filepath.Join(certsDir, "client_cert.pem"), filepath.Join(certsDir, "client_key.pem")) + if err != nil { + require.NoError(t, err, "failed to load client cert: %v", err) + } + + ca := x509.NewCertPool() + caFilePath := filepath.Join(certsDir, "ca_cert.pem") + caBytes, err := os.ReadFile(caFilePath) + if err != nil { + require.NoError(t, err, "failed to read ca cert %q: %v", caFilePath, err) + } + if ok := ca.AppendCertsFromPEM(caBytes); !ok { + require.NoError(t, err, "failed to parse %q", caFilePath) + } + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS13, + ServerName: common.GRPCServerNameOverride, + Certificates: []tls.Certificate{cert}, + RootCAs: ca, + } - require.Lenf(t, info, 2, "Info should contain exactly two elements") + return credentials.NewTLS(tlsConfig) } From d1ac5145203ded08629b54f08725b22d3096b52f Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 12 Apr 2024 09:40:06 -0300 Subject: [PATCH 03/41] GUI: AgentApiClient now depends on the certs dir We can only suceed in creating a client if the required certificates exist. We don't validate their contents upfront, though, leaving it for the framework to do so when TLS is needed. --- .../ubuntupro/lib/core/agent_api_client.dart | 74 ++++++++++++++++--- .../test/core/agent_api_client_test.dart | 65 ++++++++++++++-- .../ubuntupro/test/testdata/certs/ca_cert.pem | 11 +++ .../test/testdata/certs/client_cert.pem | 12 +++ .../test/testdata/certs/client_key.pem | 5 ++ 5 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 gui/packages/ubuntupro/test/testdata/certs/ca_cert.pem create mode 100644 gui/packages/ubuntupro/test/testdata/certs/client_cert.pem create mode 100644 gui/packages/ubuntupro/test/testdata/certs/client_key.pem diff --git a/gui/packages/ubuntupro/lib/core/agent_api_client.dart b/gui/packages/ubuntupro/lib/core/agent_api_client.dart index d41fda93d..c564138fb 100644 --- a/gui/packages/ubuntupro/lib/core/agent_api_client.dart +++ b/gui/packages/ubuntupro/lib/core/agent_api_client.dart @@ -1,6 +1,10 @@ +import 'dart:io'; +import 'dart:typed_data'; + import 'package:agentapi/agentapi.dart'; import 'package:grpc/grpc.dart'; import 'package:meta/meta.dart'; +import 'package:path/path.dart' as p; /// Type aliases for the gRPC message enums which by default have big names. typedef SubscriptionType = SubscriptionInfo_SubscriptionType; @@ -8,15 +12,16 @@ typedef LandscapeSourceType = LandscapeSource_LandscapeSourceType; /// AgentApiClient hides the gRPC details in a more convenient API. class AgentApiClient { - AgentApiClient({ - required String host, - required int port, + AgentApiClient( + String host, + int port, + Directory certsDir, [ this.stubFactory = UIClient.new, - }) : _channel = ClientChannel( + ]) : _channel = ClientChannel( host, port: port, - options: const ChannelOptions( - credentials: ChannelCredentials.insecure(), + options: ChannelOptions( + credentials: credentialsfromDirectory(certsDir), ), ) { _client = stubFactory.call(_channel); @@ -32,13 +37,17 @@ class AgentApiClient { ClientChannel _channel; /// Changes the endpoint this API client is connected to. - Future connectTo({required String host, required int port}) { + Future connectTo( + String host, + int port, + Directory certsDir, + ) { _channel.shutdown(); _channel = ClientChannel( host, port: port, - options: const ChannelOptions( - credentials: ChannelCredentials.insecure(), + options: ChannelOptions( + credentials: credentialsfromDirectory(certsDir), ), ); _client = stubFactory.call(_channel); @@ -95,3 +104,50 @@ Stream mapGRPCConnectionEvents( return ConnectionEvent.dropped; }); } + +// A custom ChannelCredentials implementation that allows for setting the client's certificate chain and private key. +// This is loosely a translation of grpc.TransportCredentials with tls.Config for Go gRPC clients into Dart. +class AgentApiChannelCredentials extends ChannelCredentials { + final Uint8List? certificateChain; + final Uint8List? privateKey; + + AgentApiChannelCredentials({ + Uint8List? trustedRoots, + this.certificateChain, + this.privateKey, + super.authority, + super.onBadCertificate, + }) : super.secure( + certificates: trustedRoots, + ); + + @override + SecurityContext? get securityContext { + final ctx = super.securityContext; + if (ctx == null) return null; + + if (certificateChain != null) { + ctx.useCertificateChainBytes(certificateChain!); + } + if (privateKey != null) { + ctx.usePrivateKeyBytes(privateKey!); + } + return ctx; + } +} + +// A factory method to create a new instance of AgentApiChannelCredentials from a +// directory containing the necessary certificates. +AgentApiChannelCredentials credentialsfromDirectory(Directory d) { + final trustedRoots = File(p.join(d.path, 'ca_cert.pem')).readAsBytesSync(); + final certificateChain = + File(p.join(d.path, 'client_cert.pem')).readAsBytesSync(); + final privateKey = File(p.join(d.path, 'client_key.pem')).readAsBytesSync(); + + return AgentApiChannelCredentials( + trustedRoots: trustedRoots, + certificateChain: certificateChain, + privateKey: privateKey, + authority: 'UP4W', + ); +} diff --git a/gui/packages/ubuntupro/test/core/agent_api_client_test.dart b/gui/packages/ubuntupro/test/core/agent_api_client_test.dart index d3ec3db0f..c663016a8 100644 --- a/gui/packages/ubuntupro/test/core/agent_api_client_test.dart +++ b/gui/packages/ubuntupro/test/core/agent_api_client_test.dart @@ -1,21 +1,31 @@ +import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:grpc/grpc.dart' as grpc; +import 'package:path/path.dart' as p; import 'package:ubuntupro/core/agent_api_client.dart'; import '../utils/mock_grpc.dart'; void main() { + final globalTmpCertsDir = Directory.current.createTempSync(); + + fillWithEmptyCerts(globalTmpCertsDir); + + tearDownAll(() => globalTmpCertsDir.deleteSync(recursive: true)); + test('ping fails', timeout: const Timeout(Duration(seconds: 5)), () async { - final client = AgentApiClient(host: '127.0.0.1', port: 9); + final client = AgentApiClient('127.0.0.1', 9, globalTmpCertsDir); // IANA discard protol: There should be no service running at this port. expect(await client.ping(), isFalse); }); group('with mocked grpc', () { final client = AgentApiClient( - host: '127.0.0.1', - port: 9, - stubFactory: MockUIClient.new, + '127.0.0.1', + 9, + globalTmpCertsDir, + MockUIClient.new, ); test('ping succeeds', () async { @@ -59,7 +69,11 @@ void main() { test('connect to new endpoint', () async { final otherRef = client; - final connected = await otherRef.connectTo(host: 'localhost', port: 9); + final connected = await otherRef.connectTo( + 'localhost', + 9, + globalTmpCertsDir, + ); // A real connection would never succeed in port 9 (Discard Protocol). expect(connected, isTrue); // The client object is still the same, although internals may have changed. @@ -105,4 +119,45 @@ void main() { ); }); }); + + test('empty security options', () async { + final dir = await Directory.current.createTemp(); + fillWithEmptyCerts(dir); + addTearDown(() => dir.deleteSync(recursive: true)); + final creds = credentialsfromDirectory(dir); + // illusion, since certificates are empty, but that's fine. + expect(creds.isSecure, isTrue); + // Problematic would be trying to access the security context, + // as it will throw an exception when validating the contents of the certificates. + expect( + () => creds.securityContext, + throwsA(const TypeMatcher()), + ); + const caCert = 'ca_cert.pem'; + await File(p.join(dir.path, caCert)).delete(); + + // A missing cert throws an exception up front. + expect( + () => credentialsfromDirectory(dir), + throwsA( + const TypeMatcher() + .having((e) => e.path, 'path', contains(caCert)), + ), + ); + }); + + test('real security options', () async { + final creds = credentialsfromDirectory(Directory('./test/testdata/certs')); + // illusion, since certificates are empty, but that's fine. + expect(creds.isSecure, isTrue); + // Problematic would be trying to access the security context, + // as it will throw an exception when validating the contents of the certificates. + expect(creds.securityContext, isNotNull); + }); +} + +void fillWithEmptyCerts(Directory dir) { + File(p.join(dir.path, 'ca_cert.pem')).writeAsStringSync(''); + File(p.join(dir.path, 'client_cert.pem')).writeAsStringSync(''); + File(p.join(dir.path, 'client_key.pem')).writeAsStringSync(''); } diff --git a/gui/packages/ubuntupro/test/testdata/certs/ca_cert.pem b/gui/packages/ubuntupro/test/testdata/certs/ca_cert.pem new file mode 100644 index 000000000..ac61984c0 --- /dev/null +++ b/gui/packages/ubuntupro/test/testdata/certs/ca_cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBqzCCAVCgAwIBAgIRAPsSlGGEMMNKO31esOqWcVowCgYIKoZIzj0EAwIwITEN +MAsGA1UEChMEVVA0VzEQMA4GA1UEAxMHVVA0VyBDQTAeFw0yNDA0MTUwNDIwMDJa +Fw0yNDA0MTUwNDIxMDJaMCExDTALBgNVBAoTBFVQNFcxEDAOBgNVBAMTB1VQNFcg +Q0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQnyjcbyj6MGV0KDdkzPzgq+Npw +5bwUNTGdIOHW5/aW1REI292fTj/28GorApiKghw3ccOCQI4E++WEX0hL2wMBo2kw +ZzAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUYrqU +1DXu6M1TEvYivM7E+FbyctowJQYDVR0RBB4wHIIEVVA0V4IJbG9jYWxob3N0ggkx +MjcuMC4wLjEwCgYIKoZIzj0EAwIDSQAwRgIhAMlCdMk+1gcIW/cU5w2V/gbCvjxK +D5/EEvyASGnVnk+DAiEAxFKx19C1mBLPLfyvNuDb7WMbnjBaJViN2JNmpL8xcok= +-----END CERTIFICATE----- diff --git a/gui/packages/ubuntupro/test/testdata/certs/client_cert.pem b/gui/packages/ubuntupro/test/testdata/certs/client_cert.pem new file mode 100644 index 000000000..493841379 --- /dev/null +++ b/gui/packages/ubuntupro/test/testdata/certs/client_cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxjCCAW2gAwIBAgIRAfYlKMMIYYaUdvq9YdUs4rAwCgYIKoZIzj0EAwIwITEN +MAsGA1UEChMEVVA0VzEQMA4GA1UEAxMHVVA0VyBDQTAeFw0yNDA0MTUwNDIwMDJa +Fw0yNDA0MTUwNDIxMDJaMB4xDTALBgNVBAoTBFVQNFcxDTALBgNVBAMTBFVQNFcw +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ+Pn/SrP7hSVe74ob/AKJNEWa5RjhB +5mA0bUH3lkBQINuwpj69yZlSbv51l3YH76ZQST/4Gi0lAuyad4NaJIPbo4GIMIGF +MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRiupTUNe7ozVMS9iK8zsT4VvJy2jAl +BgNVHREEHjAcggRVUDRXgglsb2NhbGhvc3SCCTEyNy4wLjAuMTAKBggqhkjOPQQD +AgNHADBEAiADxrxYSR8Kwa6qX5Lk7RA2ZCTQDjdsuv+wU5ssOzNtwgIgUiv642p+ +MAOQGV3OIgCbbYXhlsRAR0KStoGHmlBjYa4= +-----END CERTIFICATE----- diff --git a/gui/packages/ubuntupro/test/testdata/certs/client_key.pem b/gui/packages/ubuntupro/test/testdata/certs/client_key.pem new file mode 100644 index 000000000..14a4bd451 --- /dev/null +++ b/gui/packages/ubuntupro/test/testdata/certs/client_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkWI0benzEY8ICXVG +e6sVjbiwgVn2aTfqQ4IvJZ9fGWyhRANCAAQ+Pn/SrP7hSVe74ob/AKJNEWa5RjhB +5mA0bUH3lkBQINuwpj69yZlSbv51l3YH76ZQST/4Gi0lAuyad4NaJIPb +-----END PRIVATE KEY----- From 9f56d089adc8622dd63e902e9560eef7d9cb1ecc Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 12 Apr 2024 09:43:51 -0300 Subject: [PATCH 04/41] GUI: Regenerate mocks The annoying part of generated mocks is that they need to change quite often. Any touches in the agent API client requires this. Still better than writing them by hand. :) --- .../core/agent_connection_test.mocks.dart | 29 ++++++++++--------- .../test/core/agent_monitor_test.mocks.dart | 20 +++++++------ .../landscape/landscape_model_test.mocks.dart | 20 +++++++------ .../landscape/landscape_page_test.mocks.dart | 20 +++++++------ .../subscribe_now_model_test.mocks.dart | 20 +++++++------ .../subscription_status_model_test.mocks.dart | 20 +++++++------ .../startup/startup_model_test.mocks.dart | 2 ++ .../test/startup/startup_page_test.mocks.dart | 29 ++++++++++--------- 8 files changed, 89 insertions(+), 71 deletions(-) diff --git a/gui/packages/ubuntupro/test/core/agent_connection_test.mocks.dart b/gui/packages/ubuntupro/test/core/agent_connection_test.mocks.dart index 5d6fcb736..8d0f427d7 100644 --- a/gui/packages/ubuntupro/test/core/agent_connection_test.mocks.dart +++ b/gui/packages/ubuntupro/test/core/agent_connection_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i3; -import 'package:grpc/grpc.dart' as _i6; +import 'package:grpc/grpc.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; import 'package:ubuntupro/core/agent_api_client.dart' as _i2; import 'package:ubuntupro/core/agent_monitor.dart' as _i4; @@ -98,6 +99,7 @@ class MockAgentStartupMonitor extends _i1.Mock returnValue: ( String host, int port, + _i6.Directory certsDir, ) => _FakeAgentApiClient_0( this, @@ -151,14 +153,14 @@ class MockAgentApiClient extends _i1.Mock implements _i2.AgentApiClient { } @override - _i3.UIClient Function(_i6.ClientChannel) get stubFactory => + _i3.UIClient Function(_i7.ClientChannel) get stubFactory => (super.noSuchMethod( Invocation.getter(#stubFactory), - returnValue: (_i6.ClientChannel __p0) => _FakeUIClient_1( + returnValue: (_i7.ClientChannel __p0) => _FakeUIClient_1( this, Invocation.getter(#stubFactory), ), - ) as _i3.UIClient Function(_i6.ClientChannel)); + ) as _i3.UIClient Function(_i7.ClientChannel)); @override _i5.Stream<_i2.ConnectionEvent> get onConnectionChanged => @@ -168,18 +170,19 @@ class MockAgentApiClient extends _i1.Mock implements _i2.AgentApiClient { ) as _i5.Stream<_i2.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/core/agent_monitor_test.mocks.dart b/gui/packages/ubuntupro/test/core/agent_monitor_test.mocks.dart index 97edfb8ee..72b7a9afd 100644 --- a/gui/packages/ubuntupro/test/core/agent_monitor_test.mocks.dart +++ b/gui/packages/ubuntupro/test/core/agent_monitor_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i2; import 'package:grpc/grpc.dart' as _i4; @@ -91,18 +92,19 @@ class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { ) as _i5.Stream<_i3.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/pages/landscape/landscape_model_test.mocks.dart b/gui/packages/ubuntupro/test/pages/landscape/landscape_model_test.mocks.dart index f86ed3d9f..ad7e628e1 100644 --- a/gui/packages/ubuntupro/test/pages/landscape/landscape_model_test.mocks.dart +++ b/gui/packages/ubuntupro/test/pages/landscape/landscape_model_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i2; import 'package:grpc/grpc.dart' as _i4; @@ -91,18 +92,19 @@ class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { ) as _i5.Stream<_i3.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.mocks.dart b/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.mocks.dart index e80e81b0b..88ebe35aa 100644 --- a/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.mocks.dart +++ b/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i2; import 'package:grpc/grpc.dart' as _i4; @@ -91,18 +92,19 @@ class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { ) as _i5.Stream<_i3.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_model_test.mocks.dart b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_model_test.mocks.dart index 13d9a9061..6c6f13232 100644 --- a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_model_test.mocks.dart +++ b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_model_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i2; import 'package:grpc/grpc.dart' as _i4; @@ -91,18 +92,19 @@ class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { ) as _i5.Stream<_i3.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/pages/subscription_status/subscription_status_model_test.mocks.dart b/gui/packages/ubuntupro/test/pages/subscription_status/subscription_status_model_test.mocks.dart index 8f716d8cf..a8d4fc851 100644 --- a/gui/packages/ubuntupro/test/pages/subscription_status/subscription_status_model_test.mocks.dart +++ b/gui/packages/ubuntupro/test/pages/subscription_status/subscription_status_model_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i2; import 'package:grpc/grpc.dart' as _i4; @@ -91,18 +92,19 @@ class MockAgentApiClient extends _i1.Mock implements _i3.AgentApiClient { ) as _i5.Stream<_i3.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); diff --git a/gui/packages/ubuntupro/test/startup/startup_model_test.mocks.dart b/gui/packages/ubuntupro/test/startup/startup_model_test.mocks.dart index 31c1a2ea5..8c7aa62dd 100644 --- a/gui/packages/ubuntupro/test/startup/startup_model_test.mocks.dart +++ b/gui/packages/ubuntupro/test/startup/startup_model_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; +import 'dart:io' as _i5; import 'package:mockito/mockito.dart' as _i1; import 'package:ubuntupro/core/agent_api_client.dart' as _i2; @@ -54,6 +55,7 @@ class MockAgentStartupMonitor extends _i1.Mock returnValue: ( String host, int port, + _i5.Directory certsDir, ) => _FakeAgentApiClient_0( this, diff --git a/gui/packages/ubuntupro/test/startup/startup_page_test.mocks.dart b/gui/packages/ubuntupro/test/startup/startup_page_test.mocks.dart index 4e86d3173..a010df078 100644 --- a/gui/packages/ubuntupro/test/startup/startup_page_test.mocks.dart +++ b/gui/packages/ubuntupro/test/startup/startup_page_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; +import 'dart:io' as _i6; import 'package:agentapi/agentapi.dart' as _i3; -import 'package:grpc/grpc.dart' as _i6; +import 'package:grpc/grpc.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; import 'package:ubuntupro/core/agent_api_client.dart' as _i2; import 'package:ubuntupro/core/agent_monitor.dart' as _i4; @@ -98,6 +99,7 @@ class MockAgentStartupMonitor extends _i1.Mock returnValue: ( String host, int port, + _i6.Directory certsDir, ) => _FakeAgentApiClient_0( this, @@ -151,14 +153,14 @@ class MockAgentApiClient extends _i1.Mock implements _i2.AgentApiClient { } @override - _i3.UIClient Function(_i6.ClientChannel) get stubFactory => + _i3.UIClient Function(_i7.ClientChannel) get stubFactory => (super.noSuchMethod( Invocation.getter(#stubFactory), - returnValue: (_i6.ClientChannel __p0) => _FakeUIClient_1( + returnValue: (_i7.ClientChannel __p0) => _FakeUIClient_1( this, Invocation.getter(#stubFactory), ), - ) as _i3.UIClient Function(_i6.ClientChannel)); + ) as _i3.UIClient Function(_i7.ClientChannel)); @override _i5.Stream<_i2.ConnectionEvent> get onConnectionChanged => @@ -168,18 +170,19 @@ class MockAgentApiClient extends _i1.Mock implements _i2.AgentApiClient { ) as _i5.Stream<_i2.ConnectionEvent>); @override - _i5.Future connectTo({ - required String? host, - required int? port, - }) => + _i5.Future connectTo( + String? host, + int? port, + _i6.Directory? certsDir, + ) => (super.noSuchMethod( Invocation.method( #connectTo, - [], - { - #host: host, - #port: port, - }, + [ + host, + port, + certsDir, + ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); From ebe6930e3d28422df665b7b6af11959bc2362ede Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 12 Apr 2024 09:55:03 -0300 Subject: [PATCH 05/41] GUI: Updates AgentMonitor to pass the certs dir when creating a client. Add a verification in one of its test cases to make sure the directory passed makes sense. --- .../ubuntupro/lib/core/agent_monitor.dart | 11 +++++-- .../test/core/agent_monitor_test.dart | 30 +++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/gui/packages/ubuntupro/lib/core/agent_monitor.dart b/gui/packages/ubuntupro/lib/core/agent_monitor.dart index 692c658ce..4233ba911 100644 --- a/gui/packages/ubuntupro/lib/core/agent_monitor.dart +++ b/gui/packages/ubuntupro/lib/core/agent_monitor.dart @@ -40,7 +40,11 @@ enum AgentState { } /// A Function that knows how to create an AgentApiClient from a host and a port. -typedef ApiClientFactory = AgentApiClient Function(String host, int port); +typedef ApiClientFactory = AgentApiClient Function( + String host, + int port, + Directory certsDir, +); /// A Function that knows how to launch the agent and report success. typedef AgentLauncher = Future Function(); @@ -168,13 +172,14 @@ class AgentStartupMonitor { Future _onAddress((String, int) address) async { final (host, port) = address; + final dir = Directory(p.join(p.dirname(_addrFilePath!), 'certs')); if (_agentApiClient != null) { - await _agentApiClient!.connectTo(host: host, port: port); + await _agentApiClient!.connectTo(host, port, dir); return AgentState.ok; } - final client = clientFactory(host, port); + final client = clientFactory(host, port, dir); if (await client.ping()) { _agentApiClient = client; for (final cb in _onClient) { diff --git a/gui/packages/ubuntupro/test/core/agent_monitor_test.dart b/gui/packages/ubuntupro/test/core/agent_monitor_test.dart index 1e0ec8a9b..611d7ce8f 100644 --- a/gui/packages/ubuntupro/test/core/agent_monitor_test.dart +++ b/gui/packages/ubuntupro/test/core/agent_monitor_test.dart @@ -43,7 +43,7 @@ void main() { final monitor = AgentStartupMonitor( /// A launch request will always fail. agentLauncher: () async => false, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -70,7 +70,7 @@ void main() { final monitor = AgentStartupMonitor( /// A launch request will always succeed. agentLauncher: () async => true, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -93,7 +93,7 @@ void main() { final monitor = AgentStartupMonitor( /// A launch request will always succeed. agentLauncher: () async => true, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -115,7 +115,7 @@ void main() { final monitor = AgentStartupMonitor( /// A launch request will always succeed. agentLauncher: () async => true, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -145,7 +145,7 @@ void main() { final monitor = AgentStartupMonitor( /// A launch request will always succeed. agentLauncher: () async => true, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -163,6 +163,7 @@ void main() { test('start agent with mocks', () async { final mockClient = MockAgentApiClient(); + Directory? certsDirPassed; // Fakes a successful ping. when(mockClient.ping()).thenAnswer((_) async => true); final monitor = AgentStartupMonitor( @@ -171,7 +172,10 @@ void main() { writeDummyAddrFile(homeDir!); return true; }, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, d) { + certsDirPassed = d; + return mockClient; + }, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -186,6 +190,8 @@ void main() { ]), ); verify(mockClient.ping()).called(1); + expect(certsDirPassed, isNotNull); + expect(certsDirPassed!.path, contains(homeDir!.path)); }); test('timeout if never addr', () async { @@ -197,7 +203,7 @@ void main() { agentLauncher: () async { return true; }, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -224,7 +230,7 @@ void main() { writeDummyAddrFile(homeDir!); return true; }, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) async { // This function only completes when the completer is manually set complete. @@ -260,15 +266,14 @@ void main() { // Fakes a successful ping. when(mockClient.ping()).thenAnswer((_) async => true); // fakes a succesful connectTo call. - when(mockClient.connectTo(host: anyNamed('host'), port: anyNamed('port'))) - .thenAnswer((_) async => true); + when(mockClient.connectTo(any, any, any)).thenAnswer((_) async => true); final monitor = AgentStartupMonitor( /// A launch request will always succeed. agentLauncher: () async { writeDummyAddrFile(homeDir!); return true; }, - clientFactory: (host, port) => mockClient, + clientFactory: (_, __, ___) => mockClient, addrFileName: kAddrFileName, onClient: (_) {}, ); @@ -298,8 +303,7 @@ void main() { ); final newClient = monitor.agentApiClient; - verify(mockClient.connectTo(host: anyNamed('host'), port: anyNamed('port'))) - .called(1); + verify(mockClient.connectTo(any, any, any)).called(1); expect(newClient.hashCode, currentClient.hashCode); }); } From 05d1178368d20b8674ba4d028f922bd9d49f42fc Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 12 Apr 2024 10:05:49 -0300 Subject: [PATCH 06/41] GUI: Use the AgentApiClient constructor directly instead of proxying with another function For simplicity. --- gui/packages/ubuntupro/lib/main.dart | 5 +---- gui/packages/ubuntupro/test/startup/startup_page_test.dart | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/gui/packages/ubuntupro/lib/main.dart b/gui/packages/ubuntupro/lib/main.dart index 5ed110947..9414ab266 100644 --- a/gui/packages/ubuntupro/lib/main.dart +++ b/gui/packages/ubuntupro/lib/main.dart @@ -17,13 +17,10 @@ Future main() async { final agentMonitor = AgentStartupMonitor( addrFileName: kAddrFileName, agentLauncher: launch, - clientFactory: defaultClient, + clientFactory: AgentApiClient.new, onClient: registerServiceInstance, ); runApp(Pro4WSLApp(agentMonitor)); } -AgentApiClient defaultClient(String host, int port) => - AgentApiClient(host: host, port: port); - Future launch() => launchAgent(kAgentRelativePath); diff --git a/gui/packages/ubuntupro/test/startup/startup_page_test.dart b/gui/packages/ubuntupro/test/startup/startup_page_test.dart index 3fd12aa0e..97fac87b7 100644 --- a/gui/packages/ubuntupro/test/startup/startup_page_test.dart +++ b/gui/packages/ubuntupro/test/startup/startup_page_test.dart @@ -105,8 +105,7 @@ void main() { create: (context) => AgentStartupMonitor( addrFileName: 'anywhere', agentLauncher: () async => true, - clientFactory: (host, port) => - AgentApiClient(host: host, port: port), + clientFactory: AgentApiClient.new, onClient: (_) {}, ), ), From 57e6fccf946efb46ef3220bd208f89b5984caf20 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 12 Apr 2024 10:31:39 -0300 Subject: [PATCH 07/41] wsl-pro-services: Use the agent certificates Failing to find or load them put the service in retry state as with other error causes in the `connect` loop. Changed mock_agent to store the client creds to ease test code. --- wsl-pro-service/internal/daemon/daemon.go | 38 +++++++++++- .../internal/daemon/daemon_test.go | 14 +++++ .../internal/streams/server_test.go | 5 +- .../internal/testutils/mock_agent.go | 62 +++++++++++++++++-- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/wsl-pro-service/internal/daemon/daemon.go b/wsl-pro-service/internal/daemon/daemon.go index cbe1fb5d0..055fc6933 100644 --- a/wsl-pro-service/internal/daemon/daemon.go +++ b/wsl-pro-service/internal/daemon/daemon.go @@ -3,6 +3,8 @@ package daemon import ( "context" + "crypto/tls" + "crypto/x509" "errors" "fmt" "net" @@ -22,12 +24,13 @@ import ( "github.com/sirupsen/logrus" "github.com/ubuntu/decorate" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) // Daemon is a grpc daemon with systemd support. type Daemon struct { - addressPath string + addressPath, certsPath string // Interface to the WSL distro system *system.System @@ -93,6 +96,7 @@ func New(ctx context.Context, s *system.System, args ...Option) (*Daemon, error) systemdSdNotifier: opts.systemdSdNotifier, system: s, addressPath: filepath.Join(home, common.UserProfileDir, common.ListeningPortFileName), + certsPath: filepath.Join(home, common.UserProfileDir, common.CertificatesDir), ctx: ctx, cancel: cancel, @@ -283,11 +287,15 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error log.Infof(ctx, "Daemon: starting connection to Windows Agent via %s", addr) + tlsConfig, err := tlsConfigFromDir(d.certsPath) + if err != nil { + return nil, err + } conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStreamInterceptor(interceptorschain.StreamClient( log.StreamClientInterceptor(logrus.StandardLogger(), log.WithClientID(distroName)), - ))) + )), grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) if err != nil { return nil, fmt.Errorf("could not dial: %v", err) } @@ -301,6 +309,32 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error return streams.NewServer(ctx, d.system, conn), nil } +// tlsConfigFromDir loads certificates from the provided certs path and returns a matching tls.Config. +func tlsConfigFromDir(certsPath string) (conf *tls.Config, err error) { + decorate.OnError(&err, "could not load TLS config") + cert, err := tls.LoadX509KeyPair(filepath.Join(certsPath, "client_cert.pem"), filepath.Join(certsPath, "client_key.pem")) + if err != nil { + return nil, err + } + + ca := x509.NewCertPool() + caFilePath := filepath.Join(certsPath, "ca_cert.pem") + caBytes, err := os.ReadFile(caFilePath) + if err != nil { + return nil, err + } + if ok := ca.AppendCertsFromPEM(caBytes); !ok { + return nil, fmt.Errorf("failed to parse %q", caFilePath) + } + + return &tls.Config{ + ServerName: common.GRPCServerNameOverride, + Certificates: []tls.Certificate{cert}, + RootCAs: ca, + MinVersion: tls.VersionTLS13, + }, nil +} + // address fetches the address of the control stream from the Windows filesystem. func (d *Daemon) address(ctx context.Context, system *system.System) (string, error) { // Parse the port from the file written by the windows agent. diff --git a/wsl-pro-service/internal/daemon/daemon_test.go b/wsl-pro-service/internal/daemon/daemon_test.go index df70db801..e534937ff 100644 --- a/wsl-pro-service/internal/daemon/daemon_test.go +++ b/wsl-pro-service/internal/daemon/daemon_test.go @@ -67,6 +67,8 @@ func TestServe(t *testing.T) { precancelContext bool breakWindowsHostAddress bool dontServe bool + missingCertsDir bool + missingCaCert bool // Break the port file in various ways breakPortFile bool @@ -97,6 +99,8 @@ func TestServe(t *testing.T) { "No connection because the port file has port 0": {portFileZeroPort: true, wantConnected: false}, "No connection because the port file has a negative port": {portFileNegativePort: true, wantConnected: false}, "No connection because there is no server": {dontServe: true}, + "No connection because there are no certificates": {missingCertsDir: true, wantConnected: false}, + "No connection because cannot read ca_cert": {missingCaCert: true, wantConnected: false}, // Errors "Error because the context is pre-cancelled": {precancelContext: true, wantSystemdNotReady: true, wantErr: true}, @@ -117,6 +121,16 @@ func TestServe(t *testing.T) { agent := testutils.NewMockWindowsAgent(t, ctx, publicDir) defer agent.Stop() + if tc.missingCertsDir { + err := os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir)) + require.NotErrorIs(t, err, os.ErrNotExist, "Setup: could not remove certificates") + } + + if tc.missingCaCert { + err := os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem")) + require.NotErrorIs(t, err, os.ErrNotExist, "Setup: could not remove ca_cert.pem") + } + if tc.breakPortFile { err := os.RemoveAll(publicDir) require.NoError(t, err, "Setup: could not remove port file") diff --git a/wsl-pro-service/internal/streams/server_test.go b/wsl-pro-service/internal/streams/server_test.go index 667a2ce0d..8b1fb7ee4 100644 --- a/wsl-pro-service/internal/streams/server_test.go +++ b/wsl-pro-service/internal/streams/server_test.go @@ -11,7 +11,6 @@ import ( "github.com/canonical/ubuntu-pro-for-wsl/wsl-pro-service/internal/testutils" "github.com/stretchr/testify/require" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) func TestServe(t *testing.T) { @@ -24,7 +23,7 @@ func TestServe(t *testing.T) { defer agent.Stop() conn, err := grpc.DialContext(ctx, agent.Listener.Addr().String(), - grpc.WithTransportCredentials(insecure.NewCredentials())) + grpc.WithTransportCredentials(agent.ClientCredentials)) require.NoError(t, err, "Setup: could not Dial the mock windows agent") defer conn.Close() @@ -101,7 +100,7 @@ func TestStop(t *testing.T) { defer agent.Stop() conn, err := grpc.DialContext(ctx, agent.Listener.Addr().String(), - grpc.WithTransportCredentials(insecure.NewCredentials())) + grpc.WithTransportCredentials(agent.ClientCredentials)) require.NoError(t, err, "Setup: could not Dial the mock windows agent") defer conn.Close() diff --git a/wsl-pro-service/internal/testutils/mock_agent.go b/wsl-pro-service/internal/testutils/mock_agent.go index f0ee6f675..14cf1fb0a 100644 --- a/wsl-pro-service/internal/testutils/mock_agent.go +++ b/wsl-pro-service/internal/testutils/mock_agent.go @@ -2,9 +2,13 @@ package testutils import ( "context" + "crypto/rand" + "crypto/tls" + "crypto/x509" "errors" "fmt" "io" + "math/big" "net" "os" "path/filepath" @@ -13,10 +17,12 @@ import ( agentapi "github.com/canonical/ubuntu-pro-for-wsl/agentapi/go" "github.com/canonical/ubuntu-pro-for-wsl/common" + "github.com/canonical/ubuntu-pro-for-wsl/common/certs" log "github.com/canonical/ubuntu-pro-for-wsl/common/grpc/logstreamer" "github.com/stretchr/testify/require" "github.com/ubuntu/decorate" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // MockWindowsAgent mocks the windows agent server. @@ -25,6 +31,8 @@ type MockWindowsAgent struct { Service *mockWSLInstanceService Listener net.Listener + ClientCredentials credentials.TransportCredentials + Started chan struct{} Stopped chan struct{} } @@ -43,12 +51,15 @@ func NewMockWindowsAgent(t *testing.T, ctx context.Context, publicDir string) *M lis, err := cfg.Listen(ctx, "tcp4", "localhost:0") require.NoError(t, err, "Setup: could not listen to agent address") + clientCreds, serverCreds, err := agentTLSCreds(filepath.Join(publicDir, common.CertificatesDir)) + require.NoError(t, err, "Setup: could not create TLS certificates and config") m := MockWindowsAgent{ - Listener: lis, - Server: grpc.NewServer(), - Service: &mockWSLInstanceService{}, - Started: make(chan struct{}), - Stopped: make(chan struct{}), + Listener: lis, + Server: grpc.NewServer(grpc.Creds(serverCreds)), + Service: &mockWSLInstanceService{}, + ClientCredentials: clientCreds, + Started: make(chan struct{}), + Stopped: make(chan struct{}), } agentapi.RegisterWSLInstanceServer(m.Server, m.Service) t.Cleanup(m.Stop) @@ -81,6 +92,47 @@ func NewMockWindowsAgent(t *testing.T, ctx context.Context, publicDir string) *M return &m } +func agentTLSCreds(destDir string) (clientCreds, serverCreds credentials.TransportCredentials, err error) { + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) + } + if err := os.MkdirAll(destDir, 0700); err != nil { + return nil, nil, fmt.Errorf("failed to create certificates directory: %v", err) + } + rootCert, rootKey, err := certs.CreateRootCA("UP4W Test", serial, destDir) + if err != nil { + return nil, nil, err + } + + // Create and write the server and client certificates signed by the root certificate created above. + serverCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) + if err != nil { + return nil, nil, err + } + clientCert, err := certs.CreateTLSCertificateSignedBy("client", "test-client", serial.Lsh(serial, 3), rootCert, rootKey, destDir) + if err != nil { + return nil, nil, err + } + + ca := x509.NewCertPool() + ca.AddCert(rootCert) + clientCreds = credentials.NewTLS(&tls.Config{ + MinVersion: tls.VersionTLS13, + ServerName: common.GRPCServerNameOverride, + Certificates: []tls.Certificate{*clientCert}, + RootCAs: ca, + }) + serverCreds = credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{*serverCert}, + ClientCAs: ca, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, + }) + + return clientCreds, serverCreds, nil +} + // Stop releases all resources associated with the MockWindowsAgent. func (m *MockWindowsAgent) Stop() { <-m.Started From 6229cb7d6107a96bf1d1640c5c4990110f16552f Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 16 Apr 2024 12:57:54 -0300 Subject: [PATCH 08/41] Updates internal dependencies --- windows-agent/go.mod | 2 +- windows-agent/go.sum | 4 ++-- wsl-pro-service/go.mod | 2 +- wsl-pro-service/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/windows-agent/go.mod b/windows-agent/go.mod index 5a51b0e72..78e03d31c 100644 --- a/windows-agent/go.mod +++ b/windows-agent/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.2 require ( github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922 github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3 - github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240322101935-3e73eb563dc3 + github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3 github.com/canonical/ubuntu-pro-for-wsl/mocks v0.0.0-20240322101935-3e73eb563dc3 github.com/canonical/ubuntu-pro-for-wsl/storeapi/go-wrapper/microsoftstore v0.0.0-20240322101935-3e73eb563dc3 diff --git a/windows-agent/go.sum b/windows-agent/go.sum index e185df03b..ead63545f 100644 --- a/windows-agent/go.sum +++ b/windows-agent/go.sum @@ -4,8 +4,8 @@ github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922 github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922/go.mod h1:3N+AXDrTJvuwy+F9uIDzi2g9xqpeZpxfwobtn84JHEQ= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3 h1:HSc1LV3CyGIXHvIL/VViAf6xixLLFdJGPd4J831HTa4= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3/go.mod h1:Cud84UloJCb6yTabFFqxXiGEqzVc1g6e7emwPNDKEVM= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240322101935-3e73eb563dc3 h1:sc7pd9vWQeO613dRpQxQ7Xpg1Jg6BGekVLy2x1Nk5CM= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240322101935-3e73eb563dc3/go.mod h1:LN6n7AB95DoWVnT+sMjY5nSgpoFdanCymQh6eiFwFPo= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e h1:utI/UdCQ1i1PSron5unrcmWeVar8seLCAlm/W+WRqDE= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3 h1:QJUHhH7n5EFpoKkHA6+CDLQobJ9LDCNVcwdw0lVEWBI= github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3/go.mod h1:E3fwYB344DobWyl0ZSHSn/Se5ol03wIz8OewjhuobNk= github.com/canonical/ubuntu-pro-for-wsl/mocks v0.0.0-20240322101935-3e73eb563dc3 h1:pYpuD6W80Vs+HSuMq/ROqD3JOQJ1DC9VWrX7qUz7sdE= diff --git a/wsl-pro-service/go.mod b/wsl-pro-service/go.mod index f9baaf251..50d34f5f0 100644 --- a/wsl-pro-service/go.mod +++ b/wsl-pro-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.2 require ( github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896 - github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240412151705-5e7385e21896 + github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 diff --git a/wsl-pro-service/go.sum b/wsl-pro-service/go.sum index 3f1086787..0373be612 100644 --- a/wsl-pro-service/go.sum +++ b/wsl-pro-service/go.sum @@ -1,7 +1,7 @@ github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896 h1:HcDXZSv7jO0M6ZBeNnAOjq6Bm9ieaKcm8zj8z8681BU= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896/go.mod h1:8cMWt+GvjqWKV/HQLEkCM5bnBWMiXnfKn0UVjgNoW6U= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240412151705-5e7385e21896 h1:zLtASZGa1l6rbUVAXw6nCqEYJFLDPSctYFlvrmaeKPk= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240412151705-5e7385e21896/go.mod h1:1leBVOCX3C89rB+4XMpRbkzgfb/fXPFrc2V6AxKZ7vg= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e h1:utI/UdCQ1i1PSron5unrcmWeVar8seLCAlm/W+WRqDE= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= From bf56f64ab88f9536fe2cb13024e10f908f0a8c01 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:46:54 -0300 Subject: [PATCH 09/41] Move CreateRootCA to the top And remove an empty line in the rootCert template creation. --- common/certs/certs.go | 59 +++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index 001376840..deae7d03e 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -24,6 +24,35 @@ import ( // - https://github.com/grpc/grpc-go/blob/master/examples/features/encryption/mTLS // - and https://gist.github.com/annanay25/43e3846e21b30818d8dcd5f9987e852d. +// CreateRootCA creates a new root certificate authority (CA) certificate and private key pair with the serial number and common name provided. +// Only the cert is written into destDir in the PEM format. Being a CA, the certificate and private key returned can be used to sign other certificates. +func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCert *x509.Certificate, rootKey *ecdsa.PrivateKey, err error) { + // generate a new key-pair for the root certificate based on the P256 elliptic curve + rootKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate random key: %v", err) + } + + rootCertTmpl := template(commonName, serialNo) + // this cert will be the CA that we will use to sign the server cert + rootCertTmpl.IsCA = true + rootCertTmpl.Subject.CommonName = commonName + " CA" + rootCertTmpl.KeyUsage = x509.KeyUsageCertSign + + rootCert, rootDER, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) + if err != nil { + return nil, nil, err + } + + // Write the CA certificate to disk. + // Notice that we don't write the private key to disk. Only the caller of this function can create other certificates signed by this root CA. + if err = writeCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { + return nil, nil, err + } + + return rootCert, rootKey, nil +} + // CreateTLSCertificateSignedBy creates a certificate and key pair usable for authentication signed by the root certificate authority (root CA) certificate and key provided and write them into destDir in the PEM format. func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACert *x509.Certificate, rootCAKey *ecdsa.PrivateKey, destDir string) (tlsCert *tls.Certificate, err error) { decorate.OnError(&err, "could not create root signed certificate pair for %s:", name) @@ -68,36 +97,6 @@ func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACe }, nil } -// CreateRootCA creates a new root certificate authority (CA) certificate and private key pair with the serial number and common name provided. -// Only the cert is written into destDir in the PEM format. Being a CA, the certificate and private key returned can be used to sign other certificates. -func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCert *x509.Certificate, rootKey *ecdsa.PrivateKey, err error) { - // generate a new key-pair for the root certificate based on the P256 elliptic curve - rootKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate random key: %v", err) - } - - rootCertTmpl := template(commonName, serialNo) - - // this cert will be the CA that we will use to sign the server cert - rootCertTmpl.IsCA = true - rootCertTmpl.Subject.CommonName = commonName + " CA" - rootCertTmpl.KeyUsage = x509.KeyUsageCertSign - - rootCert, rootDER, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) - if err != nil { - return nil, nil, err - } - - // Write the CA certificate to disk. - // Notice that we don't write the private key to disk. Only the caller of this function can create other certificates signed by this root CA. - if err = writeCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { - return nil, nil, err - } - - return rootCert, rootKey, nil -} - // createCert invokes x509.CreateCertificate and returns the certificate and it's DER as bytes for serialization. func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (cert *x509.Certificate, certDER []byte, err error) { decorate.OnError(&err, "could not create certificate:") From 430cc51ff0d28378ba3e226b85ebf5dee79377f8 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:47:58 -0300 Subject: [PATCH 10/41] Retire interface{} parameter Replace with any. --- common/certs/certs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index deae7d03e..a3e0a342b 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -98,7 +98,7 @@ func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACe } // createCert invokes x509.CreateCertificate and returns the certificate and it's DER as bytes for serialization. -func createCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (cert *x509.Certificate, certDER []byte, err error) { +func createCert(template, parent *x509.Certificate, pub, parentPriv any) (cert *x509.Certificate, certDER []byte, err error) { decorate.OnError(&err, "could not create certificate:") certDER, err = x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv) From 80d2fce4db351d804fc30d28203ca873a1413bd6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:49:08 -0300 Subject: [PATCH 11/41] Shorter names for the local writers Functions are short enough so the short names won't convey any loss of context. --- common/certs/certs.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index a3e0a342b..2ada9d048 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -127,25 +127,25 @@ func template(commonName string, serial *big.Int) *x509.Certificate { // writeCert writes a certificate to disk in PEM format to the given filename. func writeCert(filename string, DER []byte) error { - certOut, err := os.Create(filename) + w, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to open %q for writing: %v", filename, err) } - defer certOut.Close() - return pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: DER}) + defer w.Close() + return pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: DER}) } // writeKey writes a private key to disk in PEM format to the given filename. func writeKey(filename string, priv *ecdsa.PrivateKey) error { - keyOut, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + k, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("failed to open %q for writing: %v", filename, err) } - defer keyOut.Close() + defer k.Close() p, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return fmt.Errorf("failed to marshal private key: %v", err) } - return pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: p}) + return pem.Encode(k, &pem.Block{Type: "PRIVATE KEY", Bytes: p}) } From d114bd3f9734a89d91a799eb822e6586746c8e7b Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:50:02 -0300 Subject: [PATCH 12/41] Empty line after writer.Close --- common/certs/certs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/certs/certs.go b/common/certs/certs.go index 2ada9d048..74bf04f23 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -132,6 +132,7 @@ func writeCert(filename string, DER []byte) error { return fmt.Errorf("failed to open %q for writing: %v", filename, err) } defer w.Close() + return pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: DER}) } @@ -142,6 +143,7 @@ func writeKey(filename string, priv *ecdsa.PrivateKey) error { return fmt.Errorf("failed to open %q for writing: %v", filename, err) } defer k.Close() + p, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return fmt.Errorf("failed to marshal private key: %v", err) From 4079aad931294ce06983ee40b38e402fb4c5b758 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:52:17 -0300 Subject: [PATCH 13/41] Fix singular first-person comment about AuthorityKeyId And remove the empty line above, as we are still arranging the cert template. --- common/certs/certs.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index 74bf04f23..768da24be 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -63,10 +63,9 @@ func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACe } certTmpl := template(certCN, serial) - // Customizing the usage for client and server certificates: - // I only got the certificates signed by the root one when I manually set the AuthorityKeyId, - // even though x509.CreateCertificate documentation says it will use it, if present. + // Even though x509.CreateCertificate documentation says it will use it, if present, + // it seems we need to set AuthorityKeyId manually to make the verification work. certTmpl.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} certTmpl.AuthorityKeyId = rootCACert.SubjectKeyId From c5742a0e8a728d83363b86f82265ac3392a4636f Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:55:35 -0300 Subject: [PATCH 14/41] Comment about the suage of template as parent to make rootCA self-signed --- common/certs/certs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/certs/certs.go b/common/certs/certs.go index 768da24be..320ae46fc 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -39,6 +39,7 @@ func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCer rootCertTmpl.Subject.CommonName = commonName + " CA" rootCertTmpl.KeyUsage = x509.KeyUsageCertSign + // We pass the template as the parent as well so that the certificate is self-signed. rootCert, rootDER, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey) if err != nil { return nil, nil, err From b593b53c624dd42348b7413952afb17a1139a2fa Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:58:43 -0300 Subject: [PATCH 15/41] Assert the existence of the server_key.pem file as well --- common/certs/certs_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/certs/certs_test.go b/common/certs/certs_test.go index 01730a305..1effcbbb7 100644 --- a/common/certs/certs_test.go +++ b/common/certs/certs_test.go @@ -65,6 +65,7 @@ func TestCreateTLSCertificateSignedBy(t *testing.T) { require.NoError(t, err, "CreateTLSCertificateSignedBy failed") require.NotNil(t, tlsCert, "CreateTLSCertificateSignedBy returned a nil certificate") require.FileExists(t, filepath.Join(dir, "server_cert.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") + require.FileExists(t, filepath.Join(dir, "server_key.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") }) } } From bfecafbad360cf610f20d1ea55780dbe6a3f1b89 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 09:59:05 -0300 Subject: [PATCH 16/41] Remove unused test case parameters in TestCreateRootCA --- common/certs/certs_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/common/certs/certs_test.go b/common/certs/certs_test.go index 1effcbbb7..33bd3aeaf 100644 --- a/common/certs/certs_test.go +++ b/common/certs/certs_test.go @@ -15,10 +15,9 @@ func TestCreateTLSCertificateSignedBy(t *testing.T) { testcases := map[string]struct { missingSerialNumber bool - - rootIsNotCA bool - breakCertPem bool - breakKeyPem bool + rootIsNotCA bool + breakCertPem bool + breakKeyPem bool wantErr bool }{ @@ -75,9 +74,7 @@ func TestCreateRooCA(t *testing.T) { testcases := map[string]struct { missingSerialNumber bool - rootIsNotCA bool breakCertPem bool - breakKeyPem bool wantErr bool }{ From 6e38cb81394e6fdf638c1a5212b61cebe1fd8774 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:00:37 -0300 Subject: [PATCH 17/41] Don`t override the package comment in proservices/certs.go --- windows-agent/internal/proservices/certs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index d818c1f8f..4e0e5ba62 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -1,4 +1,3 @@ -// Package proservices is in charge of managing the GRPC services and all business-logic side. package proservices import ( From beeba7a66f6aff3599dc0502d1e6562f72dbd864 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:01:26 -0300 Subject: [PATCH 18/41] NewTLSCertificates in the top Anything else is details of what this function achieves --- windows-agent/internal/proservices/certs.go | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index 4e0e5ba62..a2b1869c2 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -12,24 +12,6 @@ import ( "github.com/ubuntu/decorate" ) -// Certs conveniently holds the root CA and server certificates to make it easy to create a TLS config. -type Certs struct { - RootCA *x509.Certificate - ServerCert tls.Certificate -} - -// ServerTLSConfig returns a TLS config for servers that require and verify client certificates. -func (c Certs) ServerTLSConfig() *tls.Config { - ca := x509.NewCertPool() - ca.AddCert(c.RootCA) - return &tls.Config{ - Certificates: []tls.Certificate{c.ServerCert}, - ClientCAs: ca, - ClientAuth: tls.RequireAndVerifyClientCert, - MinVersion: tls.VersionTLS13, - } -} - // NewTLSCertificates creates a root CA and a server self-signed certificates and writes them into destDir. func NewTLSCertificates(destDir string) (c Certs, err error) { decorate.OnError(&err, "could not create TLS credentials:") @@ -59,3 +41,21 @@ func NewTLSCertificates(destDir string) (c Certs, err error) { return Certs{RootCA: rootCert, ServerCert: *serverCert}, nil } + +// ServerTLSConfig returns a TLS config for servers that require and verify client certificates. +func (c Certs) ServerTLSConfig() *tls.Config { + ca := x509.NewCertPool() + ca.AddCert(c.RootCA) + return &tls.Config{ + Certificates: []tls.Certificate{c.ServerCert}, + ClientCAs: ca, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, + } +} + +// Certs conveniently holds the root CA and server certificates to make it easy to create a TLS config. +type Certs struct { + RootCA *x509.Certificate + ServerCert tls.Certificate +} From 8bc73b2b9c3633623a0aac045772d3c437a8f3b7 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:02:00 -0300 Subject: [PATCH 19/41] Fix typo /s/acess/access --- windows-agent/internal/proservices/certs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index a2b1869c2..ed4995693 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -32,7 +32,7 @@ func NewTLSCertificates(destDir string) (c Certs, err error) { if err != nil { return Certs{}, err } - // We won't store the TLS client certificate, because only the agent should acess this function and it's not interested in the client TLS certificate. + // We won't store the TLS client certificate, because only the agent should access this function and it's not interested in the client TLS certificate. // But we still need to write them to disk, so clients can construct their TLS configs from there. _, err = certs.CreateTLSCertificateSignedBy("client", common.GRPCServerNameOverride, serial.Lsh(serial, 3), rootCert, rootKey, destDir) if err != nil { From 0b1fda01d0369291d2a05d905bcb28f1acdbb9f9 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:09:07 -0300 Subject: [PATCH 20/41] Clearer test case title for TestNewTLSCertificates when ... ... destination directory does not exist. Also sprinkled some empty lines for better readability in between success and error cases declaration and after t.Parallel(). --- windows-agent/internal/proservices/certs_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/windows-agent/internal/proservices/certs_test.go b/windows-agent/internal/proservices/certs_test.go index 006c6f1eb..4992a66ab 100644 --- a/windows-agent/internal/proservices/certs_test.go +++ b/windows-agent/internal/proservices/certs_test.go @@ -12,22 +12,24 @@ import ( func TestNewTLSCertificates(t *testing.T) { t.Parallel() testcases := map[string]struct { - breakDestDir bool - breakKeyFile string + inexistentDestDir bool + breakKeyFile string wantErr bool }{ "Success": {}, - "Error when the destination directory cannot be written into": {breakDestDir: true, wantErr: true}, - "Error when the server private key cannot be written": {breakKeyFile: "server_key.pem", wantErr: true}, - "Error when the client private key cannot be written": {breakKeyFile: "client_key.pem", wantErr: true}, + + "Error when the destination directory does not exist": {inexistentDestDir: true, wantErr: true}, + "Error when the server private key cannot be written": {breakKeyFile: "server_key.pem", wantErr: true}, + "Error when the client private key cannot be written": {breakKeyFile: "client_key.pem", wantErr: true}, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { t.Parallel() + dir := t.TempDir() - if tc.breakDestDir { + if tc.inexistentDestDir { dir = filepath.Join(dir, "inexistent") } From e36a3e52349fbdbdec1bc0f87eecae9cfa6f7e63 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:12:04 -0300 Subject: [PATCH 21/41] Assert that the result of NewTLSCertificates is not empty --- windows-agent/internal/proservices/certs_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows-agent/internal/proservices/certs_test.go b/windows-agent/internal/proservices/certs_test.go index 4992a66ab..d2ece73a0 100644 --- a/windows-agent/internal/proservices/certs_test.go +++ b/windows-agent/internal/proservices/certs_test.go @@ -38,12 +38,13 @@ func TestNewTLSCertificates(t *testing.T) { require.NoError(t, err, "Setup: could not write directory that should break %s", tc.breakKeyFile) } - _, err := ps.NewTLSCertificates(dir) + c, err := ps.NewTLSCertificates(dir) if tc.wantErr { require.Error(t, err, "NewTLSCertificates should have failed") return } require.NoError(t, err, "NewTLSCertificates failed") + require.NotEmpty(t, c, "NewTLSCertificates should have returned a non-empty value") }) } } From 44c87acbd0a68ea2b78f3cf092d8ea5bc3811bf2 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:34:46 -0300 Subject: [PATCH 22/41] Makes proservices/certs functionallity private It requires turning certs_test into internal/unit tests. We don't tipically do that, but we don't want to make the functions public just for the sake of testing if they are not called outside of the proservices package. Yet, that functionality is complex enough to require testing. --- windows-agent/internal/proservices/certs.go | 30 +++++++++---------- .../{certs_test.go => internal_test.go} | 5 ++-- .../internal/proservices/proservices.go | 4 +-- 3 files changed, 19 insertions(+), 20 deletions(-) rename windows-agent/internal/proservices/{certs_test.go => internal_test.go} (89%) diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index ed4995693..046ebfe25 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -12,50 +12,50 @@ import ( "github.com/ubuntu/decorate" ) -// NewTLSCertificates creates a root CA and a server self-signed certificates and writes them into destDir. -func NewTLSCertificates(destDir string) (c Certs, err error) { +// newTLSCertificates creates a self-signed root CA, agent and clients certificates and writes them into destDir. +func newTLSCertificates(destDir string) (c agentCerts, err error) { decorate.OnError(&err, "could not create TLS credentials:") // generates a pseudo-random serial number for the root CA certificate. serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { - return Certs{}, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) + return agentCerts{}, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) } rootCert, rootKey, err := certs.CreateRootCA(common.GRPCServerNameOverride, serial, destDir) if err != nil { - return Certs{}, err + return agentCerts{}, err } // Create and write the server and client certificates signed by the root certificate created above. serverCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) if err != nil { - return Certs{}, err + return agentCerts{}, err } // We won't store the TLS client certificate, because only the agent should access this function and it's not interested in the client TLS certificate. // But we still need to write them to disk, so clients can construct their TLS configs from there. _, err = certs.CreateTLSCertificateSignedBy("client", common.GRPCServerNameOverride, serial.Lsh(serial, 3), rootCert, rootKey, destDir) if err != nil { - return Certs{}, err + return agentCerts{}, err } - return Certs{RootCA: rootCert, ServerCert: *serverCert}, nil + return agentCerts{rootCA: rootCert, serverCert: *serverCert}, nil } -// ServerTLSConfig returns a TLS config for servers that require and verify client certificates. -func (c Certs) ServerTLSConfig() *tls.Config { +// agentTLSConfig returns a TLS config for servers that require and verify client certificates. +func (c agentCerts) agentTLSConfig() *tls.Config { ca := x509.NewCertPool() - ca.AddCert(c.RootCA) + ca.AddCert(c.rootCA) return &tls.Config{ - Certificates: []tls.Certificate{c.ServerCert}, + Certificates: []tls.Certificate{c.serverCert}, ClientCAs: ca, ClientAuth: tls.RequireAndVerifyClientCert, MinVersion: tls.VersionTLS13, } } -// Certs conveniently holds the root CA and server certificates to make it easy to create a TLS config. -type Certs struct { - RootCA *x509.Certificate - ServerCert tls.Certificate +// agentCerts conveniently holds the root CA and server certificates to make it easy to create a TLS config. +type agentCerts struct { + rootCA *x509.Certificate + serverCert tls.Certificate } diff --git a/windows-agent/internal/proservices/certs_test.go b/windows-agent/internal/proservices/internal_test.go similarity index 89% rename from windows-agent/internal/proservices/certs_test.go rename to windows-agent/internal/proservices/internal_test.go index d2ece73a0..e362919e5 100644 --- a/windows-agent/internal/proservices/certs_test.go +++ b/windows-agent/internal/proservices/internal_test.go @@ -1,11 +1,10 @@ -package proservices_test +package proservices import ( "os" "path/filepath" "testing" - ps "github.com/canonical/ubuntu-pro-for-wsl/windows-agent/internal/proservices" "github.com/stretchr/testify/require" ) @@ -38,7 +37,7 @@ func TestNewTLSCertificates(t *testing.T) { require.NoError(t, err, "Setup: could not write directory that should break %s", tc.breakKeyFile) } - c, err := ps.NewTLSCertificates(dir) + c, err := newTLSCertificates(dir) if tc.wantErr { require.Error(t, err, "NewTLSCertificates should have failed") return diff --git a/windows-agent/internal/proservices/proservices.go b/windows-agent/internal/proservices/proservices.go index d198412cf..63faa18b4 100644 --- a/windows-agent/internal/proservices/proservices.go +++ b/windows-agent/internal/proservices/proservices.go @@ -122,12 +122,12 @@ func New(ctx context.Context, publicDir, privateDir string, args ...Option) (s M if err := os.MkdirAll(destDir, 0700); err != nil { return s, fmt.Errorf("failed to create certificates directory: %s", err) } - certs, err := NewTLSCertificates(destDir) + certs, err := newTLSCertificates(destDir) if err != nil { return s, fmt.Errorf("failed to create certificates: %s", err) } - s.creds = credentials.NewTLS(certs.ServerTLSConfig()) + s.creds = credentials.NewTLS(certs.agentTLSConfig()) return s, err } From e165d14d3cd61d22fefcd00156ed34773ad853ab Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:36:08 -0300 Subject: [PATCH 23/41] s/ps/s for the return of proservice.New in tests For consistency with the existing test code --- windows-agent/internal/proservices/proservices_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index 6a2075f92..bfd149aed 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -111,11 +111,11 @@ func TestRegisterGRPCServices(t *testing.T) { publicDir := t.TempDir() - ps, err := proservices.New(ctx, publicDir, t.TempDir(), proservices.WithRegistry(registry.NewMock())) + s, err := proservices.New(ctx, publicDir, t.TempDir(), proservices.WithRegistry(registry.NewMock())) require.NoError(t, err, "Setup: New should return no error") - defer ps.Stop(ctx) + defer s.Stop(ctx) - server := ps.RegisterGRPCServices(context.Background()) + server := s.RegisterGRPCServices(context.Background()) info := server.GetServiceInfo() _, ok := info["agentapi.UI"] From af6d2f682bbd2f06f31c7fe6a07d20b84d54cd31 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:39:35 -0300 Subject: [PATCH 24/41] Use t.Cleanup to ensure the server stopped. --- windows-agent/internal/proservices/proservices_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index bfd149aed..dc71123fe 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -132,13 +132,18 @@ func TestRegisterGRPCServices(t *testing.T) { require.NoError(t, err, "Setup: could not create a listener") defer lis.Close() + serverDone := make(chan struct{}) go func() { + defer close(serverDone) err := server.Serve(lis) if err != nil { t.Logf("Serve exited with error: %v", err) } }() - defer server.Stop() + t.Cleanup(func() { + server.Stop() + <-serverDone + }) // Create a client connection to the server. addr := lis.Addr().String() From 2edc3eb9e4d726c4cb4031e5ec0ac2f195edf023 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:41:51 -0300 Subject: [PATCH 25/41] Oneliner write file to break the certs dir in test --- windows-agent/internal/proservices/proservices_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index dc71123fe..ec73ca1a2 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -67,9 +67,7 @@ func TestNew(t *testing.T) { require.NoError(t, err, "Setup: could not write directory where database wants to put a file") } if tc.breakCertificatesDir { - f, err := os.Create(filepath.Join(publicDir, common.CertificatesDir)) - require.NoError(t, err, "Setup: could not create the file that should break writing the certificates") - f.Close() + require.NoError(t, os.WriteFile(filepath.Join(publicDir, common.CertificatesDir), []byte{}, 0600), "Setup: could not create the file that should break writing the certificates") } if tc.breakCA { require.NoError(t, os.MkdirAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem"), 0700), "Setup: could break ca_cert.pem") From 4c4424d61a8cc4ef1a2cd47a2a092847d1e11be0 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:43:38 -0300 Subject: [PATCH 26/41] Use insecure channel as default in TestRegisterGRPCServices Using the defaul to avoid an else{} clause. The alternative is more expensive which is generating and writing certificates to disk. --- windows-agent/internal/proservices/proservices_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index ec73ca1a2..f7b210d16 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -145,10 +145,8 @@ func TestRegisterGRPCServices(t *testing.T) { // Create a client connection to the server. addr := lis.Addr().String() - var creds credentials.TransportCredentials - if tc.insecureClient { - creds = insecure.NewCredentials() - } else { + creds := insecure.NewCredentials() + if !tc.insecureClient { creds = loadClientCertificates(t, filepath.Join(publicDir, common.CertificatesDir)) } conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds)) From 26e542a3c0e89cbaf96202d8b3751c4bf5de308a Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:45:52 -0300 Subject: [PATCH 27/41] Fix long timeout leftover when calling Ping in tests. --- windows-agent/internal/proservices/proservices_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index f7b210d16..17d4e4456 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -155,7 +155,7 @@ func TestRegisterGRPCServices(t *testing.T) { c := agentapi.NewUIClient(conn) // Test the client connection. - ctx, cancel = context.WithTimeout(context.Background(), 200*time.Hour) + ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() _, err = c.Ping(ctx, &agentapi.Empty{}) From e63b2911c5209ba5749b7c6ca2902c939397093b Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:46:42 -0300 Subject: [PATCH 28/41] Don't check err before requrire clause as require will do it. --- windows-agent/internal/proservices/proservices_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index 17d4e4456..24ccdcf32 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -172,16 +172,13 @@ func loadClientCertificates(t *testing.T, certsDir string) credentials.Transport t.Helper() cert, err := tls.LoadX509KeyPair(filepath.Join(certsDir, "client_cert.pem"), filepath.Join(certsDir, "client_key.pem")) - if err != nil { - require.NoError(t, err, "failed to load client cert: %v", err) - } + require.NoError(t, err, "failed to load client cert: %v", err) ca := x509.NewCertPool() caFilePath := filepath.Join(certsDir, "ca_cert.pem") caBytes, err := os.ReadFile(caFilePath) - if err != nil { - require.NoError(t, err, "failed to read ca cert %q: %v", caFilePath, err) - } + require.NoError(t, err, "failed to read ca cert %q: %v", caFilePath, err) + if ok := ca.AppendCertsFromPEM(caBytes); !ok { require.NoError(t, err, "failed to parse %q", caFilePath) } From c502089a4f74242000193891b497ba2b68e88a5e Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:48:12 -0300 Subject: [PATCH 29/41] require that AppendCertsFromPEM returns true --- windows-agent/internal/proservices/proservices_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index 24ccdcf32..223890d38 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -179,9 +179,7 @@ func loadClientCertificates(t *testing.T, certsDir string) credentials.Transport caBytes, err := os.ReadFile(caFilePath) require.NoError(t, err, "failed to read ca cert %q: %v", caFilePath, err) - if ok := ca.AppendCertsFromPEM(caBytes); !ok { - require.NoError(t, err, "failed to parse %q", caFilePath) - } + require.True(t, ca.AppendCertsFromPEM(caBytes), "failed to parse %q", caFilePath) tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS13, From cffcd0ec5cbd11a69830e6f3c69528b6e43c06a6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:49:24 -0300 Subject: [PATCH 30/41] newTLSConfigFromDir is more precise than tlsConfigFromDir --- wsl-pro-service/internal/daemon/daemon.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wsl-pro-service/internal/daemon/daemon.go b/wsl-pro-service/internal/daemon/daemon.go index 055fc6933..a6ae27ed8 100644 --- a/wsl-pro-service/internal/daemon/daemon.go +++ b/wsl-pro-service/internal/daemon/daemon.go @@ -287,7 +287,7 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error log.Infof(ctx, "Daemon: starting connection to Windows Agent via %s", addr) - tlsConfig, err := tlsConfigFromDir(d.certsPath) + tlsConfig, err := newTLSConfigFromDir(d.certsPath) if err != nil { return nil, err } @@ -309,8 +309,8 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error return streams.NewServer(ctx, d.system, conn), nil } -// tlsConfigFromDir loads certificates from the provided certs path and returns a matching tls.Config. -func tlsConfigFromDir(certsPath string) (conf *tls.Config, err error) { +// newTLSConfigFromDir loads certificates from the provided certs path and returns a matching tls.Config. +func newTLSConfigFromDir(certsPath string) (conf *tls.Config, err error) { decorate.OnError(&err, "could not load TLS config") cert, err := tls.LoadX509KeyPair(filepath.Join(certsPath, "client_cert.pem"), filepath.Join(certsPath, "client_key.pem")) if err != nil { From 66178a5d82ace5f2bbb2f3b30b7fe81ef59ca3b8 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:49:40 -0300 Subject: [PATCH 31/41] Empty line after decorate --- wsl-pro-service/internal/daemon/daemon.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wsl-pro-service/internal/daemon/daemon.go b/wsl-pro-service/internal/daemon/daemon.go index a6ae27ed8..a4cb26e72 100644 --- a/wsl-pro-service/internal/daemon/daemon.go +++ b/wsl-pro-service/internal/daemon/daemon.go @@ -312,6 +312,7 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error // newTLSConfigFromDir loads certificates from the provided certs path and returns a matching tls.Config. func newTLSConfigFromDir(certsPath string) (conf *tls.Config, err error) { decorate.OnError(&err, "could not load TLS config") + cert, err := tls.LoadX509KeyPair(filepath.Join(certsPath, "client_cert.pem"), filepath.Join(certsPath, "client_key.pem")) if err != nil { return nil, err From 76f18517a96db6490ab0ab7b8ae1332c0379dfbe Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:53:02 -0300 Subject: [PATCH 32/41] Don't accept ErrNotExist when deleting certs in test setup --- wsl-pro-service/internal/daemon/daemon_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wsl-pro-service/internal/daemon/daemon_test.go b/wsl-pro-service/internal/daemon/daemon_test.go index e534937ff..0561948c0 100644 --- a/wsl-pro-service/internal/daemon/daemon_test.go +++ b/wsl-pro-service/internal/daemon/daemon_test.go @@ -122,18 +122,15 @@ func TestServe(t *testing.T) { defer agent.Stop() if tc.missingCertsDir { - err := os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir)) - require.NotErrorIs(t, err, os.ErrNotExist, "Setup: could not remove certificates") + require.NoError(t, os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir)), "Setup: could not remove certificates") } if tc.missingCaCert { - err := os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem")) - require.NotErrorIs(t, err, os.ErrNotExist, "Setup: could not remove ca_cert.pem") + require.NoError(t, os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem")), "Setup: could not remove ca_cert.pem") } if tc.breakPortFile { - err := os.RemoveAll(publicDir) - require.NoError(t, err, "Setup: could not remove port file") + require.NoError(t, os.RemoveAll(publicDir), "Setup: could not remove port file") } if tc.breakWindowsHostAddress { From a24f2eac913a7e2133e6398e2485770328bc1495 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 10:53:49 -0300 Subject: [PATCH 33/41] Empty line after a require clause --- wsl-pro-service/internal/testutils/mock_agent.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wsl-pro-service/internal/testutils/mock_agent.go b/wsl-pro-service/internal/testutils/mock_agent.go index 14cf1fb0a..f8ccdc393 100644 --- a/wsl-pro-service/internal/testutils/mock_agent.go +++ b/wsl-pro-service/internal/testutils/mock_agent.go @@ -53,6 +53,7 @@ func NewMockWindowsAgent(t *testing.T, ctx context.Context, publicDir string) *M clientCreds, serverCreds, err := agentTLSCreds(filepath.Join(publicDir, common.CertificatesDir)) require.NoError(t, err, "Setup: could not create TLS certificates and config") + m := MockWindowsAgent{ Listener: lis, Server: grpc.NewServer(grpc.Creds(serverCreds)), From e03d4a67f3c40e8cc1f58db81aaf4a4fee61033f Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 11:04:28 -0300 Subject: [PATCH 34/41] Turn mock_agent.agentTLSCreds into an explicit test helper Making it shorter by using require instead of if-err-return-err Also renamed the certs and credentails to be more precise to our use case: server -> agent / client -> wsl-pro-service --- .../internal/testutils/mock_agent.go | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/wsl-pro-service/internal/testutils/mock_agent.go b/wsl-pro-service/internal/testutils/mock_agent.go index f8ccdc393..dccee9c63 100644 --- a/wsl-pro-service/internal/testutils/mock_agent.go +++ b/wsl-pro-service/internal/testutils/mock_agent.go @@ -51,8 +51,7 @@ func NewMockWindowsAgent(t *testing.T, ctx context.Context, publicDir string) *M lis, err := cfg.Listen(ctx, "tcp4", "localhost:0") require.NoError(t, err, "Setup: could not listen to agent address") - clientCreds, serverCreds, err := agentTLSCreds(filepath.Join(publicDir, common.CertificatesDir)) - require.NoError(t, err, "Setup: could not create TLS certificates and config") + clientCreds, serverCreds := agentTLSCreds(t, filepath.Join(publicDir, common.CertificatesDir)) m := MockWindowsAgent{ Listener: lis, @@ -93,45 +92,40 @@ func NewMockWindowsAgent(t *testing.T, ctx context.Context, publicDir string) *M return &m } -func agentTLSCreds(destDir string) (clientCreds, serverCreds credentials.TransportCredentials, err error) { +// agentTLSCreds is a helper that creates a pair of TLS credentials for the agent and the WSL Pro service for testing. +func agentTLSCreds(t *testing.T, destDir string) (wslProService, agentCreds credentials.TransportCredentials) { + t.Helper() + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) - } - if err := os.MkdirAll(destDir, 0700); err != nil { - return nil, nil, fmt.Errorf("failed to create certificates directory: %v", err) - } + require.NoError(t, err, "failed to generate serial number for the CA cert", err) + + require.NoError(t, os.MkdirAll(destDir, 0700), "failed to create certificates directory", err) + rootCert, rootKey, err := certs.CreateRootCA("UP4W Test", serial, destDir) - if err != nil { - return nil, nil, err - } + require.NoError(t, err, "failed to create root CA", err) // Create and write the server and client certificates signed by the root certificate created above. - serverCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) - if err != nil { - return nil, nil, err - } - clientCert, err := certs.CreateTLSCertificateSignedBy("client", "test-client", serial.Lsh(serial, 3), rootCert, rootKey, destDir) - if err != nil { - return nil, nil, err - } + agentCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) + require.NoError(t, err, "failed to create agent certificate", err) + wslProServiceCert, err := certs.CreateTLSCertificateSignedBy("client", "wsl-pro-service-test", serial.Lsh(serial, 3), rootCert, rootKey, destDir) + require.NoError(t, err, "failed to create WSL Pro service certificate", err) ca := x509.NewCertPool() ca.AddCert(rootCert) - clientCreds = credentials.NewTLS(&tls.Config{ + wslProService = credentials.NewTLS(&tls.Config{ MinVersion: tls.VersionTLS13, ServerName: common.GRPCServerNameOverride, - Certificates: []tls.Certificate{*clientCert}, + Certificates: []tls.Certificate{*wslProServiceCert}, RootCAs: ca, }) - serverCreds = credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{*serverCert}, + agentCreds = credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{*agentCert}, ClientCAs: ca, ClientAuth: tls.RequireAndVerifyClientCert, MinVersion: tls.VersionTLS13, }) - return clientCreds, serverCreds, nil + return wslProService, agentCreds } // Stop releases all resources associated with the MockWindowsAgent. From d3b754df1af72654c6b33f1ba41301229d14d640 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 17 Apr 2024 11:05:31 -0300 Subject: [PATCH 35/41] Be explicit that the happy path returns a nil error --- windows-agent/internal/proservices/proservices.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows-agent/internal/proservices/proservices.go b/windows-agent/internal/proservices/proservices.go index 63faa18b4..05fbc4186 100644 --- a/windows-agent/internal/proservices/proservices.go +++ b/windows-agent/internal/proservices/proservices.go @@ -128,7 +128,7 @@ func New(ctx context.Context, publicDir, privateDir string, args ...Option) (s M } s.creds = credentials.NewTLS(certs.agentTLSConfig()) - return s, err + return s, nil } // Stop deallocates resources in the services. From e23b2ae67d4dbe7a4f7301d4249e9c5fe1b298e6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 09:41:21 -0300 Subject: [PATCH 36/41] Move TestCreateRootCA to the top So test code follows the same order as the funcitons themselves --- common/certs/certs_test.go | 70 +++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/common/certs/certs_test.go b/common/certs/certs_test.go index 33bd3aeaf..28470aa81 100644 --- a/common/certs/certs_test.go +++ b/common/certs/certs_test.go @@ -10,36 +10,25 @@ import ( "github.com/stretchr/testify/require" ) -func TestCreateTLSCertificateSignedBy(t *testing.T) { +func TestCreateRooCA(t *testing.T) { t.Parallel() testcases := map[string]struct { missingSerialNumber bool - rootIsNotCA bool breakCertPem bool - breakKeyPem bool wantErr bool }{ "Success": {}, - "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, - "Error when the signing certificate is not an authority": {rootIsNotCA: true, wantErr: true}, - "Error when the cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, - "Error when the key.pem file cannot be written": {breakKeyPem: true, wantErr: true}, + "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, + "Error when the ca_cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { t.Parallel() - rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", new(big.Int).SetInt64(1), t.TempDir()) - require.NoError(t, err, "Setup: failed to generate root CA cert") - if tc.rootIsNotCA { - rootCert.IsCA = false - rootCert.AuthorityKeyId = nil - } - var testSerial *big.Int if !tc.missingSerialNumber { testSerial = new(big.Int).SetInt64(1) @@ -48,46 +37,53 @@ func TestCreateTLSCertificateSignedBy(t *testing.T) { dir := t.TempDir() if tc.breakCertPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") - } - - if tc.breakKeyPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700), "Setup: failed to create a directory that should break key.pem") + require.NoError(t, os.MkdirAll(filepath.Join(dir, "ca_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") } - tlsCert, err := certs.CreateTLSCertificateSignedBy("server", "test-server-cn", testSerial, rootCert, rootKey, dir) + rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", testSerial, dir) if tc.wantErr { - require.Error(t, err, "CreateTLSCertificateSignedBy should have failed") + require.Error(t, err, "CreateRootCA should have failed") return } - require.NoError(t, err, "CreateTLSCertificateSignedBy failed") - require.NotNil(t, tlsCert, "CreateTLSCertificateSignedBy returned a nil certificate") - require.FileExists(t, filepath.Join(dir, "server_cert.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") - require.FileExists(t, filepath.Join(dir, "server_key.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") + require.NoError(t, err, "CreateRootCA failed") + require.NotNil(t, rootCert, "CreateRootCA didn't return a certificate") + require.NotNil(t, rootKey, "CreateRootCA didn't return a private key") + require.FileExists(t, filepath.Join(dir, "ca_cert.pem"), "CreateRootCA failed to write the certificate to disk") }) } } -func TestCreateRooCA(t *testing.T) { +func TestCreateTLSCertificateSignedBy(t *testing.T) { t.Parallel() testcases := map[string]struct { missingSerialNumber bool + rootIsNotCA bool breakCertPem bool + breakKeyPem bool wantErr bool }{ "Success": {}, - "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, - "Error when the ca_cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, + "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, + "Error when the signing certificate is not an authority": {rootIsNotCA: true, wantErr: true}, + "Error when the cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, + "Error when the key.pem file cannot be written": {breakKeyPem: true, wantErr: true}, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { t.Parallel() + rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", new(big.Int).SetInt64(1), t.TempDir()) + require.NoError(t, err, "Setup: failed to generate root CA cert") + if tc.rootIsNotCA { + rootCert.IsCA = false + rootCert.AuthorityKeyId = nil + } + var testSerial *big.Int if !tc.missingSerialNumber { testSerial = new(big.Int).SetInt64(1) @@ -96,19 +92,23 @@ func TestCreateRooCA(t *testing.T) { dir := t.TempDir() if tc.breakCertPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "ca_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") + require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") } - rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", testSerial, dir) + if tc.breakKeyPem { + require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700), "Setup: failed to create a directory that should break key.pem") + } + + tlsCert, err := certs.CreateTLSCertificateSignedBy("server", "test-server-cn", testSerial, rootCert, rootKey, dir) if tc.wantErr { - require.Error(t, err, "CreateRootCA should have failed") + require.Error(t, err, "CreateTLSCertificateSignedBy should have failed") return } - require.NoError(t, err, "CreateRootCA failed") - require.NotNil(t, rootCert, "CreateRootCA didn't return a certificate") - require.NotNil(t, rootKey, "CreateRootCA didn't return a private key") - require.FileExists(t, filepath.Join(dir, "ca_cert.pem"), "CreateRootCA failed to write the certificate to disk") + require.NoError(t, err, "CreateTLSCertificateSignedBy failed") + require.NotNil(t, tlsCert, "CreateTLSCertificateSignedBy returned a nil certificate") + require.FileExists(t, filepath.Join(dir, "server_cert.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") + require.FileExists(t, filepath.Join(dir, "server_key.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") }) } } From fbac4ac4162e9d678c82a022047b4753951fbf85 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 09:42:22 -0300 Subject: [PATCH 37/41] Follows "w" naming convention for the file writer inside writeKey --- common/certs/certs.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index 320ae46fc..22729e4b0 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -138,16 +138,16 @@ func writeCert(filename string, DER []byte) error { // writeKey writes a private key to disk in PEM format to the given filename. func writeKey(filename string, priv *ecdsa.PrivateKey) error { - k, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("failed to open %q for writing: %v", filename, err) } - defer k.Close() + defer w.Close() p, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { return fmt.Errorf("failed to marshal private key: %v", err) } - return pem.Encode(k, &pem.Block{Type: "PRIVATE KEY", Bytes: p}) + return pem.Encode(w, &pem.Block{Type: "PRIVATE KEY", Bytes: p}) } From b7cafdd0d2c070565932d7bdfc1b1add90e2715c Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 10:11:26 -0300 Subject: [PATCH 38/41] Adding constants to streamline the logic of the certificate... ...file naming. Constants placed into common. --- common/consts.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/consts.go b/common/consts.go index 7cef3d5d7..c05f10096 100644 --- a/common/consts.go +++ b/common/consts.go @@ -26,4 +26,19 @@ const ( // GRPCServerNameOverride is the name to override the server name in when configuring TLS for local clients. GRPCServerNameOverride = "UP4W" + + // The name of the certificate file that identifies the root certificate authority in the PEM format. + RootCACertFileName = "ca_cert.pem" + + // The file name prefix to identify the certificate/key pair of the agent in the PEM format. + AgentCertFilePrefix = "agent" + + // The file name prefix to identify the certificate/key pair of the clients (GUI and all WSL instances) in the PEM format. + ClientsCertFilePrefix = "client" + + // The file name suffix to the (public) certificate in the PEM format. + CertificateSuffix = "_cert.pem" + + // The file name suffix to the private key in the PEM format. + KeySuffix = "_key.pem" ) From 411104fedf66d1b19ed8ad4a5b25244e351cea87 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 10:14:41 -0300 Subject: [PATCH 39/41] Ensures consistent usage of the certs triple: - rootCA - agent - client - to refer to all WSL instances and the GUI (which are clients of the Agent APIs) --- common/certs/certs.go | 10 ++++----- common/certs/certs_test.go | 22 +++++++++++-------- windows-agent/internal/proservices/certs.go | 20 ++++++++--------- .../internal/proservices/internal_test.go | 7 +++--- .../internal/proservices/proservices_test.go | 6 ++--- wsl-pro-service/internal/daemon/daemon.go | 4 ++-- .../internal/daemon/daemon_test.go | 18 +++++++-------- 7 files changed, 46 insertions(+), 41 deletions(-) diff --git a/common/certs/certs.go b/common/certs/certs.go index 22729e4b0..396df9159 100644 --- a/common/certs/certs.go +++ b/common/certs/certs.go @@ -16,6 +16,7 @@ import ( "path/filepath" "time" + "github.com/canonical/ubuntu-pro-for-wsl/common" "github.com/ubuntu/decorate" ) @@ -34,7 +35,6 @@ func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCer } rootCertTmpl := template(commonName, serialNo) - // this cert will be the CA that we will use to sign the server cert rootCertTmpl.IsCA = true rootCertTmpl.Subject.CommonName = commonName + " CA" rootCertTmpl.KeyUsage = x509.KeyUsageCertSign @@ -47,7 +47,7 @@ func CreateRootCA(commonName string, serialNo *big.Int, destDir string) (rootCer // Write the CA certificate to disk. // Notice that we don't write the private key to disk. Only the caller of this function can create other certificates signed by this root CA. - if err = writeCert(filepath.Join(destDir, "ca_cert.pem"), rootDER); err != nil { + if err = writeCert(filepath.Join(destDir, common.RootCACertFileName), rootDER); err != nil { return nil, nil, err } @@ -64,7 +64,7 @@ func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACe } certTmpl := template(certCN, serial) - // Customizing the usage for client and server certificates: + // Customizing the usage for client and server applications: // Even though x509.CreateCertificate documentation says it will use it, if present, // it seems we need to set AuthorityKeyId manually to make the verification work. certTmpl.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageKeyEncipherment @@ -83,10 +83,10 @@ func CreateTLSCertificateSignedBy(name, certCN string, serial *big.Int, rootCACe return nil, fmt.Errorf("certificate verification failed: %v", err) } - if err = writeCert(filepath.Join(destDir, name+"_cert.pem"), der); err != nil { + if err = writeCert(filepath.Join(destDir, name+common.CertificateSuffix), der); err != nil { return nil, err } - if err = writeKey(filepath.Join(destDir, name+"_key.pem"), key); err != nil { + if err = writeKey(filepath.Join(destDir, name+common.KeySuffix), key); err != nil { return nil, err } diff --git a/common/certs/certs_test.go b/common/certs/certs_test.go index 28470aa81..640b746bf 100644 --- a/common/certs/certs_test.go +++ b/common/certs/certs_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/canonical/ubuntu-pro-for-wsl/common" "github.com/canonical/ubuntu-pro-for-wsl/common/certs" "github.com/stretchr/testify/require" ) @@ -21,8 +22,8 @@ func TestCreateRooCA(t *testing.T) { }{ "Success": {}, - "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, - "Error when the ca_cert.pem file cannot be written": {breakCertPem: true, wantErr: true}, + "Error when serial number is missing": {missingSerialNumber: true, wantErr: true}, + "Error when the root CA certificate file cannot be written": {breakCertPem: true, wantErr: true}, } for name, tc := range testcases { @@ -37,7 +38,7 @@ func TestCreateRooCA(t *testing.T) { dir := t.TempDir() if tc.breakCertPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "ca_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") + require.NoError(t, os.MkdirAll(filepath.Join(dir, common.RootCACertFileName), 0700), "Setup: failed to create a directory that should break cert.pem") } rootCert, rootKey, err := certs.CreateRootCA("test-root-ca", testSerial, dir) @@ -49,7 +50,7 @@ func TestCreateRooCA(t *testing.T) { require.NoError(t, err, "CreateRootCA failed") require.NotNil(t, rootCert, "CreateRootCA didn't return a certificate") require.NotNil(t, rootKey, "CreateRootCA didn't return a private key") - require.FileExists(t, filepath.Join(dir, "ca_cert.pem"), "CreateRootCA failed to write the certificate to disk") + require.FileExists(t, filepath.Join(dir, common.RootCACertFileName), "CreateRootCA failed to write the certificate to disk") }) } } @@ -91,15 +92,18 @@ func TestCreateTLSCertificateSignedBy(t *testing.T) { dir := t.TempDir() + agentCertName := common.AgentCertFilePrefix + common.CertificateSuffix + agentKeyName := common.AgentCertFilePrefix + common.KeySuffix + if tc.breakCertPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_cert.pem"), 0700), "Setup: failed to create a directory that should break cert.pem") + require.NoError(t, os.MkdirAll(filepath.Join(dir, agentCertName), 0700), "Setup: failed to create a directory that should break cert.pem") } if tc.breakKeyPem { - require.NoError(t, os.MkdirAll(filepath.Join(dir, "server_key.pem"), 0700), "Setup: failed to create a directory that should break key.pem") + require.NoError(t, os.MkdirAll(filepath.Join(dir, agentKeyName), 0700), "Setup: failed to create a directory that should break key.pem") } - tlsCert, err := certs.CreateTLSCertificateSignedBy("server", "test-server-cn", testSerial, rootCert, rootKey, dir) + tlsCert, err := certs.CreateTLSCertificateSignedBy(common.AgentCertFilePrefix, "test-server-cn", testSerial, rootCert, rootKey, dir) if tc.wantErr { require.Error(t, err, "CreateTLSCertificateSignedBy should have failed") @@ -107,8 +111,8 @@ func TestCreateTLSCertificateSignedBy(t *testing.T) { } require.NoError(t, err, "CreateTLSCertificateSignedBy failed") require.NotNil(t, tlsCert, "CreateTLSCertificateSignedBy returned a nil certificate") - require.FileExists(t, filepath.Join(dir, "server_cert.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") - require.FileExists(t, filepath.Join(dir, "server_key.pem"), "CreateTLSCertificateSignedBy failed to write the certificate") + require.FileExists(t, filepath.Join(dir, agentCertName), "CreateTLSCertificateSignedBy failed to write the certificate") + require.FileExists(t, filepath.Join(dir, agentKeyName), "CreateTLSCertificateSignedBy failed to write the certificate") }) } } diff --git a/windows-agent/internal/proservices/certs.go b/windows-agent/internal/proservices/certs.go index 046ebfe25..f2981da89 100644 --- a/windows-agent/internal/proservices/certs.go +++ b/windows-agent/internal/proservices/certs.go @@ -19,7 +19,7 @@ func newTLSCertificates(destDir string) (c agentCerts, err error) { // generates a pseudo-random serial number for the root CA certificate. serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { - return agentCerts{}, fmt.Errorf("failed to generate serial number for the CA cert: %v", err) + return agentCerts{}, fmt.Errorf("failed to generate serial number for the root CA cert: %v", err) } rootCert, rootKey, err := certs.CreateRootCA(common.GRPCServerNameOverride, serial, destDir) @@ -27,35 +27,35 @@ func newTLSCertificates(destDir string) (c agentCerts, err error) { return agentCerts{}, err } - // Create and write the server and client certificates signed by the root certificate created above. - serverCert, err := certs.CreateTLSCertificateSignedBy("server", common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) + // Create and write the agent and clients certificates signed by the root certificate created above. + agentCert, err := certs.CreateTLSCertificateSignedBy(common.AgentCertFilePrefix, common.GRPCServerNameOverride, serial.Rsh(serial, 2), rootCert, rootKey, destDir) if err != nil { return agentCerts{}, err } // We won't store the TLS client certificate, because only the agent should access this function and it's not interested in the client TLS certificate. // But we still need to write them to disk, so clients can construct their TLS configs from there. - _, err = certs.CreateTLSCertificateSignedBy("client", common.GRPCServerNameOverride, serial.Lsh(serial, 3), rootCert, rootKey, destDir) + _, err = certs.CreateTLSCertificateSignedBy(common.ClientsCertFilePrefix, common.GRPCServerNameOverride, serial.Lsh(serial, 3), rootCert, rootKey, destDir) if err != nil { return agentCerts{}, err } - return agentCerts{rootCA: rootCert, serverCert: *serverCert}, nil + return agentCerts{rootCA: rootCert, agentCert: *agentCert}, nil } -// agentTLSConfig returns a TLS config for servers that require and verify client certificates. +// agentTLSConfig returns a TLS config for the agent that require and verify client certificates. func (c agentCerts) agentTLSConfig() *tls.Config { ca := x509.NewCertPool() ca.AddCert(c.rootCA) return &tls.Config{ - Certificates: []tls.Certificate{c.serverCert}, + Certificates: []tls.Certificate{c.agentCert}, ClientCAs: ca, ClientAuth: tls.RequireAndVerifyClientCert, MinVersion: tls.VersionTLS13, } } -// agentCerts conveniently holds the root CA and server certificates to make it easy to create a TLS config. +// agentCerts conveniently holds the root CA and the agent certificates to make it easy to create a TLS config. type agentCerts struct { - rootCA *x509.Certificate - serverCert tls.Certificate + rootCA *x509.Certificate + agentCert tls.Certificate } diff --git a/windows-agent/internal/proservices/internal_test.go b/windows-agent/internal/proservices/internal_test.go index e362919e5..5281fe598 100644 --- a/windows-agent/internal/proservices/internal_test.go +++ b/windows-agent/internal/proservices/internal_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/canonical/ubuntu-pro-for-wsl/common" "github.com/stretchr/testify/require" ) @@ -18,9 +19,9 @@ func TestNewTLSCertificates(t *testing.T) { }{ "Success": {}, - "Error when the destination directory does not exist": {inexistentDestDir: true, wantErr: true}, - "Error when the server private key cannot be written": {breakKeyFile: "server_key.pem", wantErr: true}, - "Error when the client private key cannot be written": {breakKeyFile: "client_key.pem", wantErr: true}, + "Error when the destination directory does not exist": {inexistentDestDir: true, wantErr: true}, + "Error when the agent private key cannot be written": {breakKeyFile: common.AgentCertFilePrefix + common.KeySuffix, wantErr: true}, + "Error when the clients private key cannot be written": {breakKeyFile: common.ClientsCertFilePrefix + common.KeySuffix, wantErr: true}, } for name, tc := range testcases { diff --git a/windows-agent/internal/proservices/proservices_test.go b/windows-agent/internal/proservices/proservices_test.go index 223890d38..3ff547b46 100644 --- a/windows-agent/internal/proservices/proservices_test.go +++ b/windows-agent/internal/proservices/proservices_test.go @@ -70,7 +70,7 @@ func TestNew(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(publicDir, common.CertificatesDir), []byte{}, 0600), "Setup: could not create the file that should break writing the certificates") } if tc.breakCA { - require.NoError(t, os.MkdirAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem"), 0700), "Setup: could break ca_cert.pem") + require.NoError(t, os.MkdirAll(filepath.Join(publicDir, common.CertificatesDir, common.RootCACertFileName), 0700), "Setup: could not break the root CA certificate file") } s, err := proservices.New(ctx, publicDir, privateDir, proservices.WithRegistry(reg)) @@ -171,11 +171,11 @@ func TestRegisterGRPCServices(t *testing.T) { func loadClientCertificates(t *testing.T, certsDir string) credentials.TransportCredentials { t.Helper() - cert, err := tls.LoadX509KeyPair(filepath.Join(certsDir, "client_cert.pem"), filepath.Join(certsDir, "client_key.pem")) + cert, err := tls.LoadX509KeyPair(filepath.Join(certsDir, common.ClientsCertFilePrefix+common.CertificateSuffix), filepath.Join(certsDir, common.ClientsCertFilePrefix+common.KeySuffix)) require.NoError(t, err, "failed to load client cert: %v", err) ca := x509.NewCertPool() - caFilePath := filepath.Join(certsDir, "ca_cert.pem") + caFilePath := filepath.Join(certsDir, common.RootCACertFileName) caBytes, err := os.ReadFile(caFilePath) require.NoError(t, err, "failed to read ca cert %q: %v", caFilePath, err) diff --git a/wsl-pro-service/internal/daemon/daemon.go b/wsl-pro-service/internal/daemon/daemon.go index a4cb26e72..b13280b29 100644 --- a/wsl-pro-service/internal/daemon/daemon.go +++ b/wsl-pro-service/internal/daemon/daemon.go @@ -313,13 +313,13 @@ func (d *Daemon) connect(ctx context.Context) (server *streams.Server, err error func newTLSConfigFromDir(certsPath string) (conf *tls.Config, err error) { decorate.OnError(&err, "could not load TLS config") - cert, err := tls.LoadX509KeyPair(filepath.Join(certsPath, "client_cert.pem"), filepath.Join(certsPath, "client_key.pem")) + cert, err := tls.LoadX509KeyPair(filepath.Join(certsPath, common.ClientsCertFilePrefix+common.CertificateSuffix), filepath.Join(certsPath, common.ClientsCertFilePrefix+common.KeySuffix)) if err != nil { return nil, err } ca := x509.NewCertPool() - caFilePath := filepath.Join(certsPath, "ca_cert.pem") + caFilePath := filepath.Join(certsPath, common.RootCACertFileName) caBytes, err := os.ReadFile(caFilePath) if err != nil { return nil, err diff --git a/wsl-pro-service/internal/daemon/daemon_test.go b/wsl-pro-service/internal/daemon/daemon_test.go index 0561948c0..394fcde81 100644 --- a/wsl-pro-service/internal/daemon/daemon_test.go +++ b/wsl-pro-service/internal/daemon/daemon_test.go @@ -93,14 +93,14 @@ func TestServe(t *testing.T) { // keeps retrying the connection // // We instead check that a connection was/wasn't made with the agent, and that systemd was notified - "No connection because the port file does not exist": {breakPortFile: true, wantConnected: false}, - "No connection because the port file is empty": {portFileEmpty: true, wantConnected: false}, - "No connection because the port file has a bad port": {portFilePortNotNumber: true, wantConnected: false}, - "No connection because the port file has port 0": {portFileZeroPort: true, wantConnected: false}, - "No connection because the port file has a negative port": {portFileNegativePort: true, wantConnected: false}, - "No connection because there is no server": {dontServe: true}, - "No connection because there are no certificates": {missingCertsDir: true, wantConnected: false}, - "No connection because cannot read ca_cert": {missingCaCert: true, wantConnected: false}, + "No connection because the port file does not exist": {breakPortFile: true, wantConnected: false}, + "No connection because the port file is empty": {portFileEmpty: true, wantConnected: false}, + "No connection because the port file has a bad port": {portFilePortNotNumber: true, wantConnected: false}, + "No connection because the port file has port 0": {portFileZeroPort: true, wantConnected: false}, + "No connection because the port file has a negative port": {portFileNegativePort: true, wantConnected: false}, + "No connection because there is no server": {dontServe: true}, + "No connection because there are no certificates": {missingCertsDir: true, wantConnected: false}, + "No connection because cannot read root CA certificate file": {missingCaCert: true, wantConnected: false}, // Errors "Error because the context is pre-cancelled": {precancelContext: true, wantSystemdNotReady: true, wantErr: true}, @@ -126,7 +126,7 @@ func TestServe(t *testing.T) { } if tc.missingCaCert { - require.NoError(t, os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir, "ca_cert.pem")), "Setup: could not remove ca_cert.pem") + require.NoError(t, os.RemoveAll(filepath.Join(publicDir, common.CertificatesDir, common.RootCACertFileName)), "Setup: could not remove the root CA certificate file") } if tc.breakPortFile { From 27461aba7354f2d767a79b4184ac6c102bb8c444 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 10:38:58 -0300 Subject: [PATCH 40/41] Updates internal dependencies --- windows-agent/go.mod | 2 +- windows-agent/go.sum | 4 ++-- wsl-pro-service/go.mod | 2 +- wsl-pro-service/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/windows-agent/go.mod b/windows-agent/go.mod index 78e03d31c..8b9d39afb 100644 --- a/windows-agent/go.mod +++ b/windows-agent/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.2 require ( github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922 github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3 - github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e + github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66 github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3 github.com/canonical/ubuntu-pro-for-wsl/mocks v0.0.0-20240322101935-3e73eb563dc3 github.com/canonical/ubuntu-pro-for-wsl/storeapi/go-wrapper/microsoftstore v0.0.0-20240322101935-3e73eb563dc3 diff --git a/windows-agent/go.sum b/windows-agent/go.sum index ead63545f..0531b6dc5 100644 --- a/windows-agent/go.sum +++ b/windows-agent/go.sum @@ -4,8 +4,8 @@ github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922 github.com/canonical/landscape-hostagent-api v0.0.0-20240228165919-ed4dcfd85922/go.mod h1:3N+AXDrTJvuwy+F9uIDzi2g9xqpeZpxfwobtn84JHEQ= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3 h1:HSc1LV3CyGIXHvIL/VViAf6xixLLFdJGPd4J831HTa4= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240322101935-3e73eb563dc3/go.mod h1:Cud84UloJCb6yTabFFqxXiGEqzVc1g6e7emwPNDKEVM= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e h1:utI/UdCQ1i1PSron5unrcmWeVar8seLCAlm/W+WRqDE= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66 h1:xbh+Anp+kK7sU0Uxk42oVFiD2bceFD5OVkBMM5QHuTc= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3 h1:QJUHhH7n5EFpoKkHA6+CDLQobJ9LDCNVcwdw0lVEWBI= github.com/canonical/ubuntu-pro-for-wsl/contractsapi v0.0.0-20240322101935-3e73eb563dc3/go.mod h1:E3fwYB344DobWyl0ZSHSn/Se5ol03wIz8OewjhuobNk= github.com/canonical/ubuntu-pro-for-wsl/mocks v0.0.0-20240322101935-3e73eb563dc3 h1:pYpuD6W80Vs+HSuMq/ROqD3JOQJ1DC9VWrX7qUz7sdE= diff --git a/wsl-pro-service/go.mod b/wsl-pro-service/go.mod index 50d34f5f0..8aa5f3721 100644 --- a/wsl-pro-service/go.mod +++ b/wsl-pro-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.2 require ( github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896 - github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e + github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 diff --git a/wsl-pro-service/go.sum b/wsl-pro-service/go.sum index 0373be612..09207a78f 100644 --- a/wsl-pro-service/go.sum +++ b/wsl-pro-service/go.sum @@ -1,7 +1,7 @@ github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896 h1:HcDXZSv7jO0M6ZBeNnAOjq6Bm9ieaKcm8zj8z8681BU= github.com/canonical/ubuntu-pro-for-wsl/agentapi v0.0.0-20240412151705-5e7385e21896/go.mod h1:8cMWt+GvjqWKV/HQLEkCM5bnBWMiXnfKn0UVjgNoW6U= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e h1:utI/UdCQ1i1PSron5unrcmWeVar8seLCAlm/W+WRqDE= -github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240416151841-57e6fccf946e/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66 h1:xbh+Anp+kK7sU0Uxk42oVFiD2bceFD5OVkBMM5QHuTc= +github.com/canonical/ubuntu-pro-for-wsl/common v0.0.0-20240418133653-411104fedf66/go.mod h1:STWxCjA2cGy3wWzSdBawiWi//J38nTOwf6eVqN9DlyM= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= From 6da812340b27d6036424440b6177947c8a6ad050 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 18 Apr 2024 10:54:08 -0300 Subject: [PATCH 41/41] FIx certs constants doc comments --- common/consts.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/consts.go b/common/consts.go index c05f10096..7c8c7ba3c 100644 --- a/common/consts.go +++ b/common/consts.go @@ -27,18 +27,18 @@ const ( // GRPCServerNameOverride is the name to override the server name in when configuring TLS for local clients. GRPCServerNameOverride = "UP4W" - // The name of the certificate file that identifies the root certificate authority in the PEM format. + // RootCACertFileName is the name of the certificate file that identifies the root certificate authority in the PEM format. RootCACertFileName = "ca_cert.pem" - // The file name prefix to identify the certificate/key pair of the agent in the PEM format. + // AgentCertFilePrefix is the file name prefix to identify the certificate/key pair of the agent in the PEM format. AgentCertFilePrefix = "agent" - // The file name prefix to identify the certificate/key pair of the clients (GUI and all WSL instances) in the PEM format. + // ClientsCertFilePrefix is the file name prefix to identify the certificate/key pair of the clients (GUI and all WSL instances) in the PEM format. ClientsCertFilePrefix = "client" - // The file name suffix to the (public) certificate in the PEM format. + // CertificateSuffix is the file name suffix to the (public) certificate in the PEM format. CertificateSuffix = "_cert.pem" - // The file name suffix to the private key in the PEM format. + // KeySuffix is the file name suffix to the private key in the PEM format. KeySuffix = "_key.pem" )