diff --git a/chart-sync/go.mod b/chart-sync/go.mod index 223088b51..08db0fc84 100644 --- a/chart-sync/go.mod +++ b/chart-sync/go.mod @@ -2,7 +2,7 @@ module github.com/devtron-labs/chart-sync go 1.25.0 -replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc require ( github.com/caarlos0/env v3.5.0+incompatible diff --git a/chart-sync/go.sum b/chart-sync/go.sum index d1439d27c..88f2823a0 100644 --- a/chart-sync/go.sum +++ b/chart-sync/go.sum @@ -41,8 +41,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= diff --git a/chart-sync/internals/sql/connection.go b/chart-sync/internals/sql/connection.go index 8f602bf87..96a311318 100644 --- a/chart-sync/internals/sql/connection.go +++ b/chart-sync/internals/sql/connection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "go.uber.org/zap" "reflect" @@ -16,6 +17,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"password" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"chart-sync"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -34,17 +37,23 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/chart-sync/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go b/chart-sync/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/chart-sync/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go +++ b/chart-sync/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go +++ b/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/chart-sync/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/chart-sync/vendor/modules.txt b/chart-sync/vendor/modules.txt index 31939d840..69cf0d831 100644 --- a/chart-sync/vendor/modules.txt +++ b/chart-sync/vendor/modules.txt @@ -95,7 +95,7 @@ github.com/containerd/platforms # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/constants github.com/devtron-labs/common-lib/fetchAllEnv @@ -951,5 +951,5 @@ sigs.k8s.io/structured-merge-diff/v6/value ## explicit; go 1.22 sigs.k8s.io/yaml sigs.k8s.io/yaml/kyaml -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc # github.com/containerd/containerd v1.7.27 => github.com/containerd/containerd v1.7.29 diff --git a/ci-runner/go.mod b/ci-runner/go.mod index f2f282733..e8cb7460c 100644 --- a/ci-runner/go.mod +++ b/ci-runner/go.mod @@ -2,7 +2,7 @@ module github.com/devtron-labs/ci-runner go 1.25.0 -replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc require ( github.com/Knetic/govaluate v3.0.0+incompatible diff --git a/ci-runner/go.sum b/ci-runner/go.sum index 5ca3d4f3b..1ccf614ab 100644 --- a/ci-runner/go.sum +++ b/ci-runner/go.sum @@ -114,8 +114,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= diff --git a/ci-runner/vendor/modules.txt b/ci-runner/vendor/modules.txt index d7ad9c93e..c20141acf 100644 --- a/ci-runner/vendor/modules.txt +++ b/ci-runner/vendor/modules.txt @@ -298,7 +298,7 @@ github.com/cncf/xds/go/xds/type/v3 # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/blob-storage github.com/devtron-labs/common-lib/constants @@ -1213,4 +1213,4 @@ sigs.k8s.io/structured-merge-diff/v4/value ## explicit; go 1.22 sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/common-lib/securestore/AttributesRepoDBConnection.go b/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/common-lib/securestore/AttributesRepoDBConnection.go +++ b/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/common-lib/securestore/rollback/rollback_service.go b/common-lib/securestore/rollback/rollback_service.go index 9ecb97bda..e7444dc23 100644 --- a/common-lib/securestore/rollback/rollback_service.go +++ b/common-lib/securestore/rollback/rollback_service.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "github.com/devtron-labs/common-lib/securestore" + "github.com/devtron-labs/common-lib/utils/sql" "strings" "time" @@ -290,6 +291,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -307,12 +310,18 @@ func newDbConnection() (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Errorf("error in building tls config for database %s: %v", cfg.Database, err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/common-lib/utils/sql/connection.go b/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/common-lib/utils/sql/connection.go +++ b/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/common-lib/utils/sql/ssl.go b/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/git-sensor/go.mod b/git-sensor/go.mod index a6af75fa4..7a3745b8a 100644 --- a/git-sensor/go.mod +++ b/git-sensor/go.mod @@ -2,7 +2,7 @@ module github.com/devtron-labs/git-sensor go 1.25.0 -replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc require ( github.com/caarlos0/env v3.5.0+incompatible diff --git a/git-sensor/go.sum b/git-sensor/go.sum index a3a42bc13..dc208bc36 100644 --- a/git-sensor/go.sum +++ b/git-sensor/go.sum @@ -26,8 +26,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/devtron-labs/protos v0.0.3-0.20250323220609-ecf8a0f7305e h1:U6UdYbW8a7xn5IzFPd8cywjVVPfutGJCudjePAfL/Hs= github.com/devtron-labs/protos v0.0.3-0.20250323220609-ecf8a0f7305e/go.mod h1:1TqULGlTey+VNhAu/ag7NJuUvByJemkqodsc9L5PHJk= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= diff --git a/git-sensor/internals/sql/connection.go b/git-sensor/internals/sql/connection.go index bac4a6c19..d31e42035 100644 --- a/git-sensor/internals/sql/connection.go +++ b/git-sensor/internals/sql/connection.go @@ -20,6 +20,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "go.uber.org/zap" "reflect" @@ -32,6 +33,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"git_sensor"` ApplicationName string `env:"APP" envDefault:"git-sensor"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -50,17 +53,23 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/git-sensor/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go b/git-sensor/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/git-sensor/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go +++ b/git-sensor/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go +++ b/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/git-sensor/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/git-sensor/vendor/modules.txt b/git-sensor/vendor/modules.txt index e18380864..515fbab5e 100644 --- a/git-sensor/vendor/modules.txt +++ b/git-sensor/vendor/modules.txt @@ -66,7 +66,7 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/constants github.com/devtron-labs/common-lib/fetchAllEnv @@ -470,4 +470,4 @@ gopkg.in/yaml.v3 # mellium.im/sasl v0.3.2 ## explicit; go 1.20 mellium.im/sasl -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/image-scanner/go.mod b/image-scanner/go.mod index ebd464f7f..9da24ab56 100644 --- a/image-scanner/go.mod +++ b/image-scanner/go.mod @@ -66,4 +66,4 @@ require ( mellium.im/sasl v0.3.2 // indirect ) -replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/image-scanner/go.sum b/image-scanner/go.sum index 496e07a15..a591045e7 100644 --- a/image-scanner/go.sum +++ b/image-scanner/go.sum @@ -279,8 +279,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= diff --git a/image-scanner/pkg/sql/connection.go b/image-scanner/pkg/sql/connection.go index 12fcc09ba..bfbd3c287 100644 --- a/image-scanner/pkg/sql/connection.go +++ b/image-scanner/pkg/sql/connection.go @@ -20,6 +20,7 @@ import ( "github.com/caarlos0/env/v6" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "go.uber.org/zap" "reflect" @@ -32,6 +33,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"image-scanner"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -50,17 +53,23 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/image-scanner/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go b/image-scanner/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/image-scanner/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go +++ b/image-scanner/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go +++ b/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/image-scanner/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/image-scanner/vendor/modules.txt b/image-scanner/vendor/modules.txt index d93185fa9..19cfc1c08 100644 --- a/image-scanner/vendor/modules.txt +++ b/image-scanner/vendor/modules.txt @@ -74,7 +74,7 @@ github.com/cespare/xxhash/v2 github.com/coreos/clair/api/v3/clairpb github.com/coreos/clair/database github.com/coreos/clair/ext/versionfmt -# github.com/devtron-labs/common-lib v0.19.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.19.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/async github.com/devtron-labs/common-lib/constants @@ -458,4 +458,4 @@ google.golang.org/protobuf/types/known/wrapperspb # mellium.im/sasl v0.3.2 ## explicit; go 1.20 mellium.im/sasl -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/kubelink/go.mod b/kubelink/go.mod index 6d546fb8e..5d52f3503 100644 --- a/kubelink/go.mod +++ b/kubelink/go.mod @@ -167,4 +167,4 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect ) -replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +replace github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/kubelink/go.sum b/kubelink/go.sum index a60795ca0..2d0e7207e 100644 --- a/kubelink/go.sum +++ b/kubelink/go.sum @@ -65,8 +65,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= diff --git a/kubelink/pkg/sql/connection.go b/kubelink/pkg/sql/connection.go index cffcaff5a..4a1b6c4e1 100644 --- a/kubelink/pkg/sql/connection.go +++ b/kubelink/pkg/sql/connection.go @@ -20,6 +20,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "go.uber.org/zap" "reflect" @@ -32,6 +33,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"kubelink"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -50,17 +53,23 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/kubelink/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go b/kubelink/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/kubelink/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go +++ b/kubelink/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go +++ b/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/kubelink/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/kubelink/vendor/modules.txt b/kubelink/vendor/modules.txt index 33b0a762c..d1191c6ac 100644 --- a/kubelink/vendor/modules.txt +++ b/kubelink/vendor/modules.txt @@ -125,7 +125,7 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/async github.com/devtron-labs/common-lib/constants @@ -1411,4 +1411,4 @@ sigs.k8s.io/structured-merge-diff/v4/value sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 sigs.k8s.io/yaml/goyaml.v3 -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/kubewatch/go.mod b/kubewatch/go.mod index 1485c2b3d..c0570466b 100644 --- a/kubewatch/go.mod +++ b/kubewatch/go.mod @@ -240,5 +240,5 @@ require ( replace ( github.com/cyphar/filepath-securejoin v0.4.1 => github.com/cyphar/filepath-securejoin v0.3.6 // indirect - github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 + github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ) diff --git a/kubewatch/go.sum b/kubewatch/go.sum index 53dd3f6a2..595c7b245 100644 --- a/kubewatch/go.sum +++ b/kubewatch/go.sum @@ -99,8 +99,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= diff --git a/kubewatch/pkg/sql/connection.go b/kubewatch/pkg/sql/connection.go index 8843cf275..51d9057fa 100644 --- a/kubewatch/pkg/sql/connection.go +++ b/kubewatch/pkg/sql/connection.go @@ -20,6 +20,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/devtron-labs/kubewatch/pkg/config" "github.com/go-pg/pg" "go.uber.org/zap" @@ -33,6 +34,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"kubewatch"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -56,17 +59,23 @@ func NewDbConnection(appConfig *config.AppConfig, cfg *Config, logger *zap.Sugar logger.Debugw("skipping db connection", "appConfig", appConfig) return nil, nil } + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/kubewatch/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go b/kubewatch/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go index 907d202bd..c368905df 100644 --- a/kubewatch/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go +++ b/kubewatch/vendor/github.com/devtron-labs/common-lib/securestore/AttributesRepoDBConnection.go @@ -4,6 +4,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + "github.com/devtron-labs/common-lib/utils/sql" "github.com/go-pg/pg" "log" ) @@ -23,6 +24,8 @@ type config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"orchestrator"` ApplicationName string `env:"APP" envDefault:"orchestrator"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig LocalDev bool `env:"RUNTIME_CONFIG_LOCAL_DEV" envDefault:"false"` } @@ -49,12 +52,18 @@ func newDbConnection(databaseName string) (*pg.DB, error) { if err != nil { return nil, err } + tlsConfig, err := sql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + log.Println("error in building tls config for orchestrator db ", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection diff --git a/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go index 5c9cb23bf..d4b82e678 100644 --- a/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go +++ b/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -17,11 +17,12 @@ package sql import ( + "reflect" + "time" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" "go.uber.org/zap" - "reflect" - "time" "github.com/caarlos0/env" "github.com/go-pg/pg" @@ -38,6 +39,8 @@ type Config struct { ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` bean.PgQueryMonitoringConfig } @@ -56,6 +59,11 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, @@ -64,11 +72,12 @@ func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { ApplicationName: cfg.ApplicationName, ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) // check db connection var test string - _, err := dbConnection.QueryOne(&test, `SELECT 1`) + _, err = dbConnection.QueryOne(&test, `SELECT 1`) if err != nil { logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) diff --git a/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/kubewatch/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/kubewatch/vendor/modules.txt b/kubewatch/vendor/modules.txt index c9a759fbf..4915dd457 100644 --- a/kubewatch/vendor/modules.txt +++ b/kubewatch/vendor/modules.txt @@ -257,7 +257,7 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.0.0 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/async github.com/devtron-labs/common-lib/constants @@ -2032,4 +2032,4 @@ sigs.k8s.io/structured-merge-diff/v4/value sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 sigs.k8s.io/yaml/goyaml.v3 -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc diff --git a/lens/go.mod b/lens/go.mod index a24e96a67..b3c905b8c 100644 --- a/lens/go.mod +++ b/lens/go.mod @@ -58,6 +58,6 @@ require ( ) replace ( - github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 + github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 => go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 ) diff --git a/lens/go.sum b/lens/go.sum index cd1664541..2278a172b 100644 --- a/lens/go.sum +++ b/lens/go.sum @@ -17,8 +17,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 h1:TdSTr402uQpelYxrX+FsLOXx7g1OoYJe1PyO/oOv700= -github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc h1:vUG3Va+7UGT36MPJL1l/VckJUVj36YG6aX6HimNGluY= +github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc/go.mod h1:pehekzNkt2KRGZ3VC1b274Qq3iPBh27vGTby88T6KtI= github.com/devtron-labs/protos v0.0.3-0.20240912111807-605886d90b8d h1:IV6FWU6eWSfKq67Fs2DBx3LjkX/wtjMj9QB3ufZgga4= github.com/devtron-labs/protos v0.0.3-0.20240912111807-605886d90b8d/go.mod h1:1TqULGlTey+VNhAu/ag7NJuUvByJemkqodsc9L5PHJk= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= diff --git a/lens/internal/sql/connection.go b/lens/internal/sql/connection.go index 5b52983b1..090e5ba8c 100644 --- a/lens/internal/sql/connection.go +++ b/lens/internal/sql/connection.go @@ -21,6 +21,7 @@ import ( "github.com/caarlos0/env" "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/common-lib/utils/bean" + commonSql "github.com/devtron-labs/common-lib/utils/sql" "github.com/devtron-labs/lens/internal/logger" pg "github.com/go-pg/pg/v10" "go.uber.org/zap" @@ -34,6 +35,8 @@ type Config struct { Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-"` Database string `env:"PG_DATABASE" envDefault:"lens"` ApplicationName string `env:"APP" envDefault:"lens"` + SslMode string `env:"PG_SSL_MODE" envDefault:""` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:""` bean.PgQueryMonitoringConfig } @@ -77,17 +80,23 @@ func GetConfig() (*Config, error) { } func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := commonSql.BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "err", err) + return nil, err + } options := pg.Options{ Addr: cfg.Addr + ":" + cfg.Port, User: cfg.User, Password: cfg.Password, Database: cfg.Database, ApplicationName: cfg.ApplicationName, + TLSConfig: tlsConfig, } dbConnection := pg.Connect(&options) //check db connection var test string - _, err := dbConnection.QueryOne(pg.Scan(&test), "SELECT 1") + _, err = dbConnection.QueryOne(pg.Scan(&test), "SELECT 1") if err != nil { logger.Errorw("error in connecting db ", "db", obfuscateSecretTags(cfg), "err", err) diff --git a/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/AuditLog.go b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/AuditLog.go new file mode 100644 index 000000000..fb3a38e79 --- /dev/null +++ b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/AuditLog.go @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "time" +) + +type AuditLog struct { + CreatedOn time.Time `sql:"created_on,type:timestamptz"` + CreatedBy int32 `sql:"created_by,type:integer"` + UpdatedOn time.Time `sql:"updated_on,type:timestamptz"` + UpdatedBy int32 `sql:"updated_by,type:integer"` +} + +func NewDefaultAuditLog(userId int32) AuditLog { + return AuditLog{ + CreatedOn: time.Now(), + CreatedBy: userId, + UpdatedOn: time.Now(), + UpdatedBy: userId, + } +} + +// CreateAuditLog can be used by any repository to create AuditLog for insert operation +func (model *AuditLog) CreateAuditLog(userId int32) { + model.CreatedOn = time.Now() + model.UpdatedOn = time.Now() + model.CreatedBy = userId + model.UpdatedBy = userId +} + +// UpdateAuditLog can be used by any repository to update AuditLog for update operation +func (model *AuditLog) UpdateAuditLog(userId int32) { + model.UpdatedOn = time.Now() + model.UpdatedBy = userId +} diff --git a/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/TransactionWrapper.go b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/TransactionWrapper.go new file mode 100644 index 000000000..3c797e1e1 --- /dev/null +++ b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/TransactionWrapper.go @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import "github.com/go-pg/pg" + +type TransactionWrapper interface { + StartTx() (*pg.Tx, error) + RollbackTx(tx *pg.Tx) error + CommitTx(tx *pg.Tx) error +} + +type TransactionUtilImpl struct { + dbConnection *pg.DB +} + +func NewTransactionUtilImpl(db *pg.DB) *TransactionUtilImpl { + return &TransactionUtilImpl{ + dbConnection: db, + } +} +func (impl *TransactionUtilImpl) RollbackTx(tx *pg.Tx) error { + return tx.Rollback() +} +func (impl *TransactionUtilImpl) CommitTx(tx *pg.Tx) error { + return tx.Commit() +} +func (impl *TransactionUtilImpl) StartTx() (*pg.Tx, error) { + return impl.dbConnection.Begin() +} diff --git a/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go new file mode 100644 index 000000000..d4b82e678 --- /dev/null +++ b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/connection.go @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "reflect" + "time" + + "github.com/devtron-labs/common-lib/utils" + "github.com/devtron-labs/common-lib/utils/bean" + "go.uber.org/zap" + + "github.com/caarlos0/env" + "github.com/go-pg/pg" +) + +// CATEGORY=POSTGRES +type Config struct { + Addr string `env:"PG_ADDR" envDefault:"127.0.0.1" description:"address of postgres service" example:"postgresql-postgresql.devtroncd" deprecated:"false"` + Port string `env:"PG_PORT" envDefault:"5432" description:"port of postgresql service" example:"5432"` + User string `env:"PG_USER" envDefault:"" description:"user for postgres" example:"postgres"` + Password string `env:"PG_PASSWORD" envDefault:"" secretData:"-" description:"password for postgres, associated with PG_USER" example:"confidential ;)"` + Database string `env:"PG_DATABASE" envDefault:"orchestrator" description:"postgres database to be made connection with" example:"orchestrator, casbin, git_sensor, lens"` + CasbinDatabase string `env:"CASBIN_DATABASE" envDefault:"casbin"` + ApplicationName string `env:"APP" envDefault:"orchestrator" description:"Application name"` + ReadTimeout int64 `env:"PG_READ_TIMEOUT" envDefault:"30"` + WriteTimeout int64 `env:"PG_WRITE_TIMEOUT" envDefault:"30"` + SslMode string `env:"PG_SSL_MODE" envDefault:"" description:"ssl mode for postgres connection" example:"disable, require, verify-ca, verify-full"` + SslRootCert string `env:"PG_SSL_ROOT_CERT" envDefault:"" description:"path to the PEM CA bundle, required for verify-ca/verify-full ssl modes (for AWS RDS use the downloaded global-bundle.pem)" example:"/etc/devtron/certs/rds-ca-bundle.pem"` + bean.PgQueryMonitoringConfig +} + +func GetConfig() (*Config, error) { + cfg := &Config{} + err := env.Parse(cfg) + if err != nil { + return cfg, err + } + monitoringCfg, err := bean.GetPgQueryMonitoringConfig(cfg.ApplicationName) + if err != nil { + return cfg, err + } + cfg.PgQueryMonitoringConfig = monitoringCfg + return cfg, err +} + +func NewDbConnection(cfg *Config, logger *zap.SugaredLogger) (*pg.DB, error) { + tlsConfig, err := BuildTLSConfig(cfg.SslMode, cfg.SslRootCert, cfg.Addr) + if err != nil { + logger.Errorw("error in building tls config for db connection", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } + options := pg.Options{ + Addr: cfg.Addr + ":" + cfg.Port, + User: cfg.User, + Password: cfg.Password, + Database: cfg.Database, + ApplicationName: cfg.ApplicationName, + ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second, + TLSConfig: tlsConfig, + } + dbConnection := pg.Connect(&options) + // check db connection + var test string + _, err = dbConnection.QueryOne(&test, `SELECT 1`) + + if err != nil { + logger.Errorw("error in connecting db ", "db", ObfuscateSecretTags(cfg), "err", err) + return nil, err + } else { + logger.Infow("connected with db", "db", ObfuscateSecretTags(cfg)) + } + + // -------------- + dbConnection.OnQueryProcessed(utils.GetPGPostQueryProcessor(cfg.PgQueryMonitoringConfig)) + return dbConnection, err +} + +func ObfuscateSecretTags(cfg interface{}) interface{} { + + cfgDpl := reflect.New(reflect.ValueOf(cfg).Elem().Type()).Interface() + cfgDplElm := reflect.ValueOf(cfgDpl).Elem() + t := cfgDplElm.Type() + for i := 0; i < t.NumField(); i++ { + if _, ok := t.Field(i).Tag.Lookup("secretData"); ok { + cfgDplElm.Field(i).SetString("********") + } else { + cfgDplElm.Field(i).Set(reflect.ValueOf(cfg).Elem().Field(i)) + } + } + return cfgDpl +} + +/*type DbConnectionHolder struct { + connection *pg.DB +} + +func (holder *DbConnectionHolder) CloseConnection() error { + return holder.connection.Close() +}*/ diff --git a/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go new file mode 100644 index 000000000..4a92eeb9e --- /dev/null +++ b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/ssl.go @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "os" + "strings" +) + +// SSL modes supported for postgres connections, mirroring libpq / AWS RDS semantics. +const ( + SslModeDisable = "disable" // no TLS (default, preserves legacy behaviour) + SslModeRequire = "require" // encrypt only, no server certificate verification + SslModeVerifyCA = "verify-ca" // encrypt + verify the server cert chains to a trusted CA + SslModeVerifyFull = "verify-full" // encrypt + verify CA + server hostname +) + +// BuildTLSConfig returns a *tls.Config for the given sslMode, or nil for "disable"/empty +// (in which case go-pg connects in plaintext, i.e. the pre-existing behaviour). +// +// rootCertPath is the path to a PEM CA bundle (for AWS RDS this is the downloadable +// global-bundle.pem) and is required for verify-ca and verify-full. +// +// host is the server host used as the expected certificate hostname (ServerName) for +// verify-full; for RDS this must be the real DB endpoint (e.g. *.rds.amazonaws.com). +func BuildTLSConfig(sslMode, rootCertPath, host string) (*tls.Config, error) { + switch strings.ToLower(strings.TrimSpace(sslMode)) { + case "", SslModeDisable: + return nil, nil + case SslModeRequire: + // Encrypt the connection but do not validate the server certificate. + return &tls.Config{InsecureSkipVerify: true}, nil + case SslModeVerifyCA, SslModeVerifyFull: + rootCAs, err := loadRootCAs(rootCertPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + if strings.EqualFold(strings.TrimSpace(sslMode), SslModeVerifyFull) { + // verify-full: full verification including the server hostname. + tlsConfig.ServerName = hostWithoutPort(host) + } else { + // verify-ca: validate the certificate chain but not the hostname. + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = verifyChainOnly(rootCAs) + } + return tlsConfig, nil + default: + return nil, fmt.Errorf("pg: unsupported PG_SSL_MODE %q (supported: disable, require, verify-ca, verify-full)", sslMode) + } +} + +func loadRootCAs(rootCertPath string) (*x509.CertPool, error) { + if strings.TrimSpace(rootCertPath) == "" { + return nil, errors.New("pg: PG_SSL_ROOT_CERT is required for verify-ca/verify-full ssl modes") + } + pemData, err := os.ReadFile(rootCertPath) + if err != nil { + return nil, fmt.Errorf("pg: failed to read ssl root cert %q: %w", rootCertPath, err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(pemData) { + return nil, fmt.Errorf("pg: no certificates found in ssl root cert %q", rootCertPath) + } + return rootCAs, nil +} + +// verifyChainOnly verifies that the presented certificate chains to a trusted CA +// without checking the server hostname (verify-ca semantics). This mirrors what +// go's default verifier does, minus the DNS name check. +func verifyChainOnly(rootCAs *x509.CertPool) func([][]byte, [][]*x509.Certificate) error { + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for _, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return errors.New("pg: no server certificate presented") + } + intermediates := x509.NewCertPool() + for _, cert := range certs[1:] { + intermediates.AddCert(cert) + } + _, err := certs[0].Verify(x509.VerifyOptions{ + Roots: rootCAs, + Intermediates: intermediates, + }) + return err + } +} + +// hostWithoutPort strips a trailing ":port" if present so the value can be used as +// a TLS ServerName. +func hostWithoutPort(host string) string { + if h, _, err := net.SplitHostPort(host); err == nil { + return h + } + return host +} diff --git a/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/wire_sql.go b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/wire_sql.go new file mode 100644 index 000000000..32aee8f5b --- /dev/null +++ b/lens/vendor/github.com/devtron-labs/common-lib/utils/sql/wire_sql.go @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sql + +import ( + "github.com/google/wire" +) + +var PgSqlWireSet = wire.NewSet( + GetConfig, + NewDbConnection, +) diff --git a/lens/vendor/modules.txt b/lens/vendor/modules.txt index 748d853f0..6e2ffb065 100644 --- a/lens/vendor/modules.txt +++ b/lens/vendor/modules.txt @@ -7,7 +7,7 @@ github.com/caarlos0/env # github.com/cespare/xxhash/v2 v2.3.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 -# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib v0.19.1 => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc ## explicit; go 1.25.0 github.com/devtron-labs/common-lib/constants github.com/devtron-labs/common-lib/fetchAllEnv @@ -17,6 +17,7 @@ github.com/devtron-labs/common-lib/pubsub-lib/metrics github.com/devtron-labs/common-lib/pubsub-lib/model github.com/devtron-labs/common-lib/utils github.com/devtron-labs/common-lib/utils/bean +github.com/devtron-labs/common-lib/utils/sql # github.com/devtron-labs/protos v0.0.3-0.20240912111807-605886d90b8d ## explicit; go 1.17 github.com/devtron-labs/protos/gitSensor @@ -308,4 +309,4 @@ google.golang.org/protobuf/types/known/timestamppb # mellium.im/sasl v0.3.2 ## explicit; go 1.20 mellium.im/sasl -# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260415113300-e49850611af6 +# github.com/devtron-labs/common-lib => github.com/devtron-labs/devtron-services/common-lib v0.0.0-20260707131338-7cbce898d5fc