Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdf7b098

Browse files
committed
feat(coderd/database): add pgfileurl driver for credential rotation
1 parent6dfd6cb commitdf7b098

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Package pgfileurl provides a SQL driver that reads the database connection
2+
// URL from a file on each connection attempt. This supports credential rotation
3+
// in environments like Kubernetes where secrets may be updated without
4+
// restarting the application.
5+
package pgfileurl
6+
7+
import (
8+
"context"
9+
"database/sql"
10+
"database/sql/driver"
11+
"fmt"
12+
"hash/fnv"
13+
"os"
14+
"strings"
15+
"sync"
16+
17+
"golang.org/x/xerrors"
18+
19+
"cdr.dev/slog"
20+
)
21+
22+
// hashString returns a simple hash of the string for use in driver names.
23+
funchashString(sstring)uint32 {
24+
h:=fnv.New32a()
25+
_,_=h.Write([]byte(s))
26+
returnh.Sum32()
27+
}
28+
29+
// driver name registry to avoid duplicate registration.
30+
var (
31+
registryMu sync.Mutex
32+
registry=make(map[string]bool)
33+
)
34+
35+
typefileURLDriverstruct {
36+
parent driver.Driver
37+
filePathstring
38+
logger slog.Logger
39+
}
40+
41+
var_ driver.Driver=&fileURLDriver{}
42+
43+
// Register creates and registers a SQL driver that reads the connection URL
44+
// from a file on each connection attempt. This supports credential rotation
45+
// in environments like Kubernetes where secrets may be updated.
46+
//
47+
// The returned driver name should be used with sql.Open(). The dsn parameter
48+
// to sql.Open() is ignored since the URL is read from the file.
49+
//
50+
// The logger parameter is used for debug logging when reading the file.
51+
funcRegister(parentName,filePathstring,logger slog.Logger) (string,error) {
52+
db,err:=sql.Open(parentName,"")
53+
iferr!=nil {
54+
return"",xerrors.Errorf("open parent driver: %w",err)
55+
}
56+
deferdb.Close()
57+
58+
d:=&fileURLDriver{
59+
parent:db.Driver(),
60+
filePath:filePath,
61+
logger:logger,
62+
}
63+
64+
// Include a hash of the file path in the driver name to ensure uniqueness
65+
// when multiple drivers are registered with different file paths (e.g., in tests).
66+
name:=fmt.Sprintf("%s-fileurl-%d",parentName,hashString(filePath))
67+
68+
registryMu.Lock()
69+
deferregistryMu.Unlock()
70+
if!registry[name] {
71+
sql.Register(name,d)
72+
registry[name]=true
73+
}
74+
75+
returnname,nil
76+
}
77+
78+
// Open reads the connection URL from the file and opens a connection.
79+
// The name parameter is ignored; the URL is always read from the file
80+
// specified during Register().
81+
func (d*fileURLDriver)Open(_string) (driver.Conn,error) {
82+
// Read fresh URL from file on every connection attempt.
83+
content,err:=os.ReadFile(d.filePath)
84+
iferr!=nil {
85+
returnnil,xerrors.Errorf("read database URL file %q: %w",d.filePath,err)
86+
}
87+
dbURL:=strings.TrimSpace(string(content))
88+
89+
d.logger.Debug(context.Background(),"re-reading database connection URL from file")
90+
91+
conn,err:=d.parent.Open(dbURL)
92+
iferr!=nil {
93+
returnnil,xerrors.Errorf("open connection: %w",err)
94+
}
95+
96+
returnconn,nil
97+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package pgfileurl_test
2+
3+
import (
4+
"database/sql"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"cdr.dev/slog/sloggers/slogtest"
12+
13+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
14+
"github.com/coder/coder/v2/coderd/database/pgfileurl"
15+
)
16+
17+
funcTestRegister(t*testing.T) {
18+
t.Parallel()
19+
20+
t.Run("ReadsURLFromFile",func(t*testing.T) {
21+
t.Parallel()
22+
23+
connectionURL,err:=dbtestutil.Open(t)
24+
require.NoError(t,err)
25+
26+
// Write connection URL to a temp file.
27+
tmpDir:=t.TempDir()
28+
filePath:=filepath.Join(tmpDir,"pg_url")
29+
err=os.WriteFile(filePath, []byte(connectionURL),0o600)
30+
require.NoError(t,err)
31+
32+
// Register the file URL driver.
33+
driverName,err:=pgfileurl.Register("postgres",filePath,slogtest.Make(t,nil))
34+
require.NoError(t,err)
35+
require.Contains(t,driverName,"postgres-fileurl-")
36+
37+
// Open a connection using the file URL driver.
38+
// The DSN is ignored since the URL is read from the file.
39+
db,err:=sql.Open(driverName,"ignored")
40+
require.NoError(t,err)
41+
deferdb.Close()
42+
43+
// Verify we can query the database.
44+
varresultint
45+
err=db.QueryRow("SELECT 1").Scan(&result)
46+
require.NoError(t,err)
47+
require.Equal(t,1,result)
48+
})
49+
50+
t.Run("ReReadsFileOnNewConnection",func(t*testing.T) {
51+
t.Parallel()
52+
53+
connectionURL,err:=dbtestutil.Open(t)
54+
require.NoError(t,err)
55+
56+
// Write connection URL to a temp file.
57+
tmpDir:=t.TempDir()
58+
filePath:=filepath.Join(tmpDir,"pg_url")
59+
err=os.WriteFile(filePath, []byte(connectionURL),0o600)
60+
require.NoError(t,err)
61+
62+
// Register the file URL driver.
63+
driverName,err:=pgfileurl.Register("postgres",filePath,slogtest.Make(t,nil))
64+
require.NoError(t,err)
65+
66+
// Open a connection and verify it works.
67+
db,err:=sql.Open(driverName,"")
68+
require.NoError(t,err)
69+
deferdb.Close()
70+
71+
// Force only one connection in the pool so we can test reconnection.
72+
db.SetMaxOpenConns(1)
73+
db.SetMaxIdleConns(0)
74+
75+
varresultint
76+
err=db.QueryRow("SELECT 1").Scan(&result)
77+
require.NoError(t,err)
78+
require.Equal(t,1,result)
79+
80+
// Update the file with an invalid URL.
81+
err=os.WriteFile(filePath, []byte("postgres://invalid:5432/db"),0o600)
82+
require.NoError(t,err)
83+
84+
// Close existing connections to force a new one.
85+
db.SetMaxIdleConns(0)
86+
87+
// The next query should fail because it will read the invalid URL.
88+
// We need to force a new connection by closing the pool.
89+
db2,err:=sql.Open(driverName,"")
90+
require.NoError(t,err)
91+
deferdb2.Close()
92+
93+
err=db2.Ping()
94+
require.Error(t,err)
95+
})
96+
97+
t.Run("FileNotFound",func(t*testing.T) {
98+
t.Parallel()
99+
100+
driverName,err:=pgfileurl.Register("postgres","/nonexistent/path",slogtest.Make(t,nil))
101+
require.NoError(t,err)// Registration succeeds
102+
103+
db,err:=sql.Open(driverName,"")
104+
require.NoError(t,err)
105+
deferdb.Close()
106+
107+
// Connection should fail when trying to read the file.
108+
err=db.Ping()
109+
require.Error(t,err)
110+
require.Contains(t,err.Error(),"read database URL file")
111+
})
112+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp