Skip to content

Commit 3c79f0c

Browse files
geroplSimon Emms
authored and
Simon Emms
committed
[installer, gitpod-db] Introduce database.ssl.ca
1 parent 5372a5a commit 3c79f0c

File tree

12 files changed

+122
-18
lines changed

12 files changed

+122
-18
lines changed

components/gitpod-db/go/conn.go

+21-1
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

@@ -21,12 +23,13 @@ type ConnectionParams struct {
2123
Password string
2224
Host string
2325
Database string
26+
CaCert 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.CaCert != "" {
46+
rootCertPool := x509.NewCertPool()
47+
if ok := rootCertPool.AppendCertsFromPEM([]byte(p.CaCert)); !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{

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_CA_CERT) {
25+
dbSetup.ssl = {
26+
ca: process.env.DB_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
}

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
};

components/public-api-server/pkg/server/server.go

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func Start(logger *logrus.Entry, version string, cfg *config.Configuration) erro
4949
Password: os.Getenv("DB_PASSWORD"),
5050
Host: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
5151
Database: "gitpod",
52+
CaCert: os.Getenv("DB_CA_CERT"),
5253
})
5354
if err != nil {
5455
return fmt.Errorf("failed to establish database connection: %w", err)

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_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+
caCert := viper.GetString("caCert")
51+
if caCert != "" {
52+
rootCertPool := x509.NewCertPool()
53+
if ok := rootCertPool.AppendCertsFromPEM([]byte(caCert)); !ok {
54+
log.Fatal("Failed to append 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 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("caCert", "", os.Getenv("DB_CA_CERT"), "Custom CA cert (chain) to use when connected")
95116
}

components/usage/pkg/server/server.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func Start(cfg Config, version string) error {
5555
Password: os.Getenv("DB_PASSWORD"),
5656
Host: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
5757
Database: "gitpod",
58+
CaCert: os.Getenv("DB_CA_CERT"),
5859
})
5960
if err != nil {
6061
return fmt.Errorf("failed to establish database connection: %w", err)

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.CaCert != nil {
348+
secretRef = corev1.LocalObjectReference{Name: cfg.Database.SSL.CaCert.Name}
349+
envvars = append(envvars, corev1.EnvVar{
350+
Name: DBCaCertEnvVarName,
351+
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
352+
LocalObjectReference: secretRef,
353+
Key: DBCaFileName,
354+
}},
355+
})
356+
}
357+
347358
return envvars
348359
}
349360

install/installer/pkg/common/constants.go

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ const (
4747
ImageBuilderComponent = "image-builder-mk3"
4848
ImageBuilderRPCPort = 8080
4949
DebugNodePort = 9229
50+
DBCaCertEnvVarName = "DB_CA_CERT"
51+
DBCaFileName = "ca.crt"
52+
DBCaBasePath = "/db-ssl"
53+
DBCaPath = DBCaBasePath + "/" + DBCaFileName
5054

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

install/installer/pkg/components/database/init/constants.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
dbSessionsTag = "5.7.34"
1111
initScriptDir = "files"
1212
sqlInitScripts = "db-init-scripts"
13+
caCertMountName = "db-ca-cert"
1314
)

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.DBCaCertEnvVarName, 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.CaCert != nil {
49+
volumes = append(volumes, corev1.Volume{
50+
Name: caCertMountName,
51+
VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{
52+
SecretName: ctx.Config.Database.SSL.CaCert.Name,
53+
}},
54+
})
55+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
56+
Name: caCertMountName,
57+
MountPath: common.DBCaBasePath,
58+
ReadOnly: true,
59+
})
60+
sslOptions = fmt.Sprintf(" --ssl-mode=VERIFY_IDENTITY --ssl-ca=%s ", common.DBCaPath)
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{{
@@ -61,13 +85,9 @@ func job(ctx *common.RenderContext) ([]runtime.Object, error) {
6185
Command: []string{
6286
"sh",
6387
"-c",
64-
"mysql -h $DB_HOST --port $DB_PORT -u $DB_USERNAME -p$DB_PASSWORD < /db-init-scripts/init.sql",
88+
fmt.Sprintf("mysql -h $DB_HOST --port $DB_PORT -u $DB_USERNAME -p$DB_PASSWORD %s< /db-init-scripts/init.sql", sslOptions),
6589
},
66-
VolumeMounts: []corev1.VolumeMount{{
67-
Name: sqlInitScripts,
68-
MountPath: "/db-init-scripts",
69-
ReadOnly: true,
70-
}},
90+
VolumeMounts: volumeMounts,
7191
}},
7292
},
7393
},

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+
CaCert *ObjectRef `json:"caCert,omitempty"`
249+
}
250+
246251
type ObjectStorage struct {
247252
InCluster *bool `json:"inCluster,omitempty"`
248253
S3 *ObjectStorageS3 `json:"s3,omitempty"`

install/installer/pkg/config/v1/validation.go

+5
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ func (v version) ClusterValidation(rcfg interface{}) cluster.ValidationChecks {
166166
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("encryptionKeys", "host", "password", "port", "username")))
167167
}
168168

169+
if cfg.Database.SSL != nil && cfg.Database.SSL.CaCert != nil {
170+
secretName := cfg.Database.SSL.CaCert.Name
171+
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("ca.crt")))
172+
}
173+
169174
if cfg.License != nil {
170175
secretName := cfg.License.Name
171176
licensorKey := "type"

0 commit comments

Comments
 (0)