Skip to content

Commit a250509

Browse files
authored
Merge pull request #63 from haohanyang/add-tls-support
Add TLS support
2 parents 9f3394f + c87b814 commit a250509

File tree

19 files changed

+750
-445
lines changed

19 files changed

+750
-445
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,19 @@ jobs:
142142
steps:
143143
- name: Checkout
144144
uses: actions/checkout@v4
145+
- name: Create fake plugin metadata
146+
id: create-fake-metadata
147+
run: |
148+
tmpfile=$(mktemp -d)
149+
sed 's/>=10.4.0/\^11.x/g' src/plugin.json > $tmpfile/plugin.json
150+
echo "fake_plugin_dir=$tmpfile" >> $GITHUB_OUTPUT
145151
- name: Resolve Grafana E2E versions
146152
id: resolve-versions
147153
uses: grafana/plugin-actions/e2e-version@main
154+
with:
155+
skip-grafana-dev-image: true
156+
limit: 1
157+
plugin-directory: ${{ steps.create-fake-metadata.outputs.fake_plugin_dir }}
148158

149159
playwright-tests:
150160
needs: [resolve-versions, build]
@@ -180,6 +190,7 @@ jobs:
180190

181191
- name: Start Grafana
182192
run: |
193+
bash scripts/cert-gen.sh mongo-tls-auth
183194
docker compose -f docker-compose.test.yaml pull
184195
DEVELOPMENT=false GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose -f docker-compose.test.yaml up -d
185196

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ venv/
4343
mongodb-datasource/
4444
grafana-storage/
4545

46-
mongo-docs/
46+
mongo-docs/
47+
certs/

docker-compose.test.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
- ./dist:/var/lib/grafana/plugins/haohanyang-mongodb-datasource
2121
- ./provisioning:/etc/grafana/provisioning
2222
- .:/root/haohanyang-mongodb-datasource
23+
- ./certs:/certs
2324

2425
environment:
2526
NODE_ENV: development
@@ -49,6 +50,18 @@ services:
4950
networks:
5051
- mongodb-datasource
5152

53+
mongo-tls-auth:
54+
image: mongo
55+
container_name: mongodb-datasource-mongo-tls-auth
56+
command: [--config, /etc/mongo/mongod.conf]
57+
ports:
58+
- 27020:27017
59+
volumes:
60+
- ./certs:/certs
61+
- ./mongod.conf:/etc/mongo/mongod.conf
62+
networks:
63+
- mongodb-datasource
64+
5265
networks:
5366
mongodb-datasource:
5467
driver: bridge

mongod.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# TLS/SSL Configuration
2+
net:
3+
tls:
4+
mode: requireTLS
5+
certificateKeyFile: /certs/mongodb.pem
6+
CAFile: /certs/ca.pem

package-lock.json

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mongodb-datasource",
3-
"version": "0.3.2",
3+
"version": "0.4.0-alpha.0",
44
"scripts": {
55
"build": "webpack -c ./webpack.config.ts --env production",
66
"dev": "webpack -w -c ./webpack.config.ts --env development",
@@ -32,6 +32,7 @@
3232
"@types/react-router-dom": "^5.2.0",
3333
"@types/testing-library__jest-dom": "5.14.8",
3434
"@types/validator": "^13.15.0",
35+
"bson": "^6.10.4",
3536
"copy-webpack-plugin": "^11.0.0",
3637
"css-loader": "^6.7.3",
3738
"eslint-plugin-deprecation": "^2.0.0",

pkg/models/settings.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ type PluginSettings struct {
1515
Username string `json:"username"`
1616
ConnectionStringScheme string `json:"connectionStringScheme"`
1717
ConnectionParameters string `json:"connectionParameters"`
18+
CaCertPath string `json:"caCertPath"`
19+
ClientCertPath string `json:"clientCertPath"` // public client certificate
20+
ClientKeyPath string `json:"clientKeyPath"` // private client key
1821
Secrets *SecretPluginSettings `json:"-"`
1922
}
2023

2124
type SecretPluginSettings struct {
22-
Password string `json:"password"`
25+
Password string `json:"password"`
26+
ClientKeyPassword string `json:"clientKeyPassword"`
2327
}
2428

2529
func LoadPluginSettings(source backend.DataSourceInstanceSettings) (*PluginSettings, error) {
@@ -36,6 +40,7 @@ func LoadPluginSettings(source backend.DataSourceInstanceSettings) (*PluginSetti
3640

3741
func loadSecretPluginSettings(source map[string]string) *SecretPluginSettings {
3842
return &SecretPluginSettings{
39-
Password: source["password"],
43+
Password: source["password"],
44+
ClientKeyPassword: source["clientKeyPassword"],
4045
}
4146
}

pkg/plugin/datasource.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ package plugin
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
57
"encoding/json"
8+
"errors"
69
"fmt"
10+
"os"
711
"strings"
812
"time"
913

@@ -44,6 +48,16 @@ func NewDatasource(ctx context.Context, source backend.DataSourceInstanceSetting
4448

4549
opts := options.Client().ApplyURI(uri)
4650

51+
if config.AuthMethod == "auth-tls" {
52+
// TLS setup
53+
tlsConfig, err := tlsSetup(config)
54+
if err != nil {
55+
backend.Logger.Error("Failed to setup TLS", "error", err)
56+
return nil, err
57+
}
58+
opts.SetTLSConfig(tlsConfig)
59+
}
60+
4761
client, err := mongo.Connect(ctx, opts)
4862
if err != nil {
4963
backend.Logger.Error(fmt.Sprintf("Failed to connect to db: %s", err.Error()))
@@ -63,6 +77,39 @@ func (d *Datasource) Dispose() {
6377
d.client.Disconnect(context.TODO())
6478
}
6579

80+
func tlsSetup(config *models.PluginSettings) (*tls.Config, error) {
81+
caFile := config.CaCertPath
82+
certFile := config.ClientCertPath
83+
keyFile := config.ClientKeyPath
84+
85+
if caFile == "" || certFile == "" || keyFile == "" {
86+
return nil, errors.New("CA certificate, client certificate or client key file path is missing")
87+
}
88+
89+
// Loads CA certificate file
90+
caCert, err := os.ReadFile(caFile)
91+
if err != nil {
92+
return nil, err
93+
}
94+
caCertPool := x509.NewCertPool()
95+
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
96+
return nil, errors.New("CA file must be in PEM format")
97+
}
98+
// Loads client certificate files
99+
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
100+
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
tlsConfig := &tls.Config{
106+
RootCAs: caCertPool,
107+
Certificates: []tls.Certificate{cert},
108+
}
109+
110+
return tlsConfig, nil
111+
}
112+
66113
// QueryData handles multiple queries and returns multiple responses.
67114
// req contains the queries []DataQuery (where each query contains RefID as a unique identifier).
68115
// The QueryDataResponse contains a map of RefID to the response for each query, and each response
@@ -206,6 +253,20 @@ func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRe
206253
}
207254

208255
opts := options.Client().ApplyURI(uri).SetTimeout(5 * time.Second)
256+
257+
if config.AuthMethod == "auth-tls" {
258+
// TLS setup
259+
tlsConfig, err := tlsSetup(config)
260+
if err != nil {
261+
backend.Logger.Error("Failed to setup TLS", "error", err)
262+
263+
res.Status = backend.HealthStatusError
264+
res.Message = err.Error()
265+
return res, nil
266+
}
267+
opts.SetTLSConfig(tlsConfig)
268+
}
269+
209270
client, err := mongo.Connect(ctx, opts)
210271
if err != nil {
211272
res.Status = backend.HealthStatusError

pkg/plugin/utils.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,17 @@ func MongoUri(config *models.PluginSettings) (string, error) {
7979
return uri, errors.New("missing MongoDB username or password")
8080
}
8181
creds = fmt.Sprintf("%s:%s@", config.Username, config.Secrets.Password)
82-
} else if config.AuthMethod != "auth-none" {
83-
return uri, errors.New("unsupported auth method")
8482
}
8583

8684
if config.ConnectionParameters != "" {
8785
params = "?" + config.ConnectionParameters
8886
}
8987

88+
// TLS passphrase
89+
if config.AuthMethod == "auth-tls" && config.Secrets.ClientKeyPassword != "" {
90+
params += "&sslClientCertificateKeyPassword=" + config.Secrets.ClientKeyPassword
91+
}
92+
9093
if config.ConnectionStringScheme == "dns_seed_list" {
9194
uri = fmt.Sprintf("mongodb+srv://%s%s/%s", creds, config.Host, params)
9295
} else {

pkg/tlsconn/main.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"fmt"
8+
"os"
9+
10+
"go.mongodb.org/mongo-driver/mongo"
11+
"go.mongodb.org/mongo-driver/mongo/options"
12+
)
13+
14+
func main() {
15+
16+
caFile := "certs/ca.pem"
17+
certFile := "certs/mongodb.crt" // public client certificate
18+
keyFile := "certs/mongodb.pem" // private client key
19+
passPhrase := "123" // private client key passphrase
20+
21+
// Loads CA certificate file
22+
caCert, err := os.ReadFile(caFile)
23+
if err != nil {
24+
panic(err)
25+
}
26+
caCertPool := x509.NewCertPool()
27+
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
28+
panic("Error: CA file must be in PEM format")
29+
}
30+
// Loads client certificate files
31+
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
32+
33+
if err != nil {
34+
panic(err)
35+
}
36+
// Instantiates a Config instance
37+
tlsConfig := &tls.Config{
38+
RootCAs: caCertPool,
39+
Certificates: []tls.Certificate{cert},
40+
}
41+
uri := "mongodb://localhost:27017/?tls=true&tlsAllowInvalidCertificates=true&sslClientCertificateKeyPassword=" + passPhrase
42+
// Sets TLS options in options instance
43+
opts := options.Client().ApplyURI(uri).SetTLSConfig(tlsConfig)
44+
45+
ctx := context.TODO()
46+
client, err := mongo.Connect(ctx, opts)
47+
48+
if err != nil {
49+
panic(err)
50+
}
51+
defer client.Disconnect(ctx)
52+
53+
err = client.Ping(ctx, nil)
54+
if err != nil {
55+
panic(err)
56+
}
57+
58+
fmt.Println("Connected to MongoDB!")
59+
}

0 commit comments

Comments
 (0)