Skip to content

Commit d5e37a1

Browse files
CLOUDP-82637: Implement X509 auth (#1345)
* Implement X509 auth * Move mocks * Merge master into x509-client-authentication * Remove ubuntu images for agent * Improve test suite and small fixes * Remove some code duplication * Add unit tests for cleanup and reconciler * Watch agent cert secret and small refactor * Update api/v1/mongodbcommunity_types.go Co-authored-by: mircea-cosbuc <[email protected]> * Resolve comments * Add e2e test and suggestions * Add e2e test to actions * Remove unused error * Release notes and samples * Validate agent subject name * Extract packages from util * Improve e2e test * Increase tls test time interval * Change to camel case * Sort imports * Revert "Remove ubuntu images for agent" This reverts commit e1256a9. * Refactor Configurable interface * Pin new helm-charts * Set helm args in x509 test * Check for not found secret * Change naming of tests --------- Co-authored-by: mircea-cosbuc <[email protected]>
1 parent 555910e commit d5e37a1

File tree

55 files changed

+2692
-570
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2692
-570
lines changed

.action_templates/jobs/tests.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ tests:
6262
distro: ubi
6363
- test-name: replica_set_connection_string_options
6464
distro: ubi
65+
- test-name: replica_set_x509
66+
distro: ubi

.github/workflows/e2e-fork.yml

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ jobs:
144144
distro: ubi
145145
- test-name: replica_set_connection_string_options
146146
distro: ubi
147+
- test-name: replica_set_x509
148+
distro: ubi
147149
steps:
148150
# template: .action_templates/steps/cancel-previous.yaml
149151
- name: Cancel Previous Runs

.github/workflows/e2e.yml

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ jobs:
150150
distro: ubi
151151
- test-name: replica_set_connection_string_options
152152
distro: ubi
153+
- test-name: replica_set_x509
154+
distro: ubi
153155
steps:
154156
# template: .action_templates/steps/cancel-previous.yaml
155157
- name: Cancel Previous Runs

api/v1/mongodbcommunity_types.go

+128-59
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,23 @@ package v1
33
import (
44
"encoding/json"
55
"fmt"
6-
"net/url"
6+
"regexp"
77
"strings"
88

9-
"k8s.io/apimachinery/pkg/runtime"
10-
11-
"github.com/stretchr/objx"
12-
13-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/authentication/scram"
14-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/annotations"
15-
169
appsv1 "k8s.io/api/apps/v1"
1710
corev1 "k8s.io/api/core/v1"
18-
19-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
20-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/scale"
21-
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
2213
"k8s.io/apimachinery/pkg/runtime/schema"
23-
2414
"k8s.io/apimachinery/pkg/types"
25-
26-
"regexp"
27-
28-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2915
"k8s.io/apimachinery/pkg/util/validation"
16+
17+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/authentication/authtypes"
18+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
19+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/annotations"
20+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/constants"
21+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/scale"
22+
"github.com/stretchr/objx"
3023
)
3124

3225
type Type string
@@ -452,20 +445,22 @@ type MongoDBUser struct {
452445
DB string `json:"db,omitempty"`
453446

454447
// PasswordSecretRef is a reference to the secret containing this user's password
455-
PasswordSecretRef SecretKeyReference `json:"passwordSecretRef"`
448+
// +optional
449+
PasswordSecretRef SecretKeyReference `json:"passwordSecretRef,omitempty"`
456450

457451
// Roles is an array of roles assigned to this user
458452
Roles []Role `json:"roles"`
459453

460454
// ScramCredentialsSecretName appended by string "scram-credentials" is the name of the secret object created by the mongoDB operator for storing SCRAM credentials
461455
// These secrets names must be different for each user in a deployment.
462456
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
463-
ScramCredentialsSecretName string `json:"scramCredentialsSecretName"`
457+
// +optional
458+
ScramCredentialsSecretName string `json:"scramCredentialsSecretName,omitempty"`
464459

465460
// ConnectionStringSecretName is the name of the secret object created by the operator which exposes the connection strings for the user.
466461
// If provided, this secret must be different for each user in a deployment.
467462
// +optional
468-
ConnectionStringSecretName string `json:"connectionStringSecretName"`
463+
ConnectionStringSecretName string `json:"connectionStringSecretName,omitempty"`
469464

470465
// Additional options to be appended to the connection string.
471466
// These options apply only to this user and will override any existing options in the resource.
@@ -584,6 +579,18 @@ type Authentication struct {
584579
// Modes is an array specifying which authentication methods should be enabled.
585580
Modes []AuthMode `json:"modes"`
586581

582+
// AgentMode contains the authentication mode used by the automation agent.
583+
// +optional
584+
AgentMode AuthMode `json:"agentMode,omitempty"`
585+
586+
// AgentCertificateSecret is a reference to a Secret containing the certificate and the key for the automation agent
587+
// The secret needs to have available:
588+
// - certificate under key: "tls.crt"
589+
// - private key under key: "tls.key"
590+
// If additionally, tls.pem is present, then it needs to be equal to the concatenation of tls.crt and tls.key
591+
// +optional
592+
AgentCertificateSecret *corev1.LocalObjectReference `json:"agentCertificateSecretRef,omitempty"`
593+
587594
// IgnoreUnknownUsers set to true will ensure any users added manually (not through the CRD)
588595
// will not be removed.
589596

@@ -595,17 +602,28 @@ type Authentication struct {
595602
IgnoreUnknownUsers *bool `json:"ignoreUnknownUsers,omitempty"`
596603
}
597604

598-
// +kubebuilder:validation:Enum=SCRAM;SCRAM-SHA-256;SCRAM-SHA-1
605+
// +kubebuilder:validation:Enum=SCRAM;SCRAM-SHA-256;SCRAM-SHA-1;X509
599606
type AuthMode string
600607

608+
func IsAuthPresent(authModes []AuthMode, auth string) bool {
609+
for _, authMode := range authModes {
610+
if string(authMode) == auth {
611+
return true
612+
}
613+
}
614+
return false
615+
}
616+
601617
// ConvertAuthModeToAuthMechanism acts as a map but is immutable. It allows users to use different labels to describe the
602618
// same authentication mode.
603619
func ConvertAuthModeToAuthMechanism(authModeLabel AuthMode) string {
604620
switch authModeLabel {
605621
case "SCRAM", "SCRAM-SHA-256":
606-
return scram.Sha256
622+
return constants.Sha256
607623
case "SCRAM-SHA-1":
608-
return scram.Sha1
624+
return constants.Sha1
625+
case "X509":
626+
return constants.X509
609627
default:
610628
return ""
611629
}
@@ -673,52 +691,51 @@ func (m *MongoDBCommunity) GetOwnerReferences() []metav1.OwnerReference {
673691
return []metav1.OwnerReference{ownerReference}
674692
}
675693

676-
// GetScramOptions returns a set of Options that are used to configure scram
694+
// GetAuthOptions returns a set of Options that are used to configure scram
677695
// authentication.
678-
func (m *MongoDBCommunity) GetScramOptions() scram.Options {
696+
func (m *MongoDBCommunity) GetAuthOptions() authtypes.Options {
679697
ignoreUnknownUsers := true
680698
if m.Spec.Security.Authentication.IgnoreUnknownUsers != nil {
681699
ignoreUnknownUsers = *m.Spec.Security.Authentication.IgnoreUnknownUsers
682700
}
683701

684702
authModes := m.Spec.Security.Authentication.Modes
685703
defaultAuthMechanism := ConvertAuthModeToAuthMechanism(defaultMode)
686-
var autoAuthMechanism string
704+
autoAuthMechanism := ConvertAuthModeToAuthMechanism(m.Spec.GetAgentAuthMode())
687705
authMechanisms := make([]string, len(authModes))
688706

689-
if len(authModes) == 0 {
707+
if autoAuthMechanism == "" {
690708
autoAuthMechanism = defaultAuthMechanism
691-
} else {
692-
autoAuthMechanism = ConvertAuthModeToAuthMechanism(authModes[0])
709+
}
693710

711+
if len(authModes) == 0 {
712+
authMechanisms = []string{defaultAuthMechanism}
713+
} else {
694714
for i, authMode := range authModes {
695715
if authMech := ConvertAuthModeToAuthMechanism(authMode); authMech != "" {
696716
authMechanisms[i] = authMech
697-
if authMech == defaultAuthMechanism {
698-
autoAuthMechanism = defaultAuthMechanism
699-
}
700717
}
701718
}
702719
}
703720

704-
return scram.Options{
705-
AuthoritativeSet: !ignoreUnknownUsers,
706-
KeyFile: scram.AutomationAgentKeyFilePathInContainer,
707-
AutoAuthMechanisms: authMechanisms,
708-
AgentName: scram.AgentName,
709-
AutoAuthMechanism: autoAuthMechanism,
721+
return authtypes.Options{
722+
AuthoritativeSet: !ignoreUnknownUsers,
723+
KeyFile: constants.AutomationAgentKeyFilePathInContainer,
724+
AuthMechanisms: authMechanisms,
725+
AgentName: constants.AgentName,
726+
AutoAuthMechanism: autoAuthMechanism,
710727
}
711728
}
712729

713-
// GetScramUsers converts all of the users from the spec into users
714-
// that can be used to configure scram authentication.
715-
func (m *MongoDBCommunity) GetScramUsers() []scram.User {
716-
users := make([]scram.User, len(m.Spec.Users))
730+
// GetAuthUsers converts all the users from the spec into users
731+
// that can be used to configure authentication.
732+
func (m *MongoDBCommunity) GetAuthUsers() []authtypes.User {
733+
users := make([]authtypes.User, len(m.Spec.Users))
717734
for i, u := range m.Spec.Users {
718-
roles := make([]scram.Role, len(u.Roles))
735+
roles := make([]authtypes.Role, len(u.Roles))
719736
for j, r := range u.Roles {
720737

721-
roles[j] = scram.Role{
738+
roles[j] = authtypes.Role{
722739
Name: r.Name,
723740
Database: r.DB,
724741
}
@@ -734,20 +751,76 @@ func (m *MongoDBCommunity) GetScramUsers() []scram.User {
734751
u.DB = defaultDBForUser
735752
}
736753

737-
users[i] = scram.User{
754+
users[i] = authtypes.User{
738755
Username: u.Name,
739756
Database: u.DB,
740757
Roles: roles,
741-
PasswordSecretKey: u.GetPasswordSecretKey(),
742-
PasswordSecretName: u.PasswordSecretRef.Name,
743-
ScramCredentialsSecretName: u.GetScramCredentialsSecretName(),
744758
ConnectionStringSecretName: u.GetConnectionStringSecretName(m.Name),
745759
ConnectionStringOptions: u.AdditionalConnectionStringConfig.Object,
746760
}
761+
762+
if u.DB != constants.ExternalDB {
763+
users[i].ScramCredentialsSecretName = u.GetScramCredentialsSecretName()
764+
users[i].PasswordSecretKey = u.GetPasswordSecretKey()
765+
users[i].PasswordSecretName = u.PasswordSecretRef.Name
766+
}
747767
}
748768
return users
749769
}
750770

771+
// AgentCertificateSecretNamespacedName returns the namespaced name of the secret containing the agent certificate.
772+
func (m *MongoDBCommunity) AgentCertificateSecretNamespacedName() types.NamespacedName {
773+
return types.NamespacedName{
774+
Namespace: m.Namespace,
775+
Name: m.Spec.GetAgentCertificateRef(),
776+
}
777+
}
778+
779+
// AgentCertificatePemSecretNamespacedName returns the namespaced name of the secret containing the agent certificate in pem format.
780+
func (m *MongoDBCommunity) AgentCertificatePemSecretNamespacedName() types.NamespacedName {
781+
return types.NamespacedName{
782+
Namespace: m.Namespace,
783+
Name: m.Spec.GetAgentCertificateRef() + "-pem",
784+
}
785+
}
786+
787+
// GetAgentCertificateRef returns the name of the secret containing the agent certificate.
788+
// If it is specified in the CR, it will return this. Otherwise, it default to agent-certs.
789+
func (m *MongoDBCommunitySpec) GetAgentCertificateRef() string {
790+
agentCertSecret := "agent-certs"
791+
if m.Security.Authentication.AgentCertificateSecret != nil && m.Security.Authentication.AgentCertificateSecret.Name != "" {
792+
agentCertSecret = m.Security.Authentication.AgentCertificateSecret.Name
793+
}
794+
return agentCertSecret
795+
}
796+
797+
// GetAgentAuthMode return the agent authentication mode. If the agent auth mode is specified, it will return this.
798+
// Otherwise, if the spec.security.authentication.modes array is empty, it will default to SCRAM-SHA-256.
799+
// If spec.security.authentication.modes has one element, the agent auth mode will default to that.
800+
// If spec.security.authentication.modes has more than one element, then agent auth will need to be specified,
801+
// with one exception: if spec.security.authentication.modes contains only SCRAM-SHA-256 and SCRAM-SHA-1, then it defaults to SCRAM-SHA-256 (for backwards compatibility).
802+
func (m *MongoDBCommunitySpec) GetAgentAuthMode() AuthMode {
803+
if m.Security.Authentication.AgentMode != "" {
804+
return m.Security.Authentication.AgentMode
805+
}
806+
807+
if len(m.Security.Authentication.Modes) == 0 {
808+
return "SCRAM-SHA-256"
809+
} else if len(m.Security.Authentication.Modes) == 1 {
810+
return m.Security.Authentication.Modes[0]
811+
} else if len(m.Security.Authentication.Modes) == 2 {
812+
if (IsAuthPresent(m.Security.Authentication.Modes, "SCRAM") || IsAuthPresent(m.Security.Authentication.Modes, "SCRAM-SHA-256")) &&
813+
IsAuthPresent(m.Security.Authentication.Modes, "SCRAM-SHA-1") {
814+
return "SCRAM-SHA-256"
815+
}
816+
}
817+
return ""
818+
}
819+
820+
func (m *MongoDBCommunitySpec) IsAgentX509() bool {
821+
return m.GetAgentAuthMode() == "X509"
822+
}
823+
751824
// IsStillScaling returns true if this resource is currently scaling,
752825
// considering both arbiters and regular members.
753826
func (m *MongoDBCommunity) IsStillScaling() bool {
@@ -817,7 +890,7 @@ func (m *MongoDBCommunity) GetOptionsString() string {
817890
//
818891
// Takes into account both user options and resource options.
819892
// User options will override any existing options in the resource.
820-
func (m *MongoDBCommunity) GetUserOptionsString(user scram.User) string {
893+
func (m *MongoDBCommunity) GetUserOptionsString(user authtypes.User) string {
821894
generalOptionsMap := m.Spec.AdditionalConnectionStringConfig.Object
822895
userOptionsMap := user.ConnectionStringOptions
823896
optionValues := make([]string, len(generalOptionsMap)+len(userOptionsMap))
@@ -866,12 +939,10 @@ func (m *MongoDBCommunity) MongoSRVURI(clusterDomain string) string {
866939

867940
// MongoAuthUserURI returns a mongo uri which can be used to connect to this deployment
868941
// and includes the authentication data for the user
869-
func (m *MongoDBCommunity) MongoAuthUserURI(user scram.User, password string, clusterDomain string) string {
942+
func (m *MongoDBCommunity) MongoAuthUserURI(user authtypes.User, password string, clusterDomain string) string {
870943
optionsString := m.GetUserOptionsString(user)
871-
872-
return fmt.Sprintf("mongodb://%s:%s@%s/%s?replicaSet=%s&ssl=%t%s",
873-
url.QueryEscape(user.Username),
874-
url.QueryEscape(password),
944+
return fmt.Sprintf("mongodb://%s%s/%s?replicaSet=%s&ssl=%t%s",
945+
user.GetLoginString(password),
875946
strings.Join(m.Hosts(clusterDomain), ","),
876947
user.Database,
877948
m.Name,
@@ -881,16 +952,14 @@ func (m *MongoDBCommunity) MongoAuthUserURI(user scram.User, password string, cl
881952

882953
// MongoAuthUserSRVURI returns a mongo srv uri which can be used to connect to this deployment
883954
// and includes the authentication data for the user
884-
func (m *MongoDBCommunity) MongoAuthUserSRVURI(user scram.User, password string, clusterDomain string) string {
955+
func (m *MongoDBCommunity) MongoAuthUserSRVURI(user authtypes.User, password string, clusterDomain string) string {
885956
if clusterDomain == "" {
886957
clusterDomain = defaultClusterDomain
887958
}
888959

889960
optionsString := m.GetUserOptionsString(user)
890-
891-
return fmt.Sprintf("mongodb+srv://%s:%s@%s.%s.svc.%s/%s?replicaSet=%s&ssl=%t%s",
892-
url.QueryEscape(user.Username),
893-
url.QueryEscape(password),
961+
return fmt.Sprintf("mongodb+srv://%s%s.%s.svc.%s/%s?replicaSet=%s&ssl=%t%s",
962+
user.GetLoginString(password),
894963
m.ServiceName(),
895964
m.Namespace,
896965
clusterDomain,

0 commit comments

Comments
 (0)