Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid leaking passwords into shell history and audits events by supporting reading passwords from stdin #881

Merged
merged 8 commits into from
Nov 17, 2021
2 changes: 1 addition & 1 deletion docs/command/mongocli-atlas-security-ldap-save.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Options
- An LDAP query template that Atlas executes to obtain the LDAP groups to which the authenticated user belongs.
* - --bindPassword
- string
- true
- false
- The password used to authenticate the bindUsername.
* - --bindUsername
- string
Expand Down
2 changes: 1 addition & 1 deletion docs/command/mongocli-atlas-security-ldap-verify.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Options
- An LDAP query template that Atlas executes to obtain the LDAP groups to which the authenticated user belongs.
* - --bindPassword
- string
- true
- false
- The password used to authenticate the bindUsername.
* - --bindUsername
- string
Expand Down
119 changes: 85 additions & 34 deletions e2e/atlas/dbusers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package atlas_test

import (
"bytes"
"encoding/json"
"fmt"
"os"
Expand All @@ -30,23 +31,37 @@ import (

const (
roleReadWrite = "readWrite"
userPassword = "passW0rd"
scopeClusterDataLake = "Cluster0,Cluster1:CLUSTER"
clusterName0 = "Cluster0"
clusterName1 = "Cluster1"
clusterType = "CLUSTER"
)

func TestDBUsers(t *testing.T) {
func generateUsername() (string, error) {
n, err := e2e.RandInt(1000)
if err != nil {
return "", err
}
return fmt.Sprintf("user-%v", n), nil
}

func TestDBUsers(t *testing.T) {
username, err := generateUsername()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

stdinUsername, err := generateUsername()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
username := fmt.Sprintf("user-%v", n)

cliPath, err := e2e.Bin()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

t.Run("Create", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
Expand All @@ -55,27 +70,30 @@ func TestDBUsers(t *testing.T) {
"atlasAdmin",
"--deleteAfter", time.Now().AddDate(0, 0, 1).Format(time.RFC3339),
"--username", username,
"--password=passW0rd",
"--password", userPassword,
"--scope", scopeClusterDataLake,
"-o=json",
)
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
a := assert.New(t)
a.NoError(err)

var user mongodbatlas.DatabaseUser
if err := json.Unmarshal(resp, &user); err != nil {
t.Fatalf("unexpected error: %v", err)
}
testCreatePasswordCmd(t, cmd, username)
})

a.Equal(username, user.Username)
if a.Len(user.Scopes, 2) {
a.Equal(user.Scopes[0].Name, clusterName0)
a.Equal(user.Scopes[0].Type, clusterType)
a.Equal(user.Scopes[1].Name, clusterName1)
a.Equal(user.Scopes[0].Type, clusterType)
}
t.Run("CreateWithPasswordFromStdin", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
dbusersEntity,
"create",
"atlasAdmin",
"--deleteAfter", time.Now().AddDate(0, 0, 1).Format(time.RFC3339),
"--username", stdinUsername,
"--scope", scopeClusterDataLake,
"-o=json",
)

passwordStdin := bytes.NewBuffer([]byte(fmt.Sprintf("%s", userPassword)))
cmd.Stdin = passwordStdin

testCreatePasswordCmd(t, cmd, stdinUsername)
})

t.Run("List", func(t *testing.T) {
Expand Down Expand Up @@ -133,7 +151,10 @@ func TestDBUsers(t *testing.T) {
roleReadWrite,
"--scope",
clusterName0,
"--password",
userPassword,
"-o=json")

cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()

Expand All @@ -159,22 +180,52 @@ func TestDBUsers(t *testing.T) {
})

t.Run("Delete", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
dbusersEntity,
"delete",
username,
"--force",
"--authDB",
"admin")
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
testDeleteUser(t, cliPath, atlasEntity, dbusersEntity, username)
testDeleteUser(t, cliPath, atlasEntity, dbusersEntity, stdinUsername)
})
}

if err != nil {
t.Fatalf("unexpected error: %v, resp: %v", err, string(resp))
}
func testCreatePasswordCmd(t *testing.T, cmd *exec.Cmd, username string) {
t.Helper()

expected := fmt.Sprintf("DB user '%s' deleted\n", username)
assert.Equal(t, expected, string(resp))
})
cmd.Env = os.Environ()

resp, err := cmd.CombinedOutput()
a := assert.New(t)
a.NoError(err)

var user mongodbatlas.DatabaseUser
if err := json.Unmarshal(resp, &user); err != nil {
t.Fatalf("unexpected error: %v", err)
}

a.Equal(username, user.Username)
if a.Len(user.Scopes, 2) {
a.Equal(user.Scopes[0].Name, clusterName0)
a.Equal(user.Scopes[0].Type, clusterType)
a.Equal(user.Scopes[1].Name, clusterName1)
a.Equal(user.Scopes[1].Type, clusterType)
}
}

func testDeleteUser(t *testing.T, cliPath, atlasEntity, dbusersEntity, username string) {
t.Helper()

cmd := exec.Command(cliPath,
atlasEntity,
dbusersEntity,
"delete",
username,
"--force",
"--authDB",
"admin")
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()

if err != nil {
t.Fatalf("unexpected error: %v, resp: %v", err, string(resp))
}

expected := fmt.Sprintf("DB user '%s' deleted\n", username)
assert.Equal(t, expected, string(resp))
}
113 changes: 91 additions & 22 deletions e2e/atlas/ldap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package atlas_test

import (
"bytes"
"encoding/json"
"os"
"os/exec"
Expand All @@ -28,9 +29,10 @@ import (
)

const (
pending = "PENDING"
ldapHostname = "localhost"
ldapPort = "19657"
pending = "PENDING"
ldapHostname = "localhost"
ldapPort = "19657"
ldapBindPassword = "admin"
)

func TestLDAP(t *testing.T) {
Expand All @@ -54,24 +56,38 @@ func TestLDAP(t *testing.T) {
"--bindUsername",
"cn=admin,dc=example,dc=org",
"--bindPassword",
"admin",
ldapBindPassword,
"--projectId", g.projectID,
"-o",
"json")
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))

a := assert.New(t)
var configuration mongodbatlas.LDAPConfiguration
if err := json.Unmarshal(resp, &configuration); a.NoError(err) {
a.Equal(pending, configuration.Status)
requestID = configuration.RequestID
}
requestID = testLDAPVerifyCmd(t, cmd)
})

require.NotEmpty(t, requestID)

t.Run("VerifyPasswordStdin", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
securityEntity,
ldapEntity,
"verify",
"--hostname",
ldapHostname,
"--port",
ldapPort,
"--bindUsername",
"cn=admin,dc=example,dc=org",
"--projectId", g.projectID,
"-o",
"json")

passwordStdin := bytes.NewBuffer([]byte(ldapBindPassword))
cmd.Stdin = passwordStdin

requestID = testLDAPVerifyCmd(t, cmd)
})

t.Run("Watch", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
Expand Down Expand Up @@ -125,7 +141,7 @@ func TestLDAP(t *testing.T) {
"--bindUsername",
"cn=admin,dc=example,dc=org",
"--bindPassword",
"admin",
ldapBindPassword,
"--mappingMatch",
"(.+)@ENGINEERING.EXAMPLE.COM",
"--mappingSubstitution",
Expand All @@ -134,15 +150,35 @@ func TestLDAP(t *testing.T) {
"-o",
"json",
)
cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))

a := assert.New(t)
var configuration mongodbatlas.LDAPConfiguration
if err := json.Unmarshal(resp, &configuration); a.NoError(err) {
a.Equal(ldapHostname, configuration.LDAP.Hostname)
}
testLDAPSaveCmd(t, cmd)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 [2021/11/15 15:41:25.385] FAILURE: Failed ()
 [2021/11/15 15:41:25.385] === RUN   TestLDAPWithFlags/Save
 [2021/11/15 15:41:25.385]     ldap_test.go:132:
 [2021/11/15 15:41:25.385]         	Error Trace:	ldap_test.go:270
 [2021/11/15 15:41:25.385]         	            				ldap_test.go:132
 [2021/11/15 15:41:25.385]         	Error:      	Not equal:
 [2021/11/15 15:41:25.385]         	            	expected: "admin"
 [2021/11/15 15:41:25.385]         	            	actual  : ""
 [2021/11/15 15:41:25.385] 
 [2021/11/15 15:41:25.385]         	            	Diff:
 [2021/11/15 15:41:25.385]         	            	--- Expected
 [2021/11/15 15:41:25.385]         	            	+++ Actual
 [2021/11/15 15:41:25.385]         	            	@@ -1 +1 @@
 [2021/11/15 15:41:25.385]         	            	-admin
 [2021/11/15 15:41:25.385]         	            	+
 [2021/11/15 15:41:25.385]         	Test:       	TestLDAPWithFlags/Save
 [2021/11/15 15:41:25.385]     --- FAIL: TestLDAPWithFlags/Save (0.09s)

})

t.Run("SavePasswordStdin", func(t *testing.T) {
cmd := exec.Command(cliPath,
atlasEntity,
securityEntity,
ldapEntity,
"save",
"--hostname",
ldapHostname,
"--port",
ldapPort,
"--bindUsername",
"cn=admin,dc=example,dc=org",
"--mappingMatch",
"(.+)@ENGINEERING.EXAMPLE.COM",
"--mappingSubstitution",
"cn={0},ou=engineering,dc=example,dc=com",
"--projectId", g.projectID,
"-o",
"json",
)

passwordStdin := bytes.NewBuffer([]byte(ldapBindPassword))
cmd.Stdin = passwordStdin

testLDAPSaveCmd(t, cmd)
})

t.Run("Get", func(t *testing.T) {
Expand Down Expand Up @@ -180,3 +216,36 @@ func TestLDAP(t *testing.T) {
assert.Contains(t, string(resp), "LDAP configuration userToDNMapping deleted")
})
}

func testLDAPVerifyCmd(t *testing.T, cmd *exec.Cmd) string {
t.Helper()

cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))

a := assert.New(t)
var configuration mongodbatlas.LDAPConfiguration
if err := json.Unmarshal(resp, &configuration); a.NoError(err) {
a.Equal(pending, configuration.Status)
a.Equal(ldapBindPassword, configuration.LDAP.BindPassword)
return configuration.RequestID
}

return ""
}

func testLDAPSaveCmd(t *testing.T, cmd *exec.Cmd) {
t.Helper()

cmd.Env = os.Environ()
resp, err := cmd.CombinedOutput()
require.NoError(t, err, string(resp))

a := assert.New(t)
var configuration mongodbatlas.LDAPConfiguration
if err := json.Unmarshal(resp, &configuration); a.NoError(err) {
a.Equal(ldapHostname, configuration.LDAP.Hostname)
a.Equal(ldapBindPassword, configuration.LDAP.BindPassword)
}
}
Loading