@@ -4,7 +4,11 @@ package client
44import (
55 "context"
66 "crypto/tls"
7+ "crypto/x509"
8+ "encoding/pem"
79 "fmt"
10+ "log/slog"
11+ "os"
812 "sync"
913 "testing"
1014 "time"
@@ -23,6 +27,7 @@ import (
2327const (
2428 minConnectTimeout = 5 * time .Second
2529 backoffJitter = 0.2
30+ maxCACertFileSize = 1 << 20 // 1MB
2631)
2732
2833// Client provides gRPC connection to StackRox Central API.
@@ -65,7 +70,9 @@ func (c *Client) Connect(ctx context.Context) error {
6570
6671 tlsConfig , err := c .tlsConfig ()
6772 if err != nil {
68- return err
73+ slog .Error ("TLS configuration failed" , "error" , err )
74+
75+ return errors .New ("invalid TLS configuration: verify CA certificate configuration, check server logs for details" )
6976 }
7077
7178 var conn * grpc.ClientConn
@@ -229,11 +236,112 @@ func (c *Client) tlsConfig() (*tls.Config, error) {
229236 return nil , errors .Wrap (err , "failed to get central URL hostname" )
230237 }
231238
232- return & tls.Config {
239+ tlsCfg := & tls.Config {
233240 InsecureSkipVerify : c .config .InsecureSkipTLSVerify , //nolint:gosec
234241 MinVersion : tls .VersionTLS12 ,
235242 ServerName : hostname ,
236- }, nil
243+ }
244+
245+ // There is no reason to load certificates if we allow InsecureSkipTLSVerify.
246+ if ! c .config .InsecureSkipTLSVerify && c .config .CACertPath != "" {
247+ certPool , err := loadCACertPool (c .config .CACertPath )
248+ if err != nil {
249+ return nil , err
250+ }
251+
252+ tlsCfg .RootCAs = certPool
253+ }
254+
255+ return tlsCfg , nil
256+ }
257+
258+ func loadCACertPool (caCertPath string ) (* x509.CertPool , error ) {
259+ // File size guard
260+ fileInfo , err := os .Stat (caCertPath )
261+ if err != nil {
262+ return nil , errors .Wrapf (err , "failed to access CA certificate at %s" , caCertPath )
263+ }
264+
265+ if ! fileInfo .Mode ().IsRegular () {
266+ return nil , errors .Errorf ("CA certificate path %s is not a regular file" , caCertPath )
267+ }
268+
269+ if fileInfo .Size () == 0 {
270+ return nil , errors .Errorf ("CA certificate file %s is empty" , caCertPath )
271+ }
272+
273+ if fileInfo .Size () > maxCACertFileSize {
274+ return nil , errors .Errorf (
275+ "CA certificate file %s is too large (%d bytes, max %d)" ,
276+ caCertPath , fileInfo .Size (),
277+ maxCACertFileSize ,
278+ )
279+ }
280+
281+ caCert , err := os .ReadFile (caCertPath ) //nolint:gosec
282+ if err != nil {
283+ return nil , errors .Wrapf (err , "failed to read CA certificate from %s" , caCertPath )
284+ }
285+
286+ // Get system cert pool, warn on fallback
287+ certPool , err := x509 .SystemCertPool ()
288+ if err != nil {
289+ slog .Warn ("Failed to load system CA pool, using custom CA only" , "error" , err )
290+
291+ certPool = x509 .NewCertPool ()
292+ }
293+
294+ if ! certPool .AppendCertsFromPEM (caCert ) {
295+ return nil , errors .Errorf ("failed to parse CA certificate from %s: no valid PEM data found" , caCertPath )
296+ }
297+
298+ showCertInfo (caCert )
299+
300+ return certPool , nil
301+ }
302+
303+ // showCertInfo parses and logs certificate metadata.
304+ func showCertInfo (caCert []byte ) {
305+ block , _ := pem .Decode (caCert )
306+ if block == nil {
307+ slog .Warn ("Unable to decode CA certificate" )
308+
309+ return
310+ }
311+
312+ cert , err := x509 .ParseCertificate (block .Bytes )
313+ if err != nil {
314+ slog .Warn ("Failed to parse CA certificate" , "error" , err )
315+
316+ return
317+ }
318+
319+ slog .Info ("Loaded CA certificate" ,
320+ "subject" , cert .Subject .CommonName ,
321+ "issuer" , cert .Issuer .CommonName ,
322+ "notAfter" , cert .NotAfter ,
323+ "isCA" , cert .IsCA ,
324+ )
325+
326+ if ! cert .IsCA {
327+ slog .Warn ("Provided certificate does not have the CA basic constraint set — TLS verification may fail" ,
328+ "subject" , cert .Subject .CommonName ,
329+ )
330+ }
331+
332+ if time .Now ().After (cert .NotAfter ) {
333+ slog .Warn ("CA certificate is expired — TLS verification will fail" ,
334+ "subject" , cert .Subject .CommonName ,
335+ "expiredAt" , cert .NotAfter ,
336+ )
337+ }
338+
339+ if time .Now ().Before (cert .NotBefore ) {
340+ slog .Warn ("CA certificate is not yet valid" ,
341+ "subject" , cert .Subject .CommonName ,
342+ "validFrom" , cert .NotBefore ,
343+ )
344+ }
237345}
238346
239347func (c * Client ) connectHTTP1 (
0 commit comments