Skip to content

Commit 52928af

Browse files
CLOUDP-97443: add access tracking to the atlas go client (#241)
1 parent 44df5dc commit 52928af

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed

mongodbatlas/access_tracking.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2021 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mongodbatlas
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"net/http"
21+
)
22+
23+
const accessTrackingBasePath = "api/atlas/v1.0/groups/%s/dbAccessHistory"
24+
25+
// AccessTrackingService is an interface for interfacing with the Access Tracking endpoints of the MongoDB Atlas API.
26+
//
27+
// See more: https://docs.atlas.mongodb.com/reference/api/access-tracking/
28+
type AccessTrackingService interface {
29+
ListByCluster(context.Context, string, string, *AccessLogOptions) (*AccessLogSettings, *Response, error)
30+
ListByHostname(context.Context, string, string, *AccessLogOptions) (*AccessLogSettings, *Response, error)
31+
}
32+
33+
// AccessTrackingServiceOp handles communication with the AccessTrackingService related methods of the
34+
// MongoDB Atlas API.
35+
type AccessTrackingServiceOp service
36+
37+
var _ AccessTrackingService = &AccessTrackingServiceOp{}
38+
39+
// AccessLogOptions represents the query options of AccessTrackingService.List.
40+
type AccessLogOptions struct {
41+
Start string `url:"start,omitempty"` // Start is the timestamp in the number of milliseconds that have elapsed since the UNIX epoch for the first entry that Atlas returns from the database access logs.
42+
End string `url:"end,omitempty"` // End is the timestamp in the number of milliseconds that have elapsed since the UNIX epoch for the last entry that Atlas returns from the database access logs.
43+
NLogs int `url:"nLogs,omitempty"` // NLogs is the maximum number of log entries to return. Atlas accepts values between 0 and 20000, inclusive.
44+
IPAddress string `url:"ipAddress,omitempty"` // IPAddress is the single IP address that attempted to authenticate with the database. Atlas filters the returned logs to include documents with only this IP address.
45+
AuthResult *bool `url:"authResult,omitempty"` // AuthResult indicates whether to return either successful or failed authentication attempts. When set to true, Atlas filters the log to return only successful authentication attempts. When set to false, Atlas filters the log to return only failed authentication attempts.
46+
}
47+
48+
// AccessLogs represents authentication attempts made against the cluster.
49+
type AccessLogs struct {
50+
GroupID string `json:"groupId,omitempty"` // GroupID is the unique identifier for the project.
51+
Hostname string `json:"hostname,omitempty"` // Hostname is the hostname of the target node that received the authentication attempt.
52+
ClusterName string `json:"clusterName,omitempty"` // ClusterName is the name associated with the cluster.
53+
IPAddress string `json:"ipAddress,omitempty"` // IPAddress is the IP address that the authentication attempt originated from.
54+
AuthResult *bool `json:"authResult,omitempty"` // AuthResult is the result of the authentication attempt. Returns true if the authentication request was successful. Returns false if the authentication request resulted in failure.
55+
LogLine string `json:"logLine,omitempty"` // LogLine is the text of the server log concerning the authentication attempt.
56+
Timestamp string `json:"timestamp,omitempty"` // Timestamp is the UTC timestamp of the authentication attempt.
57+
Username string `json:"username,omitempty"` // Username is the username that attempted to authenticate.
58+
FailureReason string `json:"failureReason,omitempty"` // FailureReason is the reason that the request failed to authenticate. Returns null if the authentication request was successful.
59+
AuthSource string `json:"authSource,omitempty"` // AuthSource is the database that the request attempted to authenticate against. Returns admin if the authentication source for the user is SCRAM-SHA. Returns $external if the authentication source for the user is LDAP.
60+
}
61+
62+
// AccessLogSettings represents database access history settings.
63+
type AccessLogSettings struct {
64+
AccessLogs []*AccessLogs `json:"accessLogs,omitempty"` // AccessLogs contains the authentication attempts made against the cluster.
65+
}
66+
67+
// ListByCluster retrieves the access logs of a cluster by hostname.
68+
//
69+
// See more: https://docs.atlas.mongodb.com/reference/api/access-tracking-get-database-history-hostname/
70+
func (s *AccessTrackingServiceOp) ListByCluster(ctx context.Context, groupID, clusterName string, opts *AccessLogOptions) (*AccessLogSettings, *Response, error) {
71+
if groupID == "" {
72+
return nil, nil, NewArgError("groupID", "must be set")
73+
}
74+
75+
if clusterName == "" {
76+
return nil, nil, NewArgError("clusterName", "must be set")
77+
}
78+
79+
basePath := fmt.Sprintf(accessTrackingBasePath, groupID)
80+
path := fmt.Sprintf("%s/clusters/%s", basePath, clusterName)
81+
path, err := setListOptions(path, opts)
82+
if err != nil {
83+
return nil, nil, err
84+
}
85+
86+
req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil)
87+
if err != nil {
88+
return nil, nil, err
89+
}
90+
91+
var root *AccessLogSettings
92+
resp, err := s.Client.Do(ctx, req, &root)
93+
if err != nil {
94+
return nil, resp, err
95+
}
96+
97+
return root, resp, nil
98+
}
99+
100+
// ListByHostname retrieves the access logs of a cluster by hostname.
101+
//
102+
// See more: https://docs.atlas.mongodb.com/reference/api/access-tracking-get-database-history-hostname/
103+
func (s *AccessTrackingServiceOp) ListByHostname(ctx context.Context, groupID, hostname string, opts *AccessLogOptions) (*AccessLogSettings, *Response, error) {
104+
if groupID == "" {
105+
return nil, nil, NewArgError("groupID", "must be set")
106+
}
107+
108+
if hostname == "" {
109+
return nil, nil, NewArgError("hostname", "must be set")
110+
}
111+
112+
basePath := fmt.Sprintf(accessTrackingBasePath, groupID)
113+
path := fmt.Sprintf("%s/processes/%s", basePath, hostname)
114+
path, err := setListOptions(path, opts)
115+
if err != nil {
116+
return nil, nil, err
117+
}
118+
119+
req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil)
120+
if err != nil {
121+
return nil, nil, err
122+
}
123+
124+
var root *AccessLogSettings
125+
resp, err := s.Client.Do(ctx, req, &root)
126+
if err != nil {
127+
return nil, resp, err
128+
}
129+
130+
return root, resp, nil
131+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright 2021 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mongodbatlas
16+
17+
import (
18+
"fmt"
19+
"net/http"
20+
"testing"
21+
22+
"github.com/go-test/deep"
23+
)
24+
25+
func TestAccessTracking_ListByCluster(t *testing.T) {
26+
client, mux, teardown := setup()
27+
defer teardown()
28+
29+
mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/dbAccessHistory/clusters/%s", groupID, clusterName), func(w http.ResponseWriter, r *http.Request) {
30+
testMethod(t, r, http.MethodGet)
31+
fmt.Fprint(w, `{
32+
"accessLogs": [
33+
{
34+
"authResult": true,
35+
"authSource": "admin",
36+
"groupId": "1",
37+
"hostname": "cluster0-shard-00-00-c01ab.mongodb.net:27017",
38+
"clusterName": "globalCluster",
39+
"ipAddress": "123.45.0.1",
40+
"logLine": "2019-07-25T19:14:42.484+0000 I ACCESS [conn2167] Successfully authenticated as principal jon-snow on admin from client 123.45.0.1:8080",
41+
"timestamp": "Sun Jul 25 9:14:42 EDT 2019",
42+
"username": "jon-snow"
43+
},
44+
{
45+
"authResult": true,
46+
"authSource": "admin",
47+
"failureReason": "UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
48+
"groupId": "1",
49+
"hostname": "cluster0-shard-00-00-c01ab.mongodb.net:27017",
50+
"clusterName": "globalCluster",
51+
"ipAddress": "123.45.2.2",
52+
"logLine": "2019-07-25T19:13:39.316+0000 I ACCESS [conn1893] SASL SCRAM-SHA-1 authentication failed for jane-doe on admin from client 123.45.2.2:51842 ; UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
53+
"timestamp": "Sun Jul 25 9:14:42 EDT 2019",
54+
"username": "jane-doe"
55+
}]
56+
}`)
57+
})
58+
authResult := true
59+
opts := &AccessLogOptions{
60+
Start: "1564064082000",
61+
End: "1564064082000",
62+
NLogs: 1,
63+
IPAddress: "123.45.2.2",
64+
AuthResult: &authResult,
65+
}
66+
67+
results, _, err := client.AccessTracking.ListByCluster(ctx, groupID, clusterName, opts)
68+
if err != nil {
69+
t.Fatalf("AccessTracking.ListByCluster returned error: %v", err)
70+
}
71+
72+
expected := &AccessLogSettings{
73+
AccessLogs: []*AccessLogs{
74+
{
75+
GroupID: "1",
76+
Hostname: "cluster0-shard-00-00-c01ab.mongodb.net:27017",
77+
ClusterName: "globalCluster",
78+
IPAddress: "123.45.0.1",
79+
LogLine: "2019-07-25T19:14:42.484+0000 I ACCESS [conn2167] Successfully authenticated as principal jon-snow on admin from client 123.45.0.1:8080",
80+
Timestamp: "Sun Jul 25 9:14:42 EDT 2019",
81+
Username: "jon-snow",
82+
AuthSource: "admin",
83+
AuthResult: &authResult,
84+
},
85+
{
86+
GroupID: "1",
87+
Hostname: "cluster0-shard-00-00-c01ab.mongodb.net:27017",
88+
ClusterName: "globalCluster",
89+
IPAddress: "123.45.2.2",
90+
LogLine: "2019-07-25T19:13:39.316+0000 I ACCESS [conn1893] SASL SCRAM-SHA-1 authentication failed for jane-doe on admin from client 123.45.2.2:51842 ; UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
91+
Timestamp: "Sun Jul 25 9:14:42 EDT 2019",
92+
Username: "jane-doe",
93+
AuthSource: "admin",
94+
AuthResult: &authResult,
95+
FailureReason: "UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
96+
},
97+
},
98+
}
99+
100+
if diff := deep.Equal(results, expected); diff != nil {
101+
t.Error(diff)
102+
}
103+
}
104+
105+
func TestAccessTracking_ListByHostname(t *testing.T) {
106+
client, mux, teardown := setup()
107+
defer teardown()
108+
109+
hostname := "cluster0-shard-00-00-c01ab.mongodb.net:27017"
110+
mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/dbAccessHistory/processes/%s", groupID, hostname), func(w http.ResponseWriter, r *http.Request) {
111+
testMethod(t, r, http.MethodGet)
112+
fmt.Fprint(w, `{
113+
"accessLogs": [
114+
{
115+
"authResult": true,
116+
"authSource": "admin",
117+
"groupId": "1",
118+
"hostname": "cluster0-shard-00-00-c01ab.mongodb.net:27017",
119+
"clusterName": "globalCluster",
120+
"ipAddress": "123.45.0.1",
121+
"logLine": "2019-07-25T19:14:42.484+0000 I ACCESS [conn2167] Successfully authenticated as principal jon-snow on admin from client 123.45.0.1:8080",
122+
"timestamp": "Sun Jul 25 9:14:42 EDT 2019",
123+
"username": "jon-snow"
124+
},
125+
{
126+
"authResult": true,
127+
"authSource": "admin",
128+
"failureReason": "UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
129+
"groupId": "1",
130+
"hostname": "cluster0-shard-00-00-c01ab.mongodb.net:27017",
131+
"clusterName": "globalCluster",
132+
"ipAddress": "123.45.2.2",
133+
"logLine": "2019-07-25T19:13:39.316+0000 I ACCESS [conn1893] SASL SCRAM-SHA-1 authentication failed for jane-doe on admin from client 123.45.2.2:51842 ; UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
134+
"timestamp": "Sun Jul 25 9:14:42 EDT 2019",
135+
"username": "jane-doe"
136+
}]
137+
}`)
138+
})
139+
140+
authResult := true
141+
opts := &AccessLogOptions{
142+
Start: "1564064082000",
143+
End: "1564064082000",
144+
NLogs: 1,
145+
IPAddress: "123.45.2.2",
146+
AuthResult: &authResult,
147+
}
148+
149+
results, _, err := client.AccessTracking.ListByHostname(ctx, groupID, hostname, opts)
150+
if err != nil {
151+
t.Fatalf("AccessTracking.ListByHostname returned error: %v", err)
152+
}
153+
154+
expected := &AccessLogSettings{
155+
AccessLogs: []*AccessLogs{
156+
{
157+
GroupID: "1",
158+
Hostname: "cluster0-shard-00-00-c01ab.mongodb.net:27017",
159+
ClusterName: "globalCluster",
160+
IPAddress: "123.45.0.1",
161+
LogLine: "2019-07-25T19:14:42.484+0000 I ACCESS [conn2167] Successfully authenticated as principal jon-snow on admin from client 123.45.0.1:8080",
162+
Timestamp: "Sun Jul 25 9:14:42 EDT 2019",
163+
Username: "jon-snow",
164+
AuthSource: "admin",
165+
AuthResult: &authResult,
166+
},
167+
{
168+
GroupID: "1",
169+
Hostname: "cluster0-shard-00-00-c01ab.mongodb.net:27017",
170+
ClusterName: "globalCluster",
171+
IPAddress: "123.45.2.2",
172+
LogLine: "2019-07-25T19:13:39.316+0000 I ACCESS [conn1893] SASL SCRAM-SHA-1 authentication failed for jane-doe on admin from client 123.45.2.2:51842 ; UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
173+
Timestamp: "Sun Jul 25 9:14:42 EDT 2019",
174+
Username: "jane-doe",
175+
AuthSource: "admin",
176+
AuthResult: &authResult,
177+
FailureReason: "UserNotFound: Could not find user \"jane-doe\" for db \"admin\"",
178+
},
179+
},
180+
}
181+
182+
if diff := deep.Equal(results, expected); diff != nil {
183+
t.Error(diff)
184+
}
185+
}

mongodbatlas/mongodbatlas.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ type Client struct {
139139
AdvancedClusters AdvancedClustersService
140140
ServerlessInstances ServerlessInstancesService
141141
LiveMigration LiveMigrationService
142+
AccessTracking AccessTrackingService
142143

143144
onRequestCompleted RequestCompletionCallback
144145
}
@@ -277,6 +278,7 @@ func NewClient(httpClient *http.Client) *Client {
277278
c.AdvancedClusters = &AdvancedClustersServiceOp{Client: c}
278279
c.ServerlessInstances = &ServerlessInstancesServiceOp{Client: c}
279280
c.LiveMigration = &LiveMigrationServiceOp{Client: c}
281+
c.AccessTracking = &AccessTrackingServiceOp{Client: c}
280282

281283
return c
282284
}

0 commit comments

Comments
 (0)