Skip to content

Commit 86756f9

Browse files
authored
PMM-11040: Add security metrics with info about encryption (#599)
* PMM-11040: Add security metrics with info about encryption
1 parent f9e7fb7 commit 86756f9

File tree

6 files changed

+160
-0
lines changed

6 files changed

+160
-0
lines changed

docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,13 @@ services:
206206
ports:
207207
- "${TEST_MONGODB_STANDALONE_PORT:-27017}:27017"
208208
command: mongod --port 27017 --oplogSize 16
209+
210+
standalone-encrypted:
211+
container_name: "standalone-encrypted"
212+
image: percona/percona-server-mongodb:5.0.13-11
213+
ports:
214+
- "${TEST_MONGODB_STANDALONE_ENCRYPTED_PORT:-27027}:27017"
215+
volumes:
216+
- ./docker/secret/mongodb_secrets.txt:/secret/mongodb_secrets.txt
217+
- ./docker/scripts:/scripts
218+
command: /scripts/run-mongodb-encrypted.sh
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
# set proper permissions for secret file, otherwise mongodb won't start
4+
chmod 600 /secret/mongodb_secrets.txt
5+
mongod --port 27017 --oplogSize 16 --bind_ip_all --enableEncryption --encryptionKeyFile /secret/mongodb_secrets.txt

docker/secret/mongodb_secrets.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
zN94kcTrN2CC/X1L9a54/VebKctZnJ/QIO3JdzUWKdY=

exporter/diagnostic_data_collector.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ import (
2626
"go.mongodb.org/mongo-driver/mongo"
2727
)
2828

29+
const (
30+
kmipEncryption = "kmip"
31+
vaultEncryption = "vault"
32+
localKeyFileEncryption = "localKeyFile"
33+
)
34+
2935
type diagnosticDataCollector struct {
3036
ctx context.Context
3137
base *baseCollector
@@ -89,6 +95,13 @@ func (d *diagnosticDataCollector) collect(ch chan<- prometheus.Metric) {
8995
metrics := makeMetrics("", m, d.topologyInfo.baseLabels(), d.compatibleMode)
9096
metrics = append(metrics, locksMetrics(logger, m)...)
9197

98+
securityMetric, err := d.getSecurityMetricFromLineOptions(client)
99+
if err != nil {
100+
logger.Errorf("cannot decode getCmdLineOtpions: %s", err)
101+
} else if securityMetric != nil {
102+
metrics = append(metrics, securityMetric)
103+
}
104+
92105
if d.compatibleMode {
93106
metrics = append(metrics, specialMetrics(d.ctx, client, m, logger)...)
94107

@@ -111,5 +124,67 @@ func (d *diagnosticDataCollector) collect(ch chan<- prometheus.Metric) {
111124
}
112125
}
113126

127+
func (d *diagnosticDataCollector) getSecurityMetricFromLineOptions(client *mongo.Client) (prometheus.Metric, error) {
128+
var cmdLineOpionsBson bson.M
129+
cmdLineOptions := bson.D{{Key: "getCmdLineOpts", Value: "1"}}
130+
resCmdLineOptions := client.Database("admin").RunCommand(d.ctx, cmdLineOptions)
131+
if resCmdLineOptions.Err() != nil {
132+
return nil, errors.Wrap(resCmdLineOptions.Err(), "cannot execute getCmdLineOpts command")
133+
}
134+
if err := resCmdLineOptions.Decode(&cmdLineOpionsBson); err != nil {
135+
return nil, errors.Wrap(err, "cannot parse response of the getCmdLineOpts command")
136+
}
137+
138+
if cmdLineOpionsBson == nil || cmdLineOpionsBson["parsed"] == nil {
139+
return nil, errors.New("cmdlined options is empty")
140+
}
141+
parsedOptions, ok := cmdLineOpionsBson["parsed"].(bson.M)
142+
if !ok {
143+
return nil, errors.New("cannot cast parsed options to BSON")
144+
}
145+
securityOptions, ok := parsedOptions["security"].(bson.M)
146+
if !ok {
147+
return nil, nil
148+
}
149+
150+
metric, err := d.retrieveSecurityEncryptionMetric(securityOptions)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
return metric, nil
156+
}
157+
158+
func (d *diagnosticDataCollector) retrieveSecurityEncryptionMetric(securityOptions bson.M) (prometheus.Metric, error) {
159+
_, ok := securityOptions["enableEncryption"]
160+
if !ok {
161+
return nil, nil
162+
}
163+
164+
var encryptionType string
165+
_, ok = securityOptions["kmip"]
166+
if ok {
167+
encryptionType = kmipEncryption
168+
}
169+
_, ok = securityOptions["vault"]
170+
if ok {
171+
encryptionType = vaultEncryption
172+
}
173+
_, ok = securityOptions["encryptionKeyFile"]
174+
if ok {
175+
encryptionType = localKeyFileEncryption
176+
}
177+
178+
labels := map[string]string{"type": encryptionType}
179+
desc := prometheus.NewDesc("mongodb_security_encryption_enabled", "Shows that encryption is enabled",
180+
nil, labels)
181+
metric, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1))
182+
if err != nil {
183+
return nil, errors.Wrap(err, "cannot create metric mongodb_security_encryption_enabled")
184+
}
185+
186+
return metric, nil
187+
}
188+
114189
// check interface.
115190
var _ prometheus.Collector = (*diagnosticDataCollector)(nil)

exporter/encryption_info_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// mongodb_exporter
2+
// Copyright (C) 2017 Percona LLC
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package exporter
18+
19+
import (
20+
"context"
21+
"io"
22+
"strings"
23+
"testing"
24+
"time"
25+
26+
"github.com/prometheus/client_golang/prometheus/testutil"
27+
"github.com/sirupsen/logrus"
28+
"github.com/stretchr/testify/assert"
29+
30+
"github.com/percona/mongodb_exporter/internal/tu"
31+
)
32+
33+
func TestGetEncryptionInfo(t *testing.T) {
34+
t.Parallel()
35+
36+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
37+
defer cancel()
38+
39+
client := tu.TestClient(ctx, tu.MongoDBStandAloneEncryptedPort, t)
40+
t.Cleanup(func() {
41+
err := client.Disconnect(ctx)
42+
assert.NoError(t, err)
43+
})
44+
logger := logrus.New()
45+
logger.Out = io.Discard // disable logs in tests
46+
47+
ti := labelsGetterMock{}
48+
49+
c := newDiagnosticDataCollector(ctx, client, logger, true, ti)
50+
51+
// The last \n at the end of this string is important
52+
expected := strings.NewReader(`
53+
# HELP mongodb_security_encryption_enabled Shows that encryption is enabled
54+
# TYPE mongodb_security_encryption_enabled gauge
55+
mongodb_security_encryption_enabled{type="localKeyFile"} 1
56+
# HELP mongodb_version_info The server version
57+
# TYPE mongodb_version_info gauge
58+
mongodb_version_info{edition="Community",mongodb="5.0.13-11",vendor="Percona"} 1` + "\n")
59+
60+
filter := []string{
61+
"mongodb_security_encryption_enabled",
62+
"mongodb_version_info",
63+
}
64+
65+
err := testutil.CollectAndCompare(c, expected, filter...)
66+
assert.NoError(t, err)
67+
}

internal/tu/testutils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const (
5050
MongoDBStandAlonePort = "27017"
5151
// MongoDBConfigServer1Port MongoDB config server primary Port.
5252
MongoDBConfigServer1Port = "17009"
53+
// MongoDBStandAloneEncryptedPort MongoDB standalone encrypted instance Port.
54+
MongoDBStandAloneEncryptedPort = "27027"
5355
)
5456

5557
// GetenvDefault gets a variable from the environment and returns its value or the

0 commit comments

Comments
 (0)