Skip to content

Commit cdf4d72

Browse files
authored
CLOUDP-59682: Enable security on C/OM (#75)
1 parent 9fff8dd commit cdf4d72

11 files changed

+391
-27
lines changed

.githooks/pre-commit

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
$!/usr/bin/env bash
22

3-
echo "==> Running pre-commit git hook..."
4-
make fmt check
3+
STAGED_GO_FILES=$(git diff --cached --name-only | grep ".go$")
4+
5+
for FILE in $STAGED_GO_FILES
6+
do
7+
gofmt -w -s $FILE
8+
goimports -w $FILE
9+
done
10+
11+
make check

internal/cli/ops_manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func OpsManagerBuilder() *cobra.Command {
3232
cmd.AddCommand(AtlasBackupsBuilder())
3333
cmd.AddCommand(OpsManagerServersBuilder())
3434
cmd.AddCommand(OpsManagerAutomationBuilder())
35+
cmd.AddCommand(OpsManagerSecurityBuilder())
3536
cmd.AddCommand(OpsManagerDBUsersBuilder())
3637

3738
return cmd

internal/cli/ops_manager_security.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2020 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 cli
16+
17+
import (
18+
"github.com/mongodb/mongocli/internal/description"
19+
"github.com/spf13/cobra"
20+
)
21+
22+
func OpsManagerSecurityBuilder() *cobra.Command {
23+
cmd := &cobra.Command{
24+
Use: "security",
25+
Short: description.Security,
26+
}
27+
28+
cmd.AddCommand(OpsManagerSecurityEnableBuilder())
29+
return cmd
30+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2020 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 cli
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/mongodb/mongocli/internal/config"
21+
"github.com/mongodb/mongocli/internal/convert"
22+
"github.com/mongodb/mongocli/internal/description"
23+
"github.com/mongodb/mongocli/internal/flags"
24+
"github.com/mongodb/mongocli/internal/messages"
25+
"github.com/mongodb/mongocli/internal/store"
26+
"github.com/mongodb/mongocli/internal/usage"
27+
"github.com/spf13/cobra"
28+
)
29+
30+
const (
31+
cr = "MONGODB-CR"
32+
sha1 = "SCRAM-SHA-1"
33+
sha256 = "SCRAM-SHA-256"
34+
)
35+
36+
type opsManagerSecurityEnableOpts struct {
37+
*globalOpts
38+
mechanisms []string
39+
store store.AutomationPatcher
40+
}
41+
42+
func (opts *opsManagerSecurityEnableOpts) init() error {
43+
if opts.ProjectID() == "" {
44+
return errMissingProjectID
45+
}
46+
var err error
47+
opts.store, err = store.New()
48+
return err
49+
}
50+
51+
func (opts *opsManagerSecurityEnableOpts) Run() error {
52+
current, err := opts.store.GetAutomationConfig(opts.ProjectID())
53+
54+
if err != nil {
55+
return err
56+
}
57+
58+
if err = convert.EnableMechanism(current, opts.mechanisms); err != nil {
59+
return err
60+
}
61+
if err = opts.store.UpdateAutomationConfig(opts.ProjectID(), current); err != nil {
62+
return err
63+
}
64+
65+
fmt.Print(messages.DeploymentStatus(config.OpsManagerURL(), opts.ProjectID()))
66+
67+
return nil
68+
}
69+
70+
// mongocli ops-manager security enable[MONGODB-CR|SCRAM-SHA-256] [--projectId projectId]
71+
func OpsManagerSecurityEnableBuilder() *cobra.Command {
72+
opts := &opsManagerSecurityEnableOpts{
73+
globalOpts: newGlobalOpts(),
74+
}
75+
cmd := &cobra.Command{
76+
Use: fmt.Sprintf("enable [%s|%s]", cr, sha256),
77+
Short: description.EnableSecurity,
78+
Args: cobra.OnlyValidArgs,
79+
ValidArgs: []string{cr, sha1, sha256},
80+
PreRunE: func(cmd *cobra.Command, args []string) error {
81+
return opts.init()
82+
},
83+
RunE: func(cmd *cobra.Command, args []string) error {
84+
opts.mechanisms = args
85+
return opts.Run()
86+
},
87+
}
88+
89+
cmd.Flags().StringVar(&opts.projectID, flags.ProjectID, "", usage.ProjectID)
90+
91+
return cmd
92+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2020 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 cli
16+
17+
import (
18+
"testing"
19+
20+
"github.com/golang/mock/gomock"
21+
"github.com/mongodb/mongocli/internal/fixtures"
22+
"github.com/mongodb/mongocli/internal/mocks"
23+
)
24+
25+
func TestOpsManagerSecurityEnableOpts_Run(t *testing.T) {
26+
ctrl := gomock.NewController(t)
27+
mockStore := mocks.NewMockAutomationPatcher(ctrl)
28+
29+
defer ctrl.Finish()
30+
31+
expected := fixtures.AutomationConfig()
32+
33+
createOpts := &opsManagerSecurityEnableOpts{
34+
globalOpts: newGlobalOpts(),
35+
mechanisms: []string{"something"},
36+
store: mockStore,
37+
}
38+
39+
mockStore.
40+
EXPECT().
41+
GetAutomationConfig(createOpts.projectID).
42+
Return(expected, nil).
43+
Times(1)
44+
45+
mockStore.
46+
EXPECT().
47+
UpdateAutomationConfig(createOpts.projectID, expected).
48+
Return(nil).
49+
Times(1)
50+
51+
err := createOpts.Run()
52+
if err != nil {
53+
t.Fatalf("Run() unexpected error: %v", err)
54+
}
55+
}

internal/convert/automation_config.go

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import (
2222
)
2323

2424
const (
25-
mongod = "mongod"
25+
mongod = "mongod"
26+
atmAgentWindowsKeyFilePath = "%SystemDrive%\\MMSAutomation\\versions\\keyfile"
27+
atmAgentKeyFilePathInContainer = "/var/lib/mongodb-mms-automation/keyfile"
2628
)
2729

2830
// FromAutomationConfig convert from cloud format to mCLI format
@@ -53,6 +55,25 @@ func FromAutomationConfig(in *om.AutomationConfig) (out []ClusterConfig) {
5355
return
5456
}
5557

58+
func setDisabledByClusterName(out *om.AutomationConfig, name string, disabled bool) {
59+
// This value may not be present and is mandatory
60+
if out.Auth.DeploymentAuthMechanisms == nil {
61+
out.Auth.DeploymentAuthMechanisms = make([]string, 0)
62+
}
63+
for _, rs := range out.ReplicaSets {
64+
if rs.ID == name {
65+
for _, m := range rs.Members {
66+
for k, p := range out.Processes {
67+
if p.Name == m.Host {
68+
out.Processes[k].Disabled = disabled
69+
}
70+
}
71+
}
72+
break
73+
}
74+
}
75+
}
76+
5677
// Shutdown a cluster processes
5778
func Shutdown(out *om.AutomationConfig, name string) {
5879
setDisabledByClusterName(out, name, true)
@@ -63,6 +84,64 @@ func Startup(out *om.AutomationConfig, name string) {
6384
setDisabledByClusterName(out, name, false)
6485
}
6586

87+
func EnableMechanism(out *om.AutomationConfig, m []string) error {
88+
out.Auth.DeploymentAuthMechanisms = append(out.Auth.DeploymentAuthMechanisms, m...)
89+
out.Auth.AutoAuthMechanisms = append(out.Auth.AutoAuthMechanisms, m...)
90+
out.Auth.Disabled = false
91+
92+
var err error
93+
if out.Auth.AutoUser == "" {
94+
if err := setAutoUser(out); err != nil {
95+
return err
96+
}
97+
98+
}
99+
addMonitoringUser(out)
100+
addBackupUser(out)
101+
102+
if out.Auth.Key == "" {
103+
if out.Auth.Key, err = generateRandomBase64String(500); err != nil {
104+
return err
105+
}
106+
}
107+
if out.Auth.KeyFile == "" {
108+
out.Auth.KeyFile = atmAgentKeyFilePathInContainer
109+
}
110+
if out.Auth.KeyFileWindows == "" {
111+
out.Auth.KeyFileWindows = atmAgentWindowsKeyFilePath
112+
}
113+
114+
return nil
115+
}
116+
117+
func addMonitoringUser(out *om.AutomationConfig) {
118+
_, exists := search.MongoDBUsers(out.Auth.Users, func(user *om.MongoDBUser) bool {
119+
return user.Username == monitoringAgentName
120+
})
121+
if !exists {
122+
AddUser(out, newMonitoringUser(out.Auth.AutoPwd))
123+
}
124+
}
125+
126+
func addBackupUser(out *om.AutomationConfig) {
127+
_, exists := search.MongoDBUsers(out.Auth.Users, func(user *om.MongoDBUser) bool {
128+
return user.Username == backupAgentName
129+
})
130+
if !exists {
131+
AddUser(out, newBackupUser(out.Auth.AutoPwd))
132+
}
133+
}
134+
135+
func setAutoUser(out *om.AutomationConfig) error {
136+
var err error
137+
out.Auth.AutoUser = automationAgentName
138+
if out.Auth.AutoPwd, err = generateRandomASCIIString(500); err != nil {
139+
return err
140+
}
141+
142+
return nil
143+
}
144+
66145
// AddUser adds a MongoDBUser to the config
67146
func AddUser(out *om.AutomationConfig, u *om.MongoDBUser) {
68147
out.Auth.Users = append(out.Auth.Users, u)
@@ -78,25 +157,6 @@ func RemoveUser(out *om.AutomationConfig, username string, database string) {
78157
}
79158
}
80159

81-
func setDisabledByClusterName(out *om.AutomationConfig, name string, disabled bool) {
82-
// This value may not be present and is mandatory
83-
if out.Auth.DeploymentAuthMechanisms == nil {
84-
out.Auth.DeploymentAuthMechanisms = make([]string, 0)
85-
}
86-
for _, rs := range out.ReplicaSets {
87-
if rs.ID == name {
88-
for _, m := range rs.Members {
89-
for k, p := range out.Processes {
90-
if p.Name == m.Host {
91-
out.Processes[k].Disabled = disabled
92-
}
93-
}
94-
}
95-
break
96-
}
97-
}
98-
}
99-
100160
// convertCloudMember map cloudmanager.Member -> convert.ProcessConfig
101161
func convertCloudMember(out *ProcessConfig, in om.Member) {
102162
out.Votes = in.Votes

internal/convert/automation_config_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,33 @@ func TestRemoveUser(t *testing.T) {
9595
t.Error("User not removed\n")
9696
}
9797
}
98+
99+
func TestEnableMechanism(t *testing.T) {
100+
config := fixtures.AutomationConfigWithoutMongoDBUsers()
101+
102+
e := EnableMechanism(config, []string{"SCRAM-SHA-256"})
103+
104+
if e != nil {
105+
t.Fatalf("EnableMechanism() unexpected error: %v\n", e)
106+
}
107+
108+
if config.Auth.Disabled {
109+
t.Error("config.Auth.Disabled is true\n")
110+
}
111+
112+
if config.Auth.AutoAuthMechanisms[0] != "SCRAM-SHA-256" {
113+
t.Error("AutoAuthMechanisms not set\n")
114+
}
115+
116+
if config.Auth.AutoUser == "" || config.Auth.AutoPwd == "" {
117+
t.Error("config.Auth.Auto* not set\n")
118+
}
119+
120+
if config.Auth.Key == "" || config.Auth.KeyFileWindows == "" || config.Auth.KeyFile == "" {
121+
t.Error("config.Auth.Key* not set\n")
122+
}
123+
124+
if len(config.Auth.Users) != 2 {
125+
t.Error("automation and monitoring users not set\n")
126+
}
127+
}

internal/convert/cluster_config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func TestClusterConfig_PatchAutomationConfig(t *testing.T) {
433433
t.Run(name, func(t *testing.T) {
434434
err := tc.changes.PatchAutomationConfig(tc.current)
435435
if err != nil {
436-
t.Fatalf("PatchAutomationConfig() unexpected error: %v", err)
436+
t.Fatalf("PatchAutomationConfig() unexpected error: %v\n", err)
437437
}
438438
if diff := deep.Equal(tc.current, tc.expected); diff != nil {
439439
t.Error(diff)
@@ -460,7 +460,7 @@ func TestProtocolVersion(t *testing.T) {
460460
t.Run(name, func(t *testing.T) {
461461
ver, err := protocolVer(tc.mdbVersion)
462462
if err != nil {
463-
t.Fatalf("protocolVer() unexpected error: %v", err)
463+
t.Fatalf("protocolVer() unexpected error: %v\n", err)
464464
}
465465
if ver != tc.protocolVersion {
466466
t.Errorf("protocolVer() expected: %s but got: %s", tc.protocolVersion, ver)

0 commit comments

Comments
 (0)