Skip to content

Commit b4bf785

Browse files
authored
Enable TLS support for PostgreSQL (google#3831) (#484)
* Enable TLS support for PostgreSQL (google#3831) This PR adds TLS support for PostgreSQL connections in the Trillian server/signer. The key changes include: Added new flags: postgresql_tls_ca: Path to the CA certificate file for PostgreSQL TLS connection. postgresql_verify_full: Enable full TLS verification for PostgreSQL (sslmode=verify-full). If false, only sslmode=verify-ca is used. If no TLS configuration is provided, the connection defaults to non-TLS, ensuring backward compatibility. Tracking issue: google#3830 --------- Signed-off-by: Firas Ghanmi <fghanmi@redhat.com> * update trillian server/signer pipelines on-cel-expression Signed-off-by: Firas Ghanmi <fghanmi@redhat.com> --------- Signed-off-by: Firas Ghanmi <fghanmi@redhat.com>
1 parent 3d68e7c commit b4bf785

File tree

7 files changed

+155
-2
lines changed

7 files changed

+155
-2
lines changed

.tekton/logserver-pull-request.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ metadata:
1010
pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch
1111
== "main" && ( "go.mod".pathChanged() || "go.sum".pathChanged() || ".tekton/logserver-pull-request.yaml".pathChanged()
1212
|| "Dockerfile.logserver.rh".pathChanged() || "cmd/trillian_log_server/***".pathChanged()
13+
|| "storage/***".pathChanged()
1314
|| "trigger-konflux-builds.txt".pathChanged() )
1415
creationTimestamp: null
1516
labels:

.tekton/logserver-push.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ metadata:
1010
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch
1111
== "main" && ( "go.mod".pathChanged() || "go.sum".pathChanged() || ".tekton/logserver-push.yaml".pathChanged()
1212
|| "Dockerfile.logserver.rh".pathChanged() || "cmd/trillian_log_server/***".pathChanged()
13+
|| "storage/***".pathChanged()
1314
|| "trigger-konflux-builds.txt".pathChanged() )
1415
creationTimestamp: null
1516
labels:

.tekton/logsigner-pull-request.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ metadata:
1010
pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch
1111
== "main" && ( "go.mod".pathChanged() || "go.sum".pathChanged() || ".tekton/logsigner-pull-request.yaml".pathChanged()
1212
|| "Dockerfile.logsigner.rh".pathChanged() || "cmd/trillian_log_signer/***".pathChanged()
13+
|| "storage/***".pathChanged()
1314
|| "trigger-konflux-builds.txt".pathChanged() )
1415
creationTimestamp: null
1516
labels:

.tekton/logsigner-push.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ metadata:
1010
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch
1111
== "main" && ( "go.mod".pathChanged() || "go.sum".pathChanged() || ".tekton/logsigner-pull-request.yaml".pathChanged()
1212
|| "Dockerfile.logsigner.rh".pathChanged() || "cmd/trillian_log_signer/***".pathChanged()
13+
|| "storage/***".pathChanged()
1314
|| "trigger-konflux-builds.txt".pathChanged() )
1415
creationTimestamp: null
1516
labels:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* Bump golangci-lint to v2.1.6
66
* Fix leader resignation during a graceful shutdown by @osmman in https://github.com/google/trillian/pull/3790
77
* Add optional gRPC message size limit via `--max_msg_size_bytes` flag by @fghanmi in https://github.com/google/trillian/pull/3801
8+
* Add TLS support for PostgreSQL: https://github.com/google/trillian/pull/3831
9+
* `--postgresql_tls_ca`: users can provide a CA certificate, that is used to establish a secure communication with PostgreSQL server.
10+
* `--postgresql_verify_full`: users can enable full TLS verification for PostgreSQL (sslmode=verify-full). If false, only sslmode=verify-ca is used.
811

912
## v1.7.2
1013

storage/postgresql/provider.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ package postgresql
1616

1717
import (
1818
"flag"
19+
"fmt"
20+
"net/url"
21+
"os"
1922
"sync"
2023

2124
"github.com/google/trillian/monitoring"
@@ -25,7 +28,9 @@ import (
2528
)
2629

2730
var (
28-
postgreSQLURI = flag.String("postgresql_uri", "postgresql:///defaultdb?host=localhost&user=test", "Connection URI for PostgreSQL database")
31+
postgreSQLURI = flag.String("postgresql_uri", "postgresql:///defaultdb?host=localhost&user=test", "Connection URI for PostgreSQL database")
32+
postgresqlTLSCA = flag.String("postgresql_tls_ca", "", "Path to the CA certificate file for PostgreSQL TLS connection ")
33+
postgresqlVerifyFull = flag.Bool("postgresql_verify_full", false, "Enable full TLS verification for PostgreSQL (sslmode=verify-full). If false, only sslmode=verify-ca is used.")
2934

3035
postgresqlMu sync.Mutex
3136
postgresqlErr error
@@ -76,7 +81,14 @@ func getPostgreSQLDatabaseLocked() (*pgxpool.Pool, error) {
7681
if postgresqlDB != nil || postgresqlErr != nil {
7782
return postgresqlDB, postgresqlErr
7883
}
79-
db, err := OpenDB(*postgreSQLURI)
84+
uri := *postgreSQLURI
85+
var err error
86+
uri, err = BuildPostgresTLSURI(uri)
87+
if err != nil {
88+
postgresqlErr = err
89+
return nil, err
90+
}
91+
db, err := OpenDB(uri)
8092
if err != nil {
8193
postgresqlErr = err
8294
return nil, err
@@ -97,3 +109,31 @@ func (s *postgresqlProvider) Close() error {
97109
s.db.Close()
98110
return nil
99111
}
112+
113+
// BuildPostgresTLSURI modifies the given PostgreSQL URI to include TLS parameters based on flags.
114+
// It returns the modified URI and any error encountered.
115+
func BuildPostgresTLSURI(uri string) (string, error) {
116+
if *postgresqlTLSCA == "" {
117+
return uri, nil
118+
}
119+
if _, err := os.Stat(*postgresqlTLSCA); err != nil {
120+
postgresqlErr = fmt.Errorf("postgresql CA file error: %w", err)
121+
return "", postgresqlErr
122+
}
123+
u, err := url.Parse(uri)
124+
if err != nil {
125+
postgresqlErr = fmt.Errorf("invalid postgresql URI %q: %w", uri, err)
126+
return "", postgresqlErr
127+
}
128+
q := u.Query()
129+
q.Set("sslrootcert", *postgresqlTLSCA)
130+
if *postgresqlVerifyFull {
131+
q.Set("sslmode", "verify-full")
132+
} else {
133+
if q.Get("sslmode") == "" {
134+
q.Set("sslmode", "verify-ca")
135+
}
136+
}
137+
u.RawQuery = q.Encode()
138+
return u.String(), nil
139+
}

storage/postgresql/provider_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ package postgresql
1616

1717
import (
1818
"flag"
19+
"net/url"
20+
"os"
21+
"path/filepath"
22+
"strings"
1923
"testing"
2024

2125
"github.com/google/trillian/storage"
@@ -44,3 +48,105 @@ func TestPostgreSQLStorageProviderErrorPersistence(t *testing.T) {
4448
t.Fatalf("Expected second call to 'storage.NewProvider' to fail with %q, instead got: %q", err1, err2)
4549
}
4650
}
51+
52+
func TestBuildPostgresTLSURINoTLS(t *testing.T) {
53+
defer flagsaver.Save().MustRestore()
54+
originalURI := "postgresql:///defaultdb?host=localhost&user=test"
55+
if err := flag.Set("postgresql_tls_ca", ""); err != nil {
56+
t.Fatalf("Failed to set flag: %v", err)
57+
}
58+
59+
modified, err := BuildPostgresTLSURI(originalURI)
60+
if err != nil {
61+
t.Fatalf("Unexpected error: %v", err)
62+
}
63+
if modified != originalURI {
64+
t.Errorf("Expected unmodified URI %q, got %q", originalURI, modified)
65+
}
66+
}
67+
68+
func TestBuildPostgresTLSURIMissingCAFile(t *testing.T) {
69+
defer flagsaver.Save().MustRestore()
70+
71+
if err := flag.Set("postgresql_tls_ca", "/path/does/not/exist.crt"); err != nil {
72+
t.Fatalf("Failed to set flag: %v", err)
73+
}
74+
75+
_, err := BuildPostgresTLSURI("postgresql:///defaultdb")
76+
if err == nil {
77+
t.Fatalf("Expected error due to missing CA file, got nil")
78+
}
79+
if !strings.Contains(err.Error(), "CA file error") {
80+
t.Fatalf("Expected CA file error, got: %v", err)
81+
}
82+
}
83+
84+
func TestBuildPostgresTLSURIVerifyCA(t *testing.T) {
85+
defer flagsaver.Save().MustRestore()
86+
87+
tmp := filepath.Join(t.TempDir(), "ca.pem")
88+
if err := os.WriteFile(tmp, []byte("test_ca_cert_content"), 0400); err != nil {
89+
t.Fatalf("Failed to write CA file: %v", err)
90+
}
91+
92+
if err := flag.Set("postgresql_tls_ca", tmp); err != nil {
93+
t.Fatalf("Failed to set CA flag: %v", err)
94+
}
95+
if err := flag.Set("postgresql_verify_full", "false"); err != nil {
96+
t.Fatalf("Failed to set verify flag: %v", err)
97+
}
98+
99+
original := "postgresql:///defaultdb?host=localhost&user=test"
100+
modified, err := BuildPostgresTLSURI(original)
101+
if err != nil {
102+
t.Fatalf("Unexpected error: %v", err)
103+
}
104+
105+
u, err := url.Parse(modified)
106+
if err != nil {
107+
t.Fatalf("Failed to parse modified URI: %v", err)
108+
}
109+
q := u.Query()
110+
111+
if q.Get("sslrootcert") != tmp {
112+
t.Errorf("Expected sslrootcert=%q, got %q", tmp, q.Get("sslrootcert"))
113+
}
114+
if q.Get("sslmode") != "verify-ca" {
115+
t.Errorf("Expected sslmode=verify-ca, got %q", q.Get("sslmode"))
116+
}
117+
}
118+
119+
func TestBuildPostgresTLSURIVerifyFull(t *testing.T) {
120+
defer flagsaver.Save().MustRestore()
121+
122+
tmp := filepath.Join(t.TempDir(), "ca.pem")
123+
if err := os.WriteFile(tmp, []byte("test_ca_cert_content"), 0400); err != nil {
124+
t.Fatalf("Failed to write CA file: %v", err)
125+
}
126+
127+
if err := flag.Set("postgresql_tls_ca", tmp); err != nil {
128+
t.Fatalf("Failed to set CA flag: %v", err)
129+
}
130+
if err := flag.Set("postgresql_verify_full", "true"); err != nil {
131+
t.Fatalf("Failed to set verify_full flag: %v", err)
132+
}
133+
134+
original := "postgresql:///defaultdb?host=localhost&user=test"
135+
modified, err := BuildPostgresTLSURI(original)
136+
if err != nil {
137+
t.Fatalf("Unexpected error: %v", err)
138+
}
139+
140+
u, err := url.Parse(modified)
141+
if err != nil {
142+
t.Fatalf("Failed to parse modified URI: %v", err)
143+
}
144+
q := u.Query()
145+
146+
if q.Get("sslrootcert") != tmp {
147+
t.Errorf("Expected sslrootcert=%q, got %q", tmp, q.Get("sslrootcert"))
148+
}
149+
if q.Get("sslmode") != "verify-full" {
150+
t.Errorf("Expected sslmode=verify-full, got %q", q.Get("sslmode"))
151+
}
152+
}

0 commit comments

Comments
 (0)