diff --git a/e2e-tests/disabled-auth/conf/backup-minio.yml b/e2e-tests/disabled-auth/conf/backup-minio.yml new file mode 100644 index 0000000000..9549c64fdf --- /dev/null +++ b/e2e-tests/disabled-auth/conf/backup-minio.yml @@ -0,0 +1,9 @@ +apiVersion: psmdb.percona.com/v1 +kind: PerconaServerMongoDBBackup +metadata: + finalizers: + - percona.com/delete-backup + name: backup-minio +spec: + clusterName: some-name + storageName: minio \ No newline at end of file diff --git a/e2e-tests/disabled-auth/conf/cluster-no-auth.yml b/e2e-tests/disabled-auth/conf/cluster-no-auth.yml new file mode 100644 index 0000000000..4486a98264 --- /dev/null +++ b/e2e-tests/disabled-auth/conf/cluster-no-auth.yml @@ -0,0 +1,128 @@ +apiVersion: psmdb.percona.com/v1 +kind: PerconaServerMongoDB +metadata: + name: some-name +spec: + image: + imagePullPolicy: Always + updateStrategy: SmartUpdate + tls: + mode: disabled + unsafeFlags: + tls: true + backup: + enabled: true + image: perconalab/percona-server-mongodb-operator:1.1.0-backup + storages: + minio: + type: s3 + s3: + credentialsSecret: minio-secret + region: us-east-1 + bucket: operator-testing + endpointUrl: http://minio-service:9000/ + insecureSkipTLSVerify: false + sharding: + enabled: true + + configsvrReplSet: + size: 3 + affinity: + antiAffinityTopologyKey: none + configuration: | + net: + port: 27018 + security: + authorization: disabled + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 3Gi + expose: + enabled: true + type: ClusterIP + + mongos: + size: 3 + affinity: + antiAffinityTopologyKey: none + configuration: | + net: + port: 27019 + expose: + type: ClusterIP + + replsets: + - name: rs0 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 + security: + authorization: disabled + - name: rs1 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 + security: + authorization: disabled + - name: rs2 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 + security: + authorization: disabled \ No newline at end of file diff --git a/e2e-tests/disabled-auth/conf/cluster-with-auth.yml b/e2e-tests/disabled-auth/conf/cluster-with-auth.yml new file mode 100644 index 0000000000..681e3d4f0b --- /dev/null +++ b/e2e-tests/disabled-auth/conf/cluster-with-auth.yml @@ -0,0 +1,120 @@ +apiVersion: psmdb.percona.com/v1 +kind: PerconaServerMongoDB +metadata: + name: some-name +spec: + image: + imagePullPolicy: Always + updateStrategy: SmartUpdate + tls: + mode: disabled + unsafeFlags: + tls: true + backup: + enabled: true + image: perconalab/percona-server-mongodb-operator:1.1.0-backup + storages: + minio: + type: s3 + s3: + credentialsSecret: minio-secret + region: us-east-1 + bucket: operator-testing + endpointUrl: http://minio-service:9000/ + insecureSkipTLSVerify: false + sharding: + enabled: true + + configsvrReplSet: + size: 3 + affinity: + antiAffinityTopologyKey: none + configuration: | + net: + port: 27018 + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 3Gi + expose: + enabled: true + type: ClusterIP + + mongos: + size: 3 + affinity: + antiAffinityTopologyKey: none + configuration: | + net: + port: 27019 + expose: + type: ClusterIP + + replsets: + - name: rs0 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 + - name: rs1 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 + - name: rs2 + affinity: + antiAffinityTopologyKey: none + expose: + enabled: true + type: ClusterIP + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 0.1G + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 1Gi + size: 3 + configuration: | + net: + port: 27016 diff --git a/e2e-tests/disabled-auth/conf/restore.yml b/e2e-tests/disabled-auth/conf/restore.yml new file mode 100644 index 0000000000..ea8aa31053 --- /dev/null +++ b/e2e-tests/disabled-auth/conf/restore.yml @@ -0,0 +1,7 @@ +apiVersion: psmdb.percona.com/v1 +kind: PerconaServerMongoDBRestore +metadata: + name: +spec: + clusterName: some-name + backupName: \ No newline at end of file diff --git a/e2e-tests/disabled-auth/run b/e2e-tests/disabled-auth/run new file mode 100755 index 0000000000..e47ecb1d2c --- /dev/null +++ b/e2e-tests/disabled-auth/run @@ -0,0 +1,151 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +test_dir=$(realpath "$(dirname "$0")") +. "${test_dir}/../functions" +set_debug + +custom_port='27019' + +create_infra "$namespace" + +deploy_minio + +desc "create PSMDB sharded cluster without auth" +cluster="some-name" +kubectl_bin apply -f "$conf_dir/client.yml" + +apply_s3_storage_secrets + +apply_cluster "$test_dir/conf/cluster-no-auth.yml" + +desc 'wait for all pods to start' +wait_for_running $cluster-rs0 3 +wait_for_running $cluster-rs1 3 +wait_for_running $cluster-rs2 3 +wait_for_running $cluster-cfg 3 "false" +wait_for_running $cluster-mongos 3 + +sleep 10 + +desc 'write data without auth' +run_mongos \ + 'use myApp\n db.createCollection("test")' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port" +run_mongos \ + 'use myApp\n db.test.insert({x: 100500})' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'verify data was written' +run_mongos \ + 'use myApp\n db.test.find()' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'wait for backup agents' +wait_backup_agent $cluster-rs0-0 +wait_backup_agent $cluster-rs1-0 +wait_backup_agent $cluster-rs2-0 + +backup_name_no_auth="backup-no-auth" + +desc 'run backup without auth' +run_backup minio ${backup_name_no_auth} +wait_backup "$backup_name_no_auth" + +sleep 5 + +desc 'insert new data without auth' +run_mongos \ + 'use myApp\n db.test.insert({x: 100501})' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'verify new data exists' +count_no_auth_after=$(run_mongos \ + 'use myApp\n db.test.find()' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port") +echo "Find after insert: $count_no_auth_after" + +desc 'restore from backup (no auth)' +run_restore "$backup_name_no_auth" +wait_restore "$backup_name_no_auth" "$cluster" + +sleep 20 + +desc 'verify data was restored to original state' +count_no_auth_restored=$(run_mongos \ + 'use myApp\n db.test.find()' \ + "@$cluster-mongos.$namespace" "" "" "" "$custom_port") +echo "Find after restore: $count_no_auth_restored" + +desc 'enable authentication' +kubectl_bin apply -f "$conf_dir/secrets.yml" +apply_cluster "$test_dir/conf/cluster-with-auth.yml" + +desc 'wait for cluster to restart with auth enabled' +wait_for_running $cluster-rs0 3 +wait_for_running $cluster-rs1 3 +wait_for_running $cluster-rs2 3 +wait_for_running $cluster-cfg 3 "false" +wait_for_running $cluster-mongos 3 + +sleep 30 + +desc 'create users with auth enabled' +run_mongos \ + 'db.createUser({user:"myApp",pwd:"myPass",roles:[{db:"myApp",role:"readWrite"}]})' \ + "userAdmin:userAdmin123456@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'verify existing data is still accessible with auth' +count_with_auth=$(run_mongos \ + 'use myApp\n db.test.find()' \ + "myApp:myPass@$cluster-mongos.$namespace" "" "" "" "$custom_port") +echo "Find with auth enabled: $count_with_auth" + +desc 'insert new data with auth' +run_mongos \ + 'use myApp\n db.test.insert({x: 200500})' \ + "myApp:myPass@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'wait for backup agents after auth change' +wait_backup_agent $cluster-rs0-0 +wait_backup_agent $cluster-rs1-0 +wait_backup_agent $cluster-rs2-0 + +backup_name_with_auth="backup-with-auth" + +desc 'run backup with auth enabled' +run_backup minio ${backup_name_with_auth} +wait_backup "$backup_name_with_auth" + +sleep 5 + +desc 'insert more data with auth' +run_mongos \ + 'use myApp\n db.test.insert({x: 200501})' \ + "myApp:myPass@$cluster-mongos.$namespace" "" "" "" "$custom_port" + +desc 'verify new data exists' +count_with_auth_after=$(run_mongos \ + 'use myApp\n db.test.find()' \ + "myApp:myPass@$cluster-mongos.$namespace" "" "" "" "$custom_port") +echo "Find after insert with auth: $count_with_auth_after" + +desc 'restore from backup (with auth)' +run_restore "$backup_name_with_auth" +wait_restore "$backup_name_with_auth" "$cluster" + +sleep 10 + +desc 'verify data was restored to state before last insert' +count_with_auth_restored=$(run_mongos \ + 'use myApp\n db.test.find()' \ + "myApp:myPass@$cluster-mongos.$namespace" "" "" "" "$custom_port") +echo "Find after restore with auth: $count_with_auth_restored" + +desc 'cleanup backups' +kubectl_bin delete psmdb-backup --all + +destroy "$namespace" +desc 'test passed' \ No newline at end of file diff --git a/pkg/apis/psmdb/v1/psmdb_types.go b/pkg/apis/psmdb/v1/psmdb_types.go index 4cfcae332a..dba8c80652 100644 --- a/pkg/apis/psmdb/v1/psmdb_types.go +++ b/pkg/apis/psmdb/v1/psmdb_types.go @@ -628,6 +628,25 @@ func (conf MongoConfiguration) QuietEnabled() bool { return b } +// IsAuthorizationEnabled returns whether mongo config has `authorization` enabled under `security` section. +// https://www.mongodb.com/docs/manual/reference/configuration-options/#mongodb-setting-security.authorization +func (conf MongoConfiguration) IsAuthorizationEnabled() bool { + m, err := conf.GetOptions("security") + if err != nil || m == nil { + return true + } + v, ok := m["authorization"] + if !ok { + return true + } + + if str, ok := v.(string); ok { + return str != "disabled" + } + + return true +} + // GetPort returns the net.port of the mongo configuration. // https://www.mongodb.com/docs/manual/reference/configuration-options/#mongodb-setting-net.port func (conf MongoConfiguration) GetPort() (int32, error) { diff --git a/pkg/apis/psmdb/v1/psmdb_types_test.go b/pkg/apis/psmdb/v1/psmdb_types_test.go index 3bbdc63a4f..da70dc0343 100644 --- a/pkg/apis/psmdb/v1/psmdb_types_test.go +++ b/pkg/apis/psmdb/v1/psmdb_types_test.go @@ -146,6 +146,69 @@ func TestReplsetSpec_GetPort(t *testing.T) { } } +func TestMongoConfiguration_IsAuthorizationEnabled(t *testing.T) { + tests := map[string]struct { + conf MongoConfiguration + expected bool + }{ + "no security section": { + conf: `systemLog: + verbosity: 1`, + expected: true, + }, + "empty config": { + conf: MongoConfiguration(""), + expected: true, + }, + "security section without authorization": { + conf: `security: + keyFile: /etc/mongodb-keyfile`, + expected: true, + }, + "authorization explicitly enabled": { + conf: `security: + authorization: enabled`, + expected: true, + }, + "authorization explicitly disabled": { + conf: `security: + authorization: disabled`, + expected: false, + }, + "authorization with other string value": { + conf: `security: + authorization: someOtherValue`, + expected: true, + }, + "authorization with empty string": { + conf: `security: + authorization: ""`, + expected: true, + }, + "complete security config with authorization enabled": { + conf: `security: + keyFile: /etc/mongodb-keyfile + authorization: enabled + clusterAuthMode: keyFile`, + expected: true, + }, + "complete security config with authorization disabled": { + conf: `security: + keyFile: /etc/mongodb-keyfile + authorization: disabled + clusterAuthMode: keyFile`, + expected: false, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + result := tt.conf.IsAuthorizationEnabled() + assert.Equal(t, tt.expected, result) + }) + } +} + func TestBackupSpec_MainStorage(t *testing.T) { tests := map[string]struct { spec BackupSpec diff --git a/pkg/psmdb/container.go b/pkg/psmdb/container.go index ab527afcad..5994fa2433 100644 --- a/pkg/psmdb/container.go +++ b/pkg/psmdb/container.go @@ -195,17 +195,29 @@ func containerArgs(ctx context.Context, cr *api.PerconaServerMongoDB, replset *a // TODO(andrew): in the safe mode `sslAllowInvalidCertificates` should be set only with the external services args := []string{ "--bind_ip_all", - "--auth", - "--dbpath=" + config.MongodContainerDataDir, - "--port=" + strconv.Itoa(int(replset.GetPort())), - "--replSet=" + replset.Name, - "--storageEngine=" + string(replset.Storage.Engine), - "--relaxPermChecks", } + if cr.CompareVersion("1.22.0") < 0 || replset.Configuration.IsAuthorizationEnabled() { + args = append(args, "--auth") + } + + args = append(args, + "--dbpath="+config.MongodContainerDataDir, + "--port="+strconv.Itoa(int(replset.GetPort())), + "--replSet="+replset.Name, + "--storageEngine="+string(replset.Storage.Engine), + "--relaxPermChecks", + ) + name, err := replset.CustomReplsetName() if err == nil { - args[4] = "--replSet=" + name + // given that --auth option is optional, we cannot rely on the fixed hardcoded index. + for i, arg := range args { + if len(arg) >= 9 && arg[:9] == "--replSet" { + args[i] = "--replSet=" + name + break + } + } } if *cr.Spec.TLS.AllowInvalidCertificates || cr.CompareVersion("1.16.0") < 0 {