mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-14 22:08:52 +00:00
security(globalsign): remove InsecureSkipVerify and pin CA pool (H-5)
The GlobalSign Atlas HVCA connector previously used InsecureSkipVerify:true on its mTLS TLS config, disabling server certificate validation and defeating the purpose of the client-side mTLS handshake. This was a CWE-295 Improper Certificate Validation vulnerability silently degrading trust on every production call to GlobalSign's signing API. Remediation (per H-5 audit finding, Lens 4.4): - Remove InsecureSkipVerify from all three http.Client construction sites (ValidateConfig, getHTTPClient, and legacy initialisation path). - Introduce buildServerTLSConfig() helper that constructs tls.Config with MinVersion: tls.VersionTLS12 (addresses adjacent L-1 recommendation). - New optional config field `server_ca_path` (env: CERTCTL_GLOBALSIGN_SERVER_CA_PATH). When unset the connector trusts the system root CA bundle (correct default for GlobalSign's publicly-trusted HVCA endpoints). When set the bundle is loaded via x509.NewCertPool() + AppendCertsFromPEM, and only those roots are trusted (supports private HVCA deployments and defence-in-depth root pinning). - Error wrapping chain: "failed to read server CA bundle at %s" and "no valid PEM certificates found in server CA bundle at %s" surface config problems at ValidateConfig time instead of silently failing at request time. Docs, config, service env-seed, and GUI issuer type definition updated to expose the new field. Tests: 9 dead `InsecureSkipVerify: true` client TLSClientConfig blocks (no-ops against httptest.NewServer plain-HTTP) replaced with bare http.Client; new TestGlobalSign_ServerTLSConfig covers pinned-CA trust, untrusted-server rejection, missing-file and invalid-PEM error paths. Verification: - go build ./... clean - go vet ./... clean - go test -race ./internal/connector/issuer/globalsign/... ./internal/config/... ./internal/service/... ok - go test ./... (excluding testcontainers-gated repo layer) ok - golangci-lint run ./... 0 issues - govulncheck ./... 0 reachable vulns - Per-layer coverage: service 68.7% (≥55), handler 83.6% (≥60), domain 82.0% (≥40), middleware 63.8% (≥30) - globalsign package coverage: 75.9% - Invariant sweep: 0 InsecureSkipVerify references remain in globalsign package (only a test-file comment documenting the removal).
This commit is contained in:
@@ -34,6 +34,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -64,6 +65,14 @@ type Config struct {
|
||||
// Must match the certificate in ClientCertPath.
|
||||
// Required. Set via CERTCTL_GLOBALSIGN_CLIENT_KEY_PATH environment variable.
|
||||
ClientKeyPath string `json:"client_key_path"`
|
||||
|
||||
// ServerCAPath is the filesystem path to a PEM file containing the CA
|
||||
// certificate(s) used to verify the GlobalSign Atlas HVCA API server certificate.
|
||||
// Optional. If empty, the system trust store is used. This option exists for
|
||||
// private/lab deployments of GlobalSign Atlas that terminate TLS with an
|
||||
// internal CA not present in the host's default trust bundle.
|
||||
// Set via CERTCTL_GLOBALSIGN_SERVER_CA_PATH environment variable.
|
||||
ServerCAPath string `json:"server_ca_path,omitempty"`
|
||||
}
|
||||
|
||||
// Connector implements the issuer.Connector interface for GlobalSign Atlas HVCA.
|
||||
@@ -153,14 +162,12 @@ func (c *Connector) ValidateConfig(ctx context.Context, rawConfig json.RawMessag
|
||||
return fmt.Errorf("failed to load GlobalSign client certificate: %w", err)
|
||||
}
|
||||
|
||||
// Create an mTLS client for validation
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
// InsecureSkipVerify=true allows testing against self-signed server certs.
|
||||
// In production, GlobalSign's API uses a proper certificate chain.
|
||||
// This matches the pattern used by other connectors (F5, network scanner, etc.)
|
||||
// that also need to bypass hostname verification for internal/lab environments.
|
||||
InsecureSkipVerify: true,
|
||||
// Build a verifying mTLS TLS config. If ServerCAPath is set, that PEM
|
||||
// bundle is used as the trust anchor for the server certificate;
|
||||
// otherwise the system trust store is used. TLS 1.2 is the minimum.
|
||||
tlsConfig, err := buildServerTLSConfig(&cfg, cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build GlobalSign TLS config: %w", err)
|
||||
}
|
||||
|
||||
validationClient := &http.Client{
|
||||
@@ -225,9 +232,9 @@ func (c *Connector) getHTTPClient(ctx context.Context) (*http.Client, error) {
|
||||
return nil, fmt.Errorf("failed to load GlobalSign client certificate: %w", err)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
tlsConfig, err := buildServerTLSConfig(c.config, cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build GlobalSign TLS config: %w", err)
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
@@ -238,6 +245,38 @@ func (c *Connector) getHTTPClient(ctx context.Context) (*http.Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildServerTLSConfig returns a TLS configuration for the GlobalSign Atlas
|
||||
// HVCA API client. It always verifies the server certificate. When
|
||||
// cfg.ServerCAPath is set, the PEM bundle at that path is used as the
|
||||
// trust anchor (enables pinning a private/lab CA); otherwise the host's
|
||||
// system trust store is used. TLS 1.2 is the minimum protocol version.
|
||||
//
|
||||
// This helper is the single source of truth for both the ValidateConfig
|
||||
// probe client and the steady-state getHTTPClient production client, so
|
||||
// any future TLS policy change applies uniformly.
|
||||
func buildServerTLSConfig(cfg *Config, clientCert tls.Certificate) (*tls.Config, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
if cfg.ServerCAPath != "" {
|
||||
caPEM, err := os.ReadFile(cfg.ServerCAPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read server CA bundle at %s: %w", cfg.ServerCAPath, err)
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(caPEM) {
|
||||
return nil, fmt.Errorf("no valid PEM certificates found in server CA bundle at %s", cfg.ServerCAPath)
|
||||
}
|
||||
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// IssueCertificate submits a certificate order to GlobalSign Atlas HVCA.
|
||||
// Returns the serial number immediately; typically the cert is available within seconds (DV) to minutes (OV).
|
||||
func (c *Connector) IssueCertificate(ctx context.Context, request issuer.IssuanceRequest) (*issuer.IssuanceResult, error) {
|
||||
|
||||
Reference in New Issue
Block a user