Compare commits

...

2 Commits

Author SHA1 Message Date
Raghav e44c053dcc feat: [CI-23743]: call TrustHarnessCA before wrapper auth + export for reuse
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

AI-Session-Id: 6ea09391-f44a-4dcd-9618-f17586946b96
AI-Tool: claude-code
AI-Model: unknown
2026-07-16 13:29:34 +05:30
Raghav 48f8235649 [CI-23743]: Support loading hosted certs in windows (#518) 2026-07-15 15:55:16 +05:30
12 changed files with 249 additions and 52 deletions
+10 -6
View File
@@ -123,8 +123,8 @@ store**, so base-image pulls fail with
`x509: certificate signed by unknown authority` unless that CA is trusted. `x509: certificate signed by unknown authority` unless that CA is trusted.
Set the `HARNESS_CA_PATH` environment variable to a PEM CA (bundle) file and the Set the `HARNESS_CA_PATH` environment variable to a PEM CA (bundle) file and the
plugin installs it into the host trust store **before starting the Docker plugin installs it into the host trust store **before any HTTPS** (registry
daemon**: wrapper auth such as GCP STS, and before starting the Docker daemon):
```yaml ```yaml
steps: steps:
@@ -146,18 +146,22 @@ Behavior:
`update-ca-certificates` / `update-ca-trust`. It also appends the CA directly `update-ca-certificates` / `update-ca-trust`. It also appends the CA directly
to the consolidated bundle (`/etc/ssl/certs/ca-certificates.crt` or the RHEL to the consolidated bundle (`/etc/ssl/certs/ca-certificates.crt` or the RHEL
equivalent) so trust holds even on minimal images without a refresh tool. equivalent) so trust holds even on minimal images without a refresh tool.
- **Windows** — imports the CA into the machine trust store via - **Windows** — imports the CA into `LocalMachine\Root` via the Windows
`certutil -addstore -f Root`. CryptoAPI (`crypt32.dll`). This works on Nano Server images that lack
`certutil` / PowerShell.
- **macOS / other** — logged no-op (not currently supported). - **macOS / other** — logged no-op (not currently supported).
Notes: Notes:
- Registry wrappers (`plugins/gcr`, `gar`, `acr`, `ecr`) call the same install
before their pre-docker HTTPS auth (e.g. `sts.googleapis.com` token exchange).
- It is **best-effort and idempotent**: when `HARNESS_CA_PATH` is unset, the file - It is **best-effort and idempotent**: when `HARNESS_CA_PATH` is unset, the file
is missing, or the file is empty, the plugin logs and continues. Re-running the is missing, or the file is empty, the plugin logs and continues. Re-running the
step will not duplicate the CA in the bundle. step will not duplicate the CA in the bundle.
- The CA is trusted for the daemon's own registry TLS (base-image pulls, cache - The CA is trusted for the daemon's own registry TLS (base-image pulls, cache
endpoints). To make the CA available to `RUN` steps inside the build, also pass endpoints) and for Go TLS clients in the plugin process. To make the CA
it as needed via the Dockerfile / build args. available to `RUN` steps inside the build, also pass it as needed via the
Dockerfile / build args.
- This is separate from the proxy build args - This is separate from the proxy build args
(`http_proxy`/`https_proxy`/`no_proxy`), which the plugin already injects from (`http_proxy`/`https_proxy`/`no_proxy`), which the plugin already injects from
the environment (including `HARNESS_`-prefixed variants). the environment (including `HARNESS_`-prefixed variants).
+29
View File
@@ -0,0 +1,29 @@
package docker
import (
"encoding/pem"
"fmt"
)
// decodePEMCertificates extracts DER-encoded CERTIFICATE blocks from a PEM
// (or PEM bundle). Non-certificate blocks are skipped. Returns an error when
// no certificate blocks are present.
func decodePEMCertificates(pemBytes []byte) ([][]byte, error) {
var ders [][]byte
rest := pemBytes
for {
var block *pem.Block
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
continue
}
ders = append(ders, block.Bytes)
}
if len(ders) == 0 {
return nil, fmt.Errorf("no CERTIFICATE PEM blocks found")
}
return ders, nil
}
+114
View File
@@ -0,0 +1,114 @@
package docker
import (
"bytes"
"crypto/x509"
"encoding/pem"
"testing"
)
// Real self-signed CAs generated for these tests (CN=HarnessTestCA / HarnessTestCA2).
const harnessTestCAPEM = `-----BEGIN CERTIFICATE-----
MIIDETCCAfmgAwIBAgIUUnMVT6JwuvsR8lPanhZxYJnqUPYwDQYJKoZIhvcNAQEL
BQAwGDEWMBQGA1UEAwwNSGFybmVzc1Rlc3RDQTAeFw0yNjA3MTUwNzMxMDBaFw0z
NjA3MTIwNzMxMDBaMBgxFjAUBgNVBAMMDUhhcm5lc3NUZXN0Q0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJOssgNxmGP8itq7IxObLY2igTK3/YeMIJ
9gvjWzUGe/RRrWqWJvfplKScNaIbyGaABYbDaGURnL3uGr5QQWEUbqvOMlEYaoNq
21TmVbEl6Sponc+4aFfDPP9CFAOn2tqyrZaU3wZIjSUWbGo2wwseWULzaQxZ8lZ6
a+kgjipymolfTZeHbJQAk/hwXVhbrz6xBbOhue/K/tFJxFBuawvwgXEi+2ywzXgy
fn80wYWdjeFEpFAtNKyjg+bu36DtHikwDS9XqZzgSZnGhDKEGh3cj0hd3/nhr59h
FCDm56XsBvWQVHmNmiuuxCn3svzSa9qdyk6tcIqZN8wKz1J7lFRfAgMBAAGjUzBR
MB0GA1UdDgQWBBSrLR7fG/bwSozrDipyfT5MtiuYjTAfBgNVHSMEGDAWgBSrLR7f
G/bwSozrDipyfT5MtiuYjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4IBAQAq9+CxKqe2vhBTz+5i9NBpxzxFy3fZTKcakuB98hafnNw29D4VggjV7sgo
99En87gkTIPsiOsRa0YH3wl5aa5EUp/Dx0ZfXasjzS5BFSZKZDYcuLuuUEeK9N6j
UBxaWDxNRjENevkX8SWpbxyNkcDGZM2IfJvVA/g7h8ERZLjlu9iOTqb6jt3PDe4q
CNoMbU5L3md3gk8mfoEZAX/IIUmbw7hAy7zE9dEfbKHeXl3FrcXPL0WmLePcK9BF
8XEpsOUSoq4sMPdVxaPF7l+SKDbdsR4gKtwlEMLefiiLqk0k4HhygjzzRgdsMOc3
S8dL7zv0BrAFy//XsfmsVeVFVhRN
-----END CERTIFICATE-----
`
const harnessTestCA2PEM = `-----BEGIN CERTIFICATE-----
MIIDEzCCAfugAwIBAgIUR5AfE7MTe0ucAk1JzlqvOJd3Z7AwDQYJKoZIhvcNAQEL
BQAwGTEXMBUGA1UEAwwOSGFybmVzc1Rlc3RDQTIwHhcNMjYwNzE1MDczMTAwWhcN
MzYwNzEyMDczMTAwWjAZMRcwFQYDVQQDDA5IYXJuZXNzVGVzdENBMjCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAI2EDM2Ac+jwhP8OaWFFb+DlVwzKSkEI
LoaZ41RQGHZaCckTLtaCtIBaNQHLWqj8q6I0fIz+8mmaXuxRKDjnhGl3AFIJ2wql
Hbyywo2MQcu+6wfs2Ewd887NDb5wrNJs/gl1GYRilSAd8n53T5IJUHHA0IGxtq4g
9XwS6OTmfBdQ3ycEsi9Yexd5Oz79sLMDhBnt21nYWrEO8Kumgyfd7gfQUBcGc1nH
NhoBrZffLmo+cljGqPNEFyAlWEnsmiqZnYh/EOUXGdXxsZMvyMWt02kLe0BlGmJr
PrI8iJCMElj86UhKK2oIjWLMA6cviYSkw9jyiihhodSUChhmqOABoe8CAwEAAaNT
MFEwHQYDVR0OBBYEFKK9RP95NSP4xmxN+hojPXlxVVatMB8GA1UdIwQYMBaAFKK9
RP95NSP4xmxN+hojPXlxVVatMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBABlrORwXmhmO+eIdTHZMuad72JMEJeHxiRCGbwH6vAVb03YZAXLmIf31
sBKkUCDV+Vm0MaTYmYncK3eZZBn6teEWcx7+L9XlF7ckVKBqj9xkwFUdfVipCimD
cjBl/H+2PEY5Hzoy/uf2/sE1czrfHA2HeEIEacVyht9t0HTtqXQqrn44ijESkNtb
SqqaVYCcceS85ID2oJYYKQycMnFJd0VwSHlD+Lu4ZubXbrg+pzSqw6olTOwCE7Q2
LhLxQRB4f8Xw807aIuFK2mKbPs+4dmPkhClM3rbFzzldQJ4lKLnvZHcFtXJUXg2a
UzwP8JQn8xuz7gIZxMO3vGqTweP48yc=
-----END CERTIFICATE-----
`
func TestDecodePEMCertificates_Single(t *testing.T) {
ders, err := decodePEMCertificates([]byte(harnessTestCAPEM))
if err != nil {
t.Fatalf("decodePEMCertificates: %v", err)
}
if len(ders) != 1 {
t.Fatalf("got %d certs, want 1", len(ders))
}
cert, err := x509.ParseCertificate(ders[0])
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}
if cert.Subject.CommonName != "HarnessTestCA" {
t.Fatalf("CN = %q, want HarnessTestCA", cert.Subject.CommonName)
}
}
func TestDecodePEMCertificates_Bundle(t *testing.T) {
bundle := harnessTestCAPEM + "\n" + harnessTestCA2PEM
ders, err := decodePEMCertificates([]byte(bundle))
if err != nil {
t.Fatalf("decodePEMCertificates: %v", err)
}
if len(ders) != 2 {
t.Fatalf("got %d certs, want 2", len(ders))
}
for i, der := range ders {
if _, err := x509.ParseCertificate(der); err != nil {
t.Fatalf("cert %d: ParseCertificate: %v", i, err)
}
}
}
func TestDecodePEMCertificates_SkipsNonCertificateBlocks(t *testing.T) {
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("not-a-key")})
mixed := append(append([]byte{}, keyPEM...), []byte(harnessTestCAPEM)...)
ders, err := decodePEMCertificates(mixed)
if err != nil {
t.Fatalf("decodePEMCertificates: %v", err)
}
if len(ders) != 1 {
t.Fatalf("got %d certs, want 1 (private key block skipped)", len(ders))
}
}
func TestDecodePEMCertificates_Empty(t *testing.T) {
_, err := decodePEMCertificates([]byte("not a pem\n"))
if err == nil {
t.Fatal("expected error for input with no CERTIFICATE blocks")
}
}
func TestDecodePEMCertificates_MatchesOpenSSLDER(t *testing.T) {
ders, err := decodePEMCertificates([]byte(harnessTestCAPEM))
if err != nil {
t.Fatal(err)
}
origBlock, _ := pem.Decode([]byte(harnessTestCAPEM))
if !bytes.Equal(origBlock.Bytes, ders[0]) {
t.Fatal("decoded DER does not match original PEM payload")
}
}
+4 -4
View File
@@ -31,7 +31,7 @@ func TestTrustHarnessCA_Unset(t *testing.T) {
os.Unsetenv("HARNESS_CA_PATH") os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called when HARNESS_CA_PATH is unset, got %d calls", *calls) t.Fatalf("expected installer not to be called when HARNESS_CA_PATH is unset, got %d calls", *calls)
@@ -44,7 +44,7 @@ func TestTrustHarnessCA_MissingFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called when CA file is missing, got %d calls", *calls) t.Fatalf("expected installer not to be called when CA file is missing, got %d calls", *calls)
@@ -60,7 +60,7 @@ func TestTrustHarnessCA_EmptyFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called for whitespace-only CA, got %d calls", *calls) t.Fatalf("expected installer not to be called for whitespace-only CA, got %d calls", *calls)
@@ -76,7 +76,7 @@ func TestTrustHarnessCA_ValidFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
captured, calls := withStubbedInstall(t) captured, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 1 { if *calls != 1 {
t.Fatalf("expected installer to be called once for a valid CA, got %d calls", *calls) t.Fatalf("expected installer to be called once for a valid CA, got %d calls", *calls)
+59 -27
View File
@@ -5,8 +5,9 @@ package docker
import ( import (
"fmt" "fmt"
"os" "unsafe"
"os/exec"
"golang.org/x/sys/windows"
) )
// installHarnessCA imports the Harness egress-proxy CA into the Windows // installHarnessCA imports the Harness egress-proxy CA into the Windows
@@ -14,38 +15,69 @@ import (
// Docker daemon and Go's crypto/x509 consult). Best-effort: failures are logged, // Docker daemon and Go's crypto/x509 consult). Best-effort: failures are logged,
// never fatal. // never fatal.
// //
// certutil needs a file path, so the (already-validated) CA bytes are written to // Uses CryptoAPI (crypt32.dll) directly so it works on Nano Server images that
// a temp .crt first, then added with `certutil -addstore -f Root <file>`. // lack certutil and PowerShell. CERT_STORE_ADD_REPLACE_EXISTING keeps the step
// idempotent across retries.
func installHarnessCA(caPEM []byte) { func installHarnessCA(caPEM []byte) {
tmp, err := os.CreateTemp("", "harness-egress-ca-*.crt") ders, err := decodePEMCertificates(caPEM)
if err != nil { if err != nil {
fmt.Printf("Could not create temp file for Harness CA: %s\n", err) fmt.Printf("Could not decode Harness CA PEM: %s\n", err)
return
}
tmpPath := tmp.Name()
defer os.Remove(tmpPath)
if _, err := tmp.Write(caPEM); err != nil {
tmp.Close()
fmt.Printf("Could not write temp Harness CA: %s\n", err)
return
}
tmp.Close()
if _, err := exec.LookPath("certutil"); err != nil {
fmt.Printf("certutil not found on PATH; cannot install Harness egress CA: %s\n", err)
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.") fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
return return
} }
// -f overwrites an existing entry, keeping the step idempotent across retries. storeName, err := windows.UTF16PtrFromString("ROOT")
cmd := exec.Command("certutil", "-addstore", "-f", "Root", tmpPath) if err != nil {
cmd.Stdout = os.Stdout fmt.Printf("Could not prepare Windows root store name: %s\n", err)
cmd.Stderr = os.Stderr return
if err := cmd.Run(); err != nil { }
fmt.Printf("Warning: certutil -addstore Root failed: %s\n", err)
store, err := windows.CertOpenStore(
windows.CERT_STORE_PROV_SYSTEM,
0,
0,
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE,
uintptr(unsafe.Pointer(storeName)),
)
if err != nil {
fmt.Printf("Could not open LocalMachine\\Root store: %s\n", err)
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.") fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
return return
} }
fmt.Println("Installed Harness egress CA into the Windows LocalMachine\\Root store.") defer windows.CertCloseStore(store, 0)
for i, der := range ders {
if err := addCertToStore(store, der); err != nil {
fmt.Printf("Warning: failed to add Harness CA cert %d to LocalMachine\\Root: %s\n", i+1, err)
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
return
}
}
fmt.Printf("Installed %d Harness egress CA certificate(s) into the Windows LocalMachine\\Root store.\n", len(ders))
}
func addCertToStore(store windows.Handle, der []byte) error {
if len(der) == 0 {
return fmt.Errorf("empty DER certificate")
}
ctx, err := windows.CertCreateCertificateContext(
windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING,
&der[0],
uint32(len(der)),
)
if err != nil {
return fmt.Errorf("CertCreateCertificateContext: %w", err)
}
defer windows.CertFreeCertificateContext(ctx)
if err := windows.CertAddCertificateContextToStore(
store,
ctx,
windows.CERT_STORE_ADD_REPLACE_EXISTING,
nil,
); err != nil {
return fmt.Errorf("CertAddCertificateContextToStore: %w", err)
}
return nil
} }
+4
View File
@@ -52,6 +52,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Must run before Azure / ACR HTTPS auth: this binary does auth before
// spawning drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
var ( var (
repo = getenv("PLUGIN_REPO") repo = getenv("PLUGIN_REPO")
registry = getenv("PLUGIN_REGISTRY") registry = getenv("PLUGIN_REGISTRY")
+4
View File
@@ -24,6 +24,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Install egress CA before any TLS in this process (including the Docker
// daemon started from Plugin.Exec).
docker.TrustHarnessCA()
app := cli.NewApp() app := cli.NewApp()
app.Name = "docker plugin" app.Name = "docker plugin"
app.Usage = "docker plugin" app.Usage = "docker plugin"
+4
View File
@@ -30,6 +30,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Must run before AWS / ECR HTTPS auth: this binary does auth before
// spawning drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
var ( var (
repo = getenv("PLUGIN_REPO") repo = getenv("PLUGIN_REPO")
registry = getenv("PLUGIN_REGISTRY") registry = getenv("PLUGIN_REGISTRY")
+4
View File
@@ -49,6 +49,10 @@ func loadConfig() Config {
} }
} }
// Must run before STS / oauth HTTPS: this binary does auth before spawning
// drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
idToken := getenv("PLUGIN_OIDC_TOKEN_ID") idToken := getenv("PLUGIN_OIDC_TOKEN_ID")
projectId := getenv("PLUGIN_PROJECT_NUMBER") projectId := getenv("PLUGIN_PROJECT_NUMBER")
poolId := getenv("PLUGIN_POOL_ID") poolId := getenv("PLUGIN_POOL_ID")
+4
View File
@@ -39,6 +39,10 @@ func loadConfig() Config {
} }
} }
// Must run before STS / oauth HTTPS: this binary does auth before spawning
// drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
idToken := getenv("PLUGIN_OIDC_TOKEN_ID") idToken := getenv("PLUGIN_OIDC_TOKEN_ID")
projectId := getenv("PLUGIN_PROJECT_NUMBER") projectId := getenv("PLUGIN_PROJECT_NUMBER")
poolId := getenv("PLUGIN_POOL_ID") poolId := getenv("PLUGIN_POOL_ID")
+12 -14
View File
@@ -132,14 +132,6 @@ type (
// Exec executes the plugin step // Exec executes the plugin step
func (p Plugin) Exec() error { func (p Plugin) Exec() error {
// Trust the Harness egress-proxy CA (if HARNESS_CA_PATH is set) BEFORE the
// daemon starts. The Docker daemon and its embedded BuildKit verify registry
// TLS against the host system trust store; when traffic goes through the
// TLS-intercepting egress proxy, the presented cert is signed by the Harness
// CA and must be trusted here or base-image pulls fail with an x509
// "unknown authority" error.
trustHarnessCA()
// start the Docker daemon server // start the Docker daemon server
if !p.Daemon.Disabled { if !p.Daemon.Disabled {
p.startDaemon() p.startDaemon()
@@ -583,17 +575,23 @@ func getSecretCmdArg(kvp string, file bool) (string, error) {
return fmt.Sprintf("id=%s,env=%s", key, value), nil return fmt.Sprintf("id=%s,env=%s", key, value), nil
} }
// trustHarnessCA installs the Harness egress-proxy CA into the host trust store // TrustHarnessCA installs the Harness egress-proxy CA into the host trust store
// so the Docker daemon / embedded BuildKit trust the TLS-intercepting proxy when // so the Docker daemon / embedded BuildKit (and this process's Go TLS clients)
// pulling base images. It is driven by HARNESS_CA_PATH (path to a PEM CA bundle) // trust the TLS-intercepting proxy. It is driven by HARNESS_CA_PATH (path to a
// and is a best-effort no-op when the var is unset, the file is missing, or the // PEM CA bundle) and is a best-effort no-op when the var is unset, the file is
// platform isn't supported — build failures are surfaced later by docker itself. // missing, or the platform isn't supported — build failures are surfaced later
// by docker itself.
//
// Call from each binary's main (after loading PLUGIN_ENV_FILE) before any HTTPS:
// Go caches SystemCertPool on first use, and registry wrappers (gcr/gar/acr/ecr)
// perform auth HTTPS before spawning drone-docker.
//
// The platform-specific installation is provided by installHarnessCA (see // The platform-specific installation is provided by installHarnessCA (see
// ca_linux.go / ca_windows.go / ca_other.go); it is indirected through // ca_linux.go / ca_windows.go / ca_other.go); it is indirected through
// installHarnessCAFn so tests can stub the actual system-trust write. // installHarnessCAFn so tests can stub the actual system-trust write.
var installHarnessCAFn = installHarnessCA var installHarnessCAFn = installHarnessCA
func trustHarnessCA() { func TrustHarnessCA() {
caPath := os.Getenv("HARNESS_CA_PATH") caPath := os.Getenv("HARNESS_CA_PATH")
if caPath == "" { if caPath == "" {
return return
+1 -1
View File
@@ -17,6 +17,7 @@ require (
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
golang.org/x/oauth2 v0.34.0 golang.org/x/oauth2 v0.34.0
golang.org/x/sys v0.39.0
google.golang.org/api v0.187.0 google.golang.org/api v0.187.0
) )
@@ -61,7 +62,6 @@ require (
go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect google.golang.org/grpc v1.79.3 // indirect