Skip to content

Commit cf604cd

Browse files
committed
[installer, gitpod-db] Introduce database.ssl.ca
1 parent e47f2d5 commit cf604cd

File tree

10 files changed

+129
-31
lines changed

10 files changed

+129
-31
lines changed

Diff for: components/gitpod-db/go/conn.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package db
66

77
import (
8+
"crypto/tls"
9+
"crypto/x509"
810
"fmt"
911
"time"
1012

@@ -17,16 +19,17 @@ import (
1719
)
1820

1921
type ConnectionParams struct {
20-
User string
21-
Password string
22-
Host string
23-
Database string
22+
User string
23+
Password string
24+
Host string
25+
Database string
26+
CustomCACert string
2427
}
2528

2629
func Connect(p ConnectionParams) (*gorm.DB, error) {
2730
loc, err := time.LoadLocation("UTC")
2831
if err != nil {
29-
return nil, fmt.Errorf("failed to load UT location: %w", err)
32+
return nil, fmt.Errorf("Failed to load UT location: %w", err)
3033
}
3134
cfg := driver_mysql.Config{
3235
User: p.User,
@@ -39,6 +42,23 @@ func Connect(p ConnectionParams) (*gorm.DB, error) {
3942
ParseTime: true,
4043
}
4144

45+
if p.CustomCACert != "" {
46+
rootCertPool := x509.NewCertPool()
47+
if ok := rootCertPool.AppendCertsFromPEM([]byte(p.CustomCACert)); !ok {
48+
log.Fatal("Failed to append custom DB CA cert.")
49+
}
50+
51+
tlsConfigName := "custom"
52+
err = driver_mysql.RegisterTLSConfig(tlsConfigName, &tls.Config{
53+
RootCAs: rootCertPool,
54+
MinVersion: tls.VersionTLS12, // semgrep finding: set lower boundary to exclude insecure TLS1.0
55+
})
56+
if err != nil {
57+
return nil, fmt.Errorf("Failed to register custom DB CA cert: %w", err)
58+
}
59+
cfg.TLSConfig = tlsConfigName
60+
}
61+
4262
// refer to https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
4363
return gorm.Open(mysql.Open(cfg.FormatDSN()), &gorm.Config{
4464
Logger: logger.New(log.Log, logger.Config{

Diff for: components/gitpod-db/src/config.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,40 @@ import { ConnectionConfig } from "mysql";
1313
export class Config {
1414
get dbConfig(): DatabaseConfig {
1515
// defaults to be used only in tests
16-
const dbSetup = {
16+
const dbSetup: DatabaseConfig = {
1717
host: process.env.DB_HOST || "localhost",
1818
port: getEnvVarParsed("DB_PORT", Number.parseInt, "3306"),
1919
username: process.env.DB_USERNAME || "gitpod",
2020
password: process.env.DB_PASSWORD || "test",
2121
database: process.env.DB_NAME || "gitpod",
2222
};
2323

24+
if (process.env.DB_CUSTOM_CA_CERT) {
25+
dbSetup.ssl = {
26+
ca: process.env.DB_CUSTOM_CA_CERT,
27+
};
28+
}
29+
2430
log.info(`Using DB: ${dbSetup.host}:${dbSetup.port}/${dbSetup.database}`);
2531

2632
return dbSetup;
2733
}
2834

2935
get mysqlConfig(): ConnectionConfig {
3036
const dbConfig = this.dbConfig;
31-
return {
37+
const mysqlConfig: ConnectionConfig = {
3238
host: dbConfig.host,
3339
port: dbConfig.port,
3440
user: dbConfig.username,
3541
password: dbConfig.password,
3642
database: dbConfig.database,
3743
};
44+
if (dbConfig.ssl?.ca) {
45+
mysqlConfig.ssl = {
46+
ca: dbConfig.ssl.ca,
47+
};
48+
}
49+
return mysqlConfig;
3850
}
3951

4052
get dbEncryptionKeys(): string {
@@ -48,4 +60,7 @@ export interface DatabaseConfig {
4860
database?: string;
4961
username?: string;
5062
password?: string;
63+
ssl?: {
64+
ca?: string;
65+
};
5166
}

Diff for: components/gitpod-db/src/wait-for-db.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as mysql from "mysql";
1313

1414
const retryPeriod = 5000; // [ms]
1515
const totalAttempts = 30;
16-
const connCfg = {
16+
const connCfg: mysql.ConnectionConfig = {
1717
...new Config().mysqlConfig,
1818
timeout: retryPeriod,
1919
};

Diff for: components/service-waiter/cmd/database.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package cmd
66

77
import (
8+
"crypto/tls"
9+
"crypto/x509"
810
"database/sql"
911
"net"
1012
"os"
@@ -24,7 +26,7 @@ var databaseCmd = &cobra.Command{
2426
Short: "waits for a MySQL database to become available",
2527
Long: `Uses the default db env config of a Gitpod deployment to try and
2628
connect to a MySQL database, specifically DB_HOST, DB_PORT, DB_PASSWORD,
27-
and DB_USER(=gitpod)`,
29+
DB_CUSTOM_CA_CERT and DB_USER(=gitpod)`,
2830
PreRun: func(cmd *cobra.Command, args []string) {
2931
err := viper.BindPFlags(cmd.Flags())
3032
if err != nil {
@@ -38,13 +40,31 @@ and DB_USER(=gitpod)`,
3840
cfg.User = viper.GetString("username")
3941
cfg.Passwd = viper.GetString("password")
4042
cfg.Timeout = 1 * time.Second
41-
dsn := cfg.FormatDSN()
4243

44+
dsn := cfg.FormatDSN()
4345
censoredDSN := dsn
4446
if cfg.Passwd != "" {
4547
censoredDSN = strings.Replace(dsn, cfg.Passwd, "*****", -1)
4648
}
4749

50+
customCACert := viper.GetString("customCA")
51+
if customCACert != "" {
52+
rootCertPool := x509.NewCertPool()
53+
if ok := rootCertPool.AppendCertsFromPEM([]byte(customCACert)); !ok {
54+
log.Fatal("Failed to append custom DB CA cert.")
55+
}
56+
57+
tlsConfigName := "custom"
58+
err := mysql.RegisterTLSConfig(tlsConfigName, &tls.Config{
59+
RootCAs: rootCertPool,
60+
MinVersion: tls.VersionTLS12, // semgrep finding: set lower boundary to exclude insecure TLS1.0
61+
})
62+
if err != nil {
63+
log.WithError(err).Fatal("Failed to register custom DB CA cert")
64+
}
65+
cfg.TLSConfig = tlsConfigName
66+
}
67+
4868
timeout := getTimeout()
4969
done := make(chan bool)
5070
go func() {
@@ -92,4 +112,5 @@ func init() {
92112
databaseCmd.Flags().StringP("port", "p", envOrDefault("DB_PORT", "3306"), "Port to connect on")
93113
databaseCmd.Flags().StringP("password", "P", os.Getenv("DB_PASSWORD"), "Password to use when connecting")
94114
databaseCmd.Flags().StringP("username", "u", envOrDefault("DB_USERNAME", "gitpod"), "Username to use when connected")
115+
databaseCmd.Flags().StringP("customCA", "", os.Getenv("DB_CUSTOM_CA_CERT"), "Custom CA cert (chain) to use when connected")
95116
}

Diff for: components/usage/pkg/server/server.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ func Start(cfg Config, version string) error {
5151
log.WithField("config", cfg).Info("Starting usage component.")
5252

5353
conn, err := db.Connect(db.ConnectionParams{
54-
User: os.Getenv("DB_USERNAME"),
55-
Password: os.Getenv("DB_PASSWORD"),
56-
Host: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
57-
Database: "gitpod",
54+
User: os.Getenv("DB_USERNAME"),
55+
Password: os.Getenv("DB_PASSWORD"),
56+
Host: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
57+
Database: "gitpod",
58+
CustomCACert: os.Getenv("DB_CUSTOM_CA_CERT"),
5859
})
5960
if err != nil {
6061
return fmt.Errorf("failed to establish database connection: %w", err)

Diff for: install/installer/pkg/common/common.go

+11
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,17 @@ func DatabaseEnv(cfg *config.Config) (res []corev1.EnvVar) {
344344
},
345345
)
346346

347+
if cfg.Database.SSL != nil && cfg.Database.SSL.CustomCA != nil {
348+
secretRef = corev1.LocalObjectReference{Name: cfg.Database.SSL.CustomCA.Name}
349+
envvars = append(envvars, corev1.EnvVar{
350+
Name: DBCustomCaCertEnvVarName,
351+
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
352+
LocalObjectReference: secretRef,
353+
Key: DBCustomCaFileName,
354+
}},
355+
})
356+
}
357+
347358
return envvars
348359
}
349360

Diff for: install/installer/pkg/common/constants.go

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ const (
4848
ImageBuilderComponent = "image-builder-mk3"
4949
ImageBuilderRPCPort = 8080
5050
DebugNodePort = 9229
51+
DBCustomCaCertEnvVarName = "DB_CUSTOM_CA_CERT"
52+
DBCustomCaFileName = "ca.crt"
53+
DBCustomCaBasePath = "/db-ssl"
54+
DBCustomCaPath = DBCustomCaBasePath + "/" + DBCustomCaFileName
5155

5256
AnnotationConfigChecksum = "gitpod.io/checksum_config"
5357
)

Diff for: install/installer/pkg/components/database/init/constants.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
package init
66

77
const (
8-
Component = "dbinit"
9-
dbSessionsImage = "library/mysql"
10-
dbSessionsTag = "5.7.34"
11-
initScriptDir = "files"
12-
sqlInitScripts = "db-init-scripts"
8+
Component = "dbinit"
9+
dbSessionsImage = "library/mysql"
10+
dbSessionsTag = "5.7.34"
11+
initScriptDir = "files"
12+
sqlInitScripts = "db-init-scripts"
13+
customCaMountName = "db-custom-ca"
1314
)

Diff for: install/installer/pkg/components/database/init/job.go

+32-12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ func job(ctx *common.RenderContext) ([]runtime.Object, error) {
3131
Annotations: common.CustomizeAnnotation(ctx, Component, common.TypeMetaBatchJob),
3232
}
3333

34+
volumes := []corev1.Volume{{
35+
Name: sqlInitScripts,
36+
VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
37+
LocalObjectReference: corev1.LocalObjectReference{Name: sqlInitScripts},
38+
}},
39+
}}
40+
volumeMounts := []corev1.VolumeMount{{
41+
Name: sqlInitScripts,
42+
MountPath: "/db-init-scripts",
43+
ReadOnly: true,
44+
}}
45+
46+
// We already have CA loaded at common.DBCustomCaCertEnvVarName, but mysql cli needs a file here, so we mount it like as one.
47+
sslOptions := ""
48+
if ctx.Config.Database.SSL != nil && ctx.Config.Database.SSL.CustomCA != nil {
49+
volumes = append(volumes, corev1.Volume{
50+
Name: customCaMountName,
51+
VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{
52+
SecretName: ctx.Config.Database.SSL.CustomCA.Name,
53+
}},
54+
})
55+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
56+
Name: customCaMountName,
57+
MountPath: common.DBCustomCaBasePath,
58+
ReadOnly: true,
59+
})
60+
sslOptions = fmt.Sprintf(" --ssl-mode=VERIFY_IDENTITY --ssl-ca=%s ", common.DBCustomCaPath)
61+
}
62+
3463
return []runtime.Object{&batchv1.Job{
3564
TypeMeta: common.TypeMetaBatchJob,
3665
ObjectMeta: objectMeta,
@@ -43,12 +72,7 @@ func job(ctx *common.RenderContext) ([]runtime.Object, error) {
4372
RestartPolicy: corev1.RestartPolicyNever,
4473
ServiceAccountName: Component,
4574
EnableServiceLinks: pointer.Bool(false),
46-
Volumes: []corev1.Volume{{
47-
Name: sqlInitScripts,
48-
VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
49-
LocalObjectReference: corev1.LocalObjectReference{Name: sqlInitScripts},
50-
}},
51-
}},
75+
Volumes: volumes,
5276
// The init container is designed to emulate Helm hooks
5377
InitContainers: []corev1.Container{*common.DatabaseWaiterContainer(ctx)},
5478
Containers: []corev1.Container{{
@@ -64,13 +88,9 @@ func job(ctx *common.RenderContext) ([]runtime.Object, error) {
6488
Command: []string{
6589
"sh",
6690
"-c",
67-
"mysql -h $DB_HOST --port $DB_PORT -u $DB_USERNAME -p$DB_PASSWORD < /db-init-scripts/init.sql",
91+
fmt.Sprintf("mysql -h $DB_HOST --port $DB_PORT -u $DB_USERNAME -p$DB_PASSWORD %s< /db-init-scripts/init.sql", sslOptions),
6892
},
69-
VolumeMounts: []corev1.VolumeMount{{
70-
Name: sqlInitScripts,
71-
MountPath: "/db-init-scripts",
72-
ReadOnly: true,
73-
}},
93+
VolumeMounts: volumeMounts,
7494
}},
7595
},
7696
},

Diff for: install/installer/pkg/config/v1/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ type Database struct {
232232
InCluster *bool `json:"inCluster,omitempty"`
233233
External *DatabaseExternal `json:"external,omitempty"`
234234
CloudSQL *DatabaseCloudSQL `json:"cloudSQL,omitempty"`
235+
SSL *SSLOptions `json:"ssl,omitempty"`
235236
}
236237

237238
type DatabaseExternal struct {
@@ -243,6 +244,10 @@ type DatabaseCloudSQL struct {
243244
Instance string `json:"instance" validate:"required"`
244245
}
245246

247+
type SSLOptions struct {
248+
CustomCA *ObjectRef `json:"customCa,omitempty"`
249+
}
250+
246251
type ObjectStorage struct {
247252
InCluster *bool `json:"inCluster,omitempty"`
248253
S3 *ObjectStorageS3 `json:"s3,omitempty"`

0 commit comments

Comments
 (0)