Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const (
VaultToken = "VAULT_TOKEN"
VaultNamespace = "VAULT_NAMESPACE"
VaultSkipTLSVerify = "VAULT_SKIP_VERIFY"
VaultCACert = "VAULT_CACERT"
VaultCAPath = "VAULT_CAPATH"
VaultClientCert = "VAULT_CLIENT_CERT"
VaultClientKey = "VAULT_CLIENT_KEY"
VaultTLSServerName = "VAULT_TLS_SERVER_NAME"
VaultHeaderToken = "X-Vault-Token"
VaultHeaderNamespace = "X-Vault-Namespace"
)
Expand All @@ -49,10 +54,32 @@ func NewVaultClient(sessionId string, vaultAddress string, vaultSkipTLSVerify bo
config := api.DefaultConfig()
config.Address = vaultAddress

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: vaultSkipTLSVerify},
// Honor the standard Vault TLS environment variables so a private or
// self-signed CA can be trusted without modifying the host trust store.
// ConfigureTLS updates the existing transport's TLSClientConfig in place
// (RootCAs, client certificate, ServerName) instead of replacing it with a
// bare tls.Config, which previously discarded the configured RootCAs.
if err := config.ConfigureTLS(&api.TLSConfig{
CACert: getEnv(VaultCACert, ""),
CAPath: getEnv(VaultCAPath, ""),
ClientCert: getEnv(VaultClientCert, ""),
ClientKey: getEnv(VaultClientKey, ""),
TLSServerName: getEnv(VaultTLSServerName, ""),
}); err != nil {
return nil, fmt.Errorf("failed to configure Vault TLS: %w", err)
}

// Apply the caller-resolved skip-verify decision explicitly. ConfigureTLS
// and ReadEnvironment (invoked by DefaultConfig) only ever set
// InsecureSkipVerify to true and never clear it, so set it directly here to
// preserve the context/env precedence resolved by the caller in both
// directions.
if tr, ok := config.HttpClient.Transport.(*http.Transport); ok {
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{}
}
tr.TLSClientConfig.InsecureSkipVerify = vaultSkipTLSVerify
}
config.HttpClient = &http.Client{Transport: tr}

client, err := api.NewClient(config)
if err != nil {
Expand Down Expand Up @@ -151,7 +178,7 @@ func CreateVaultClientForSession(ctx context.Context, session server.ClientSessi
logger.WithFields(log.Fields{
"session_id": session.SessionID(),
"value": envVal,
}).Warn("Invalid boolean value for VAULT_SKIP_VERIFY; using default value false")
}).Warn("Invalid boolean value for VAULT_SKIP_VERIFY; using default value false")
} else {
vaultSkipTLSVerify = parsed
}
Expand Down
68 changes: 68 additions & 0 deletions pkg/client/client_cacert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0

package client

import (
"context"
"encoding/pem"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)

// writeServerCACert PEM-encodes the test server's certificate to a temp file so
// it can be used as a CA bundle via VAULT_CACERT.
func writeServerCACert(t *testing.T, srv *httptest.Server) string {
t.Helper()
caFile := filepath.Join(t.TempDir(), "ca.pem")
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: srv.Certificate().Raw})
if err := os.WriteFile(caFile, pemBytes, 0o600); err != nil {
t.Fatalf("write CA cert: %v", err)
}
return caFile
}

// TestNewVaultClientHonorsCACert verifies that VAULT_CACERT lets the client
// verify a Vault listener presenting a private/self-signed certificate, and
// that without it verification still fails (i.e. we did not silently disable it).
func TestNewVaultClientHonorsCACert(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()

caFile := writeServerCACert(t, srv)

// Keep the test fast: no retry backoff on the expected TLS failure.
t.Setenv("VAULT_MAX_RETRIES", "0")

doRequest := func(sessionId string) error {
c, err := NewVaultClient(sessionId, srv.URL, false, "test-token", "")
if err != nil {
t.Fatalf("NewVaultClient: %v", err)
}
req := c.NewRequest(http.MethodGet, "/v1/sys/health")
resp, err := c.RawRequestWithContext(context.Background(), req)
if resp != nil {
_ = resp.Body.Close()
}
return err
}

t.Run("without CA cert verification fails", func(t *testing.T) {
t.Setenv(VaultCACert, "")
if err := doRequest("no-ca"); err == nil {
t.Fatal("expected TLS verification error against self-signed cert, got nil")
}
})

t.Run("with CA cert verification succeeds", func(t *testing.T) {
t.Setenv(VaultCACert, caFile)
if err := doRequest("with-ca"); err != nil {
t.Fatalf("expected success with VAULT_CACERT set, got: %v", err)
}
})
}