We are excited to announce Percona Operator for PostgreSQL 2.9.0! In this release, we bring significant improvements across database lifecycle management, security, backup/restore, and operational observability, making it easier than ever to run production PostgreSQL on Kubernetes. Here’s a deep dive into what’s new. Percona Operator for PostgreSQL 2.9.0 PostgreSQL 18 Is Now the […]
01
2026
Percona Operator for PostgreSQL 2.9.0: PostgreSQL 18 Default, PVC Snapshot Backups, LDAP Support, and More!
07
2025
LDAP Isn’t Going Away, and Neither Is Our Support for Percona Server for MongoDB
As enterprise software vendors race toward proprietary cloud ecosystems, some features long relied upon by businesses are being quietly deprecated. One recent example is MongoDB Enterprise Advanced and Atlas dropping support for LDAP authentication, a foundational identity protocol for countless organizations. At Percona, we’re taking a different path. We’ve supported LDAP in Percona Server for MongoDB for […]
07
2025
LDAP Isn’t Going Away, and Neither Is Our Support for Percona Server for MongoDB
As enterprise software vendors race toward proprietary cloud ecosystems, some features long relied upon by businesses are being quietly deprecated. One recent example is MongoDB Enterprise Advanced and Atlas dropping support for LDAP authentication, a foundational identity protocol for countless organizations. At Percona, we’re taking a different path. We’ve supported LDAP in Percona Server for MongoDB for […]
27
2024
Adding TLS to LDAP Authentication in Percona Operator for MongoDB
As per K8SPSMDB-732, TLS is now supported with LDAP authentication on Percona Operator for MongoDB 1.16.0 and above. This feature has been documented here as well. I’ve written a previous article on using LDAP authentication and authorization without TLS, so let me provide the instructions here on incorporating TLS with LDAP. First, you need to […]
30
2024
LDAP Authentication in PgBouncer Through PAM
There are many cases where external connection poolers like pgBouncer become unavoidable despite the costs and complexities associated with them. PgBouncer is one of the most popular external connection poolers for PostgreSQL. It is thin and lightweight, so it doesn’t have built-in authentication features like LDAP, which is essential for many enterprises. Luckily, pgBouncer has […]
06
2022
LDAP Improvements in Percona Server for MySQL 8.0.30-22

Percona Server for MySQL 8.0.30-22 introduces several improvements for the LDAP plugin: SASL support, support for fallback servers, and support for LDAP-based authorization. All improvements are in technical preview.
A description of the existing plugin features and usage instructions are available in the earlier blog post introducing the LDAP simple plugin, and in the Percona Server for MySQL documentation.
SASL connections
We introduced a new plugin similar to the MySQL Enterprise authentication_ldap_sasl. This new plugin is configured the same way as the simple authentication plugin, but the “simple” in the variable names is changed to “sasl”. For example, the variable authentication_ldap_simple_server_host is called authentication_ldap_sasl_server_host.
On the client side, instead of the clear password plugin, the SASL authentication uses the authentication_ldap_sasl_client plugin, which means that the MySQL authentication is secure even without using an SSL connection between the MySQL server and MySQL client. While SASL supports multiple authentication mechanisms, the plugin currently only supports the SCRAM-SHA-1 mechanism.
Please note that while the MySQL authentication is secure even without using SSL, for SASL to work, the LDAP server has to store the password either in clear text or in a reversibly encrypted form, which is less secure than the default hashed form. Because of this, using the simple plugin with an SSL connection between the MySQL server and client should be preferred over the SASL plugin where possible.
Fallback server
Both the simple and SASL plugins support one or more fallback servers. This is implemented using two-two new variables:
- authentication_ldap_<simple/sasl>_fallback_server_host
- authentication_ldap_<simple/sasl>_fallback_server_port
By default the host variable is empty, and the port variable is 0, which doesn’t change the default behavior, where only a single server is used.
By setting the port to the port of the LDAP server, and the host to a hostname, similarly to the main server and port variables, the plugins also start using the fallback server: when the primary server becomes unavailable, the server will try to use the fallback server. If it works, it will keep using the fallback server.
The plugins internally use connection pooling, and they won’t try to switch back to the fallback server automatically. While new connections will try to connect to the primary server first, existing connections might keep using the fallback server for a time, even if the primary is available again.
Alternatively, if multiple fallback servers are needed, it is also possible to leave the port variable at 0, and specify multiple connection strings in the host variable in the form “ldap://host1:port1,ldap://host2:port,….”.
This way the server will try to use all hosts before giving up.
LDAP-based authorization
Percona Server for MySQL 8.0.30-22 introduces a new feature, which not only allows authenticating using LDAP but also assigns MySQL roles to users based on their LDAP groups.
This feature is controlled by a global system variable, authentication_ldap_<simple/sasl>_group_role_mapping. The variable by default is empty, which doesn’t change the existing behavior, users authenticating with LDAP won’t get any additional roles.
To enable the feature, LDAP group and MySQL role pairs can be specified in the format “ldap_group1=mysql_role1,ldap_group2=mysql_role2,…”.
When authenticating with LDAP, if a user is a member of the “ldap_group1” LDAP group, it will automatically get assigned the “mysql_role1” role and all permissions associated with it. Similarly, if the user is part of the “ldap_group2” LDAP group, it gets assigned to the “mysql_role2” role.
Because these roles depend on authentication, they will only show up in commands such as SHOW GRANTS after the user logged on to the server at least once. For the same reason, these roles can’t be removed with ALTER USER, since it would be misleading: the next login would give the role back to the user.
Percona Distribution for MySQL is the most complete, stable, scalable, and secure, open-source MySQL solution available, delivering enterprise-grade database environments for your most critical business applications… and it’s free to use!
19
2022
Testing LDAP Authentication and Authorization on Percona Operator for MongoDB

As of Percona Operator for MongoDB 1.12.0, the documentation now has instructions on how to configure LDAP Authentication and Authorization. It already contains an example of how to configure the operator if OpenLDAP is your LDAP server. Here is another example of setting it up but using Samba as your LDAP server.
To simplify the installation and configuration, I will use Ubuntu Jammy 22.04 LTS since the distribution repository contains the packages to install Samba and Kubernetes.
This is the current configuration of the test server:
OS: Ubuntu Jammy 22.04 LTS
Hostname: samba.percona.local
IP Address: 192.168.0.101
Setting up Samba
Let’s install the necessary packages to install Samba as PDC and troubleshooting tools:
$ sudo apt update $ sudo apt -y upgrade $ sudo apt -y install samba net-tools winbind ldap-utils
Disable smbd, winbind, and systemd-resolved services because we will need to reconfigure samba as a PDC and DNS resolver. Also remove current samba configuration, /etc/samba/smb.conf.
$ sudo systemctl stop smbd $ sudo systemctl stop systemd-resolved $ sudo systemctl stop winbind $ sudo systemctl disable smbd $ sudo systemctl disable systemd-resolved $ sudo systemctl disable winbind $ sudo rm /etc/samba/smb.conf
Delete the symlink on /etc/resolv.conf and replace the content with “nameserver 127.0.0.1” to use the samba’s DNS service:
$ sudo rm -f /etc/resolv.conf $ sudo echo -e "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
Create a domain environment with the following settings:
Realm: PERCONA.LOCAL
Domain: PERCONA
Administrator Password: PerconaLDAPTest2022
$ sudo samba-tool domain provision --realm percona.local --domain percona --admin=PerconaLDAPTest2022
Edit /etc/samba/smb.conf and set DNS forwarder to 8.8.8.8 to resolve other zones. We will also disable mandatory TLS authentication since Percona Operator does not support LDAP with TLS at the time of writing this article.
$ cat /etc/samba/smb.conf # Global parameters [global] dns forwarder = 8.8.8.8 netbios name = SAMBA realm = PERCONA.LOCAL server role = active directory domain controller workgroup = PERCONA ldap server require strong auth = No [sysvol] path = /var/lib/samba/sysvol read only = No [netlogon] path = /var/lib/samba/sysvol/percona.local/scripts read only = No
Symlink krb5.conf configuration.
$ sudo ln -s /var/lib/samba/private/krb5.conf /etc
Unmask samba-ad-dc service and start it. Ensure it will start at boot time.
$ sudo systemctl unmask samba-ad-dc $ sudo systemctl start samba-ad-dc $ sudo systemctl enable samba-ad-dc
Check if the Samba services are up and running
$ sudo netstat -tapn|grep samba tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN 4376/samba: task[ld tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 4406/samba: task[dn tcp 0 0 0.0.0.0:636 0.0.0.0:* LISTEN 4376/samba: task[ld tcp 0 0 0.0.0.0:135 0.0.0.0:* LISTEN 4371/samba: task[rp tcp6 0 0 :::389 :::* LISTEN 4376/samba: task[ld tcp6 0 0 :::53 :::* LISTEN 4406/samba: task[dn tcp6 0 0 :::636 :::* LISTEN 4376/samba: task[ld tcp6 0 0 :::135 :::* LISTEN 4371/samba: task[rp $ host google.com google.com has address 172.217.194.101 $ host samba.percona.local samba.percona.local has address 192.168.0.101
Adding users and groups
Now that Samba is up and running, we can now perform user and group management. We will create Samba users and groups and assign users to groups with samba-tool.
$ sudo samba-tool user add dbauser01 --surname=User01 --given-name=Dba --mail-address=dbauser01@percona.local DbaPassword1 $ sudo samba-tool user add devuser01 --surname=User01 --given-name=Dev --mail-address=devuser01@percona.local DevPassword1 $ sudo samba-tool user add searchuser01 --surname=User01 --given-name=Search --mail-address=searchuser01@percona.local SearchPassword1 $ sudo samba-tool group add developers $ sudo samba-tool group add dbadmins $ sudo samba-tool group addmembers developers devuser01 $ sudo samba-tool group addmembers dbadmins dbauser01
Use samba-tool again to view the details of the users and groups:
$ sudo samba-tool user show devuser01 dn: CN=Dev User01,CN=Users,DC=percona,DC=local objectClass: person objectClass: user cn: Dev User01 sn: User01 givenName: Dev name: Dev User01 sAMAccountName: devuser01 mail: devuser01@percona.local memberOf: CN=developers,CN=Users,DC=percona,DC=local $ sudo samba-tool group show dbadmins dn: CN=dbadmins,CN=Users,DC=percona,DC=local objectClass: group cn: dbadmins name: dbadmins sAMAccountName: dbadmins member: CN=Dba User01,CN=Users,DC=percona,DC=local
Searching with ldapsearch
Troubleshooting LDAP starts with being able to use the ldapsearch tool to specify the credentials and filters. Once you are successful with authentication and searching, it’s easier to plug the same or similar parameters used in ldapsearch in the configuration of the Percona operator. Here are some examples of useful ldapsearch commands:
1. Logging in as “CN=Dev User01,CN=Users,DC=percona,DC=local”. If authenticated, return the DN, First Name, Last Name, email and sAMAccountName for that record.
$ ldapsearch -LLL -W -x -H ldap://samba.percona.local -b "CN=Dev User01,CN=Users,DC=percona,DC=local" -D "CN=Dev User01,CN=Users,DC=percona,DC=local" "givenName" "sn" "mail" "sAMAccountName" Enter LDAP Password: dn: CN=Dev User01,CN=Users,DC=percona,DC=local sn: User01 givenName: Dev sAMAccountName: devuser01 mail: devuser01@percona.local
Essentially, without mapping,you will need to supply the username as the full DN to login to MongoDB. Eg. mongo -u “CN=Dev User01,CN=Users,DC=percona,DC=local”
2. Logging in as “CN=Search User01,CN=Users,DC=percona,DC=local” and looking for users in “DC=percona,dc=local” where sAMAccountName is “dbauser01”. If there’s a match, it will return the DN, First Name, Last Name, mail and sAMAccountName for that record.
$ ldapsearch -LLL -W -x -H ldap://samba.percona.local -b "DC=percona,dc=local" -D "CN=Search User01,CN=Users,DC=percona,DC=local" "(&(objectClass=person)(sAMAccountName=dbauser01))" "givenName" "sn" "mail" "sAMAccountName" Enter LDAP Password: dn: CN=Dba User01,CN=Users,DC=percona,DC=local sn: User01 givenName: Dba sAMAccountName: dbauser01 mail: dbauser01@percona.local
With mapping, you can now authenticate by specifying sAMAaccountName or mail depending on how mapping is defined. Eg. mongo -u dbauser01 or mongo -u “dbauser01@percona.local”
3. Logging in as “CN=Search User01,CN=Users,DC=percona,DC=local”, looking for groups in “DC=percona,dc=local” where “CN=Dev User01,CN=Users,DC=percona,DC=local” is a member. If there’s a match, it will return the DN and common name of the group.
$ ldapsearch -LLL -W -x -H ldap://samba.percona.local -b "DC=percona,dc=local" -D "CN=Search User01,CN=Users,DC=percona,DC=local" "(&(objectClass=group)(member=CN=Dev User01,CN=Users,DC=percona,DC=local))" "cn" Enter LDAP Password: dn: CN=developers,CN=Users,DC=percona,DC=local cn: developers
This type of search is important to enumerate the groups of that user for we can define the privileges of that user based on its group membership.
Kubernetes installation and configuration
Now that authenticating to LDAP and search filters are working, we are ready to test this in the Percona Operator. Since this is just for testing, we might as well use the same server to deploy Kubernetes. In this example, we will use Microk8s.
$ sudo snap install microk8s --classic $ sudo usermod -a -G microk8s $USER $ sudo chown -f -R $USER ~/.kube $ newgrp microk8s $ microk8s status --wait-ready $ microk8s enable dns $ microk8s enable hostpath-storage $ alias kubectl='microk8s kubectl'
Once installed, check system pods when all are running before we continue to the next step:
$ kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-node-bj9c4 1/1 Running 0 3m12s kube-system coredns-66bcf65bb8-l9hwb 1/1 Running 0 65s kube-system calico-kube-controllers-644d5c79cb-fhhkc 1/1 Running 0 3m11s kube-system hostpath-provisioner-85ccc46f96-qmjrq 1/1 Running 0 3m
Deploying the Percona Operator for MongoDB
Now that Kubernetes is running, we can download the Percona Operator for MongoDB. Let’s download version 1.13.0 with git:
$ git clone -b v1.13.0 https://github.com/percona/percona-server-mongodb-operator
Then let’s go to the deploy directory and apply bundle.yaml to install the Percona operator:
$ cd percona-server-mongodb-operator/deploy $ kubectl apply -f bundle.yaml customresourcedefinition.apiextensions.k8s.io/perconaservermongodbs.psmdb.percona.com created customresourcedefinition.apiextensions.k8s.io/perconaservermongodbbackups.psmdb.percona.com created customresourcedefinition.apiextensions.k8s.io/perconaservermongodbrestores.psmdb.percona.com created role.rbac.authorization.k8s.io/percona-server-mongodb-operator created serviceaccount/percona-server-mongodb-operator created rolebinding.rbac.authorization.k8s.io/service-account-percona-server-mongodb-operator created deployment.apps/percona-server-mongodb-operator created
Check if the operator is up and running:
$ kubectl get pods NAME READY STATUS RESTARTS AGE percona-server-mongodb-operator-547c499bd8-p8k74 1/1 Running 0 41s
Now that it is running we need to apply cr.yaml to create the MongoDB instances and services. We will just use minimal deployment in cr-minimal.yaml which is provided in the deploy directory.
$ kubectl apply -f cr-minimal.yaml perconaservermongodb.psmdb.percona.com/my-cluster-name created
Wait until all pods are created:
$ kubectl get pods NAME READY STATUS RESTARTS AGE percona-server-mongodb-operator-547c499bd8-p8k74 1/1 Running 0 5m16s minimal-cluster-cfg-0 1/1 Running 0 3m25s minimal-cluster-rs0-0 1/1 Running 0 3m24s minimal-cluster-mongos-0 1/1 Running 0 3m24s
Setting up roles on the Percona Operator
Now that MongoDB pods are running, let’s add the groups for role-based mapping. We need to add this configuration from the primary config server which will be used by mongos and replicaset for authorization when logging in.
First, let’s get the username and password of the admin user:
$ kubectl get secrets NAME TYPE DATA AGE minimal-cluster Opaque 10 4m3s internal-minimal-cluster-users Opaque 10 4m3s minimal-cluster-mongodb-keyfile Opaque 1 4m3s minimal-cluster-mongodb-encryption-key Opaque 1 4m3s $ kubectl get secrets minimal-cluster -o yaml apiVersion: v1 data: MONGODB_BACKUP_PASSWORD: b2NNNkFjOHdEUU42OUpmYnE= MONGODB_BACKUP_USER: YmFja3Vw MONGODB_CLUSTER_ADMIN_PASSWORD: aElBWlVyajFkZWF0eEhWSzI= MONGODB_CLUSTER_ADMIN_USER: Y2x1c3RlckFkbWlu MONGODB_CLUSTER_MONITOR_PASSWORD: V1p6YkFhN1o3T2RkSm5Gbg== MONGODB_CLUSTER_MONITOR_USER: Y2x1c3Rlck1vbml0b3I= MONGODB_DATABASE_ADMIN_PASSWORD: U0hMR3Y3WlF2SVpxZ1dhcUFh MONGODB_DATABASE_ADMIN_USER: ZGF0YWJhc2VBZG1pbg== MONGODB_USER_ADMIN_PASSWORD: eW5TZjRzQjkybm5UdjdVdXduTQ== MONGODB_USER_ADMIN_USER: dXNlckFkbWlu kind: Secret metadata: creationTimestamp: "2022-09-15T15:57:42Z" name: minimal-cluster namespace: default resourceVersion: "5673" uid: d3f4f678-a3db-4578-b10c-69e8c4410b00 type: Opaque $ echo `echo "dXNlckFkbWlu"|base64 --decode` userAdmin $ echo `echo "eW5TZjRzQjkybm5UdjdVdXduTQ=="|base64 --decode` ynSf4sB92nnTv7UuwnM
Next, let’s connect to the primary config server:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.152.183.1 443/TCP 22m
minimal-cluster-cfg ClusterIP None 27017/TCP 7m27s
minimal-cluster-rs0 ClusterIP None 27017/TCP 7m27s
minimal-cluster-mongos ClusterIP 10.152.183.220 27017/TCP 7m27s
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- bash -il
[mongodb@percona-client /]$ mongo --host minimal-cluster-cfg -u userAdmin -p ynSf4sB92nnTv7UuwnM
Percona Server for MongoDB shell version v5.0.11-10
connecting to: mongodb://minimal-cluster-cfg:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("5f1f7db8-d75f-4658-a579-86b9bbf22471") }
Percona Server for MongoDB server version: v5.0.11-10
cfg:PRIMARY>
From the console, we can create two roles “CN=dbadmins,CN=Users,DC=percona,DC=local” and “CN=developers,CN=Users,DC=percona,DC=local” with their corresponding privileges:
use admin
db.createRole(
{
role: "CN=dbadmins,CN=Users,DC=percona,DC=local",
roles: [ "root"],
privileges: []
}
)
db.createRole(
{
role: "CN=developers,CN=Users,DC=percona,DC=local",
roles: [
"readWriteAnyDatabase"
],
privileges: []
}
)
Note that the role names defined here correspond to the Samba groups I created with samba-tool. Also, you will need to add the same roles in the replicaset endpoint if you want your LDAP users to have these privileges when connecting to the replicaset directly.
Finally, exit the mongo console by typing exit and pressing Enter. Do the same to exit the pod as well.
Applying the LDAP configuration to the replicaset, mongos, and config servers
Now, we can add the LDAP configuration to the config server. Our first test configuration is to supply the full DN when logging in so the configuration will be:
$ cat fulldn-config.yaml
security:
authorization: "enabled"
ldap:
authz:
queryTemplate: 'DC=percona,DC=local??sub?(&(objectClass=group)(member:={PROVIDED_USER}))'
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
Next, apply the configuration to the config servers:
$ kubectl create secret generic minimal-cluster-cfg-mongod --from-file=mongod.conf=fulldn-config.yaml
Additionally, if you want to log in to the replica set with LDAP, you can apply the same configuration as well:
$ kubectl create secret generic minimal-cluster-rs0-mongod --from-file=mongod.conf=fulldn-config.yaml
As for mongos, you will still need to omit the settings for authorization because this will come from the config server:
$ cat fulldn-mongos-config.yaml
security:
ldap:
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
Then apply the configuration for mongos:
$ kubectl create secret generic minimal-cluster-mongos --from-file=mongos.conf=fulldn-mongos-config.yaml
One-by-one the pods will be recreated. Wait until all of them are recreated:
$ kubectl get pods NAME READY STATUS RESTARTS AGE percona-server-mongodb-operator-547c499bd8-p8k74 1/1 Running 0 24m minimal-cluster-cfg-0 1/1 Running 0 4m27s minimal-cluster-rs0-0 1/1 Running 0 3m34s minimal-cluster-mongos-0 1/1 Running 0 65s
Now you can test authentication in one of the endpoints:
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-mongos -u "CN=Dba User01,CN=Users,DC=percona,DC=local" -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
+ exec mongo --host minimal-cluster-mongos -u 'CN=Dba User01,CN=Users,DC=percona,DC=local' -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism PLAIN --eval 'db.runCommand({connectionStatus:1})'
Percona Server for MongoDB shell version v5.0.11-10
connecting to: mongodb://minimal-cluster-mongos:27017/?authMechanism=PLAIN&authSource=%24external&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("7eca812d-ad04-4ae2-8484-3b55dee1a673") }
Percona Server for MongoDB server version: v5.0.11-10
{
"authInfo" : {
"authenticatedUsers" : [
{
"user" : "CN=Dba User01,CN=Users,DC=percona,DC=local",
"db" : "$external"
}
],
"authenticatedUserRoles" : [
{
"role" : "CN=dbadmins,CN=Users,DC=percona,DC=local",
"db" : "admin"
},
{
"role" : "root",
"db" : "admin"
}
]
}
}
pod "percona-client" deleted
As you can see above, the user,”CN=Dba User01,CN=Users,DC=percona,DC=local” has assumed the role as root. You can test other endpoints using these commands.
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-rs0 -u "CN=Dba User01,CN=Users,DC=percona,DC=local" -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-cfg -u "CN=Dba User01,CN=Users,DC=percona,DC=local" -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
Using userToDNMapping to simplify usernames
Obviously, you may not want the users to authenticate with the full DN. Perhaps, you want the users to specify just the first CN. You can use match and substitution mapping for this:
$ cat mapping1-config.yaml
security:
authorization: "enabled"
ldap:
authz:
queryTemplate: 'DC=percona,DC=local??sub?(&(objectClass=group)(member:={USER}))'
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
userToDNMapping: >-
[
{
match: "(.+)",
substitution: "CN={0},CN=users,DC=percona,DC=local"
}
]
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
$ cat mapping1-mongos-config.yaml
security:
ldap:
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
userToDNMapping: >-
[
{
match: "(.+)",
substitution: "CN={0},CN=users,DC=percona,DC=local"
}
]
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
You will need to delete the old configuration and apply the new ones:
$ kubectl delete secret minimal-cluster-cfg-mongod $ kubectl delete secret minimal-cluster-rs0-mongod $ kubectl delete secret minimal-cluster-mongos $ kubectl create secret generic minimal-cluster-cfg-mongod --from-file=mongod.conf=mapping1-config.yaml $ kubectl create secret generic minimal-cluster-rs0-mongod --from-file=mongod.conf=mapping1-config.yaml $ kubectl create secret generic minimal-cluster-mongos --from-file=mongos.conf=mapping1-mongos-config.yaml
With userToDNMapping, match and substitution you can now just specify the first CN. Once all of the pods are restarted, try logging in with a shorter username:
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-mongos -u "Dba User01" -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
Perhaps, it still seems awkward to have usernames with spaces and you would like to login based on other attributes such as sAMAccountName or mail. You can use an additional LDAP query in userToDBMapping to search for the record based on these properties. Once the record is found it will extract the user’s DN for authentication. For the example below, we will use sAMAccountName as input for the username:
$ cat mapping2-config.yaml
security:
authorization: "enabled"
ldap:
authz:
queryTemplate: 'DC=percona,DC=local??sub?(&(objectClass=group)(member:={USER}))'
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
userToDNMapping: >-
[
{
match: "(.+)",
ldapQuery: "dc=percona,dc=local??sub?(&(sAMAccountName={0})(objectClass=person))"
}
]
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
$ cat mapping2-mongos-config.yaml
security:
ldap:
servers: "192.168.0.101"
transportSecurity: none
bind:
queryUser: "CN=Search User01,CN=Users,DC=percona,DC=local"
queryPassword: "SearchPassword1"
userToDNMapping: >-
[
{
match: "(.+)",
ldapQuery: "dc=percona,dc=local??sub?(&(sAMAccountName={0})(objectClass=person))"
}
]
setParameter:
authenticationMechanisms: 'PLAIN,SCRAM-SHA-1,SCRAM-SHA-256'
Again, we will need to delete the old configuration and apply new ones:
$ kubectl delete secret minimal-cluster-cfg-mongod $ kubectl delete secret minimal-cluster-rs0-mongod $ kubectl delete secret minimal-cluster-mongos $ kubectl create secret generic minimal-cluster-cfg-mongod --from-file=mongod.conf=mapping2-config.yaml $ kubectl create secret generic minimal-cluster-rs0-mongod --from-file=mongod.conf=mapping2-config.yaml $ kubectl create secret generic minimal-cluster-mongos --from-file=mongos.conf=mapping2-mongos-config.yaml
Once the pods are recreated, we can now authenticate with regular usernames.
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-mongos -u devuser01 -p DevPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
$ kubectl run -i --rm --tty percona-client --image=percona/percona-server-mongodb:5.0.11-10 --restart=Never -- mongo --host minimal-cluster-mongos -u dbauser01 -p DbaPassword1 --authenticationDatabase '$external' --authenticationMechanism 'PLAIN' --eval "db.runCommand({connectionStatus:1})"
Summary
I hope this article gets you up to speed on setting up LDAP authentication and authorization with Percona Operator for MongoDB.
08
2021
Authenticate Percona Server for MongoDB Users via Native LDAP

Percona Server for MongoDB supports two different ways of authenticating against an LDAP service:
- operating system libraries (aka Native LDAP)
- saslauthd (aka LDAP proxy)
We’ve talked about the LDAP proxy option many times already. In this post, I am going to discuss the Native LDAP approach.
Note: for the purposes of the examples, I am considering a RHEL-based distribution.
Prerequisites
First of all, the following packages are needed at the operating system level:
yum install cyrus-sasl-devel cyrus-sasl-md5 cyrus-sasl-plain cyrus-sasl-gssapi cyrus-sasl-lib
If any of these are missing, you most likely will encounter some cryptic errors. For example, something like the following could appear in your mongod.log:
2021-07-22T14:29:14.905-0500 E QUERY [js] Error: SASL(-4): no mechanism available: No worthy mechs found :
By default, MongoDB creates a TLS connection when binding to the LDAP server. The next step is to make the certificate for the company’s internal Certificate Authority (CA) available to our MongoDB server. We can do this by placing the certificate file in /etc/openldap/certs/ directory:
cp my_CA.crt /etc/openldap/certs/
Next, we need to point our server to the CA certificate we copied, by adding the following line to /etc/openldap/ldap.conf:
tee -a /etc/openldap/ldap.conf <<EOF TLS_CACERT /etc/openldap/certs/my_CA.crt EOF
MongoDB Configuration
Once the prerequisites are fulfilled, we need to adjust our mongod.conf to authenticate against LDAP. We need:
- a read-only user that allows MongoDB to query LDAP
- an LDAP queryTemplate to authorize users based on LDAP group membership
If you don’t know what this query string will be, you should work together with the LDAP server administrators to figure it out. The following example is for an Active Directory deployment:
ldap:
servers: "ldap.example.com"
authz:
queryTemplate: "DC=example,DC=com??sub?(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={USER}))"
userToDNMapping:
'[
{
match : "(.+)",
ldapQuery: "DC=example,DC=com??sub?(userPrincipalName={0})"
}
]'
bind:
queryUser: "ldapreadonly@example"
queryPassword: "mypwd"
setParameter:
authenticationMechanisms: "PLAIN,SCRAM-SHA-1,SCRAM-SHA-256"
We can also use transformation expressions in order to avoid specifying the complete DN of the authenticating users. In the example above, the {0} is replaced with the first token of the user as specified. If you are logging in as myuser@example.com that would be the string “myuser”, so the query becomes:
ldapQuery: "DC=example,DC=com??sub?(userPrincipalName=myuser)"
This returns the following LDAP result:
"cn=myuser,dc=example,dc=com"
The queryTemplate specified in the config file is the standard AD-specific way to query a user’s groups recursively. The {USER} above is replaced with the transformed username and becomes:
queryTemplate: "DC=example,DC=com??sub?(&(objectClass=group)(member:1.2.840.113556.1.4.1941:="cn=myuser,dc=example,dc=com"))"
The authenticationMechanisms as specified allows MongoDB to authenticate both LDAP and built-in users. The PLAIN word might raise some eyebrows but remember the connection is still encrypted unless you specify the transportSecurity: none.
Creating Roles for LDAP Groups
We need to create roles in the MongoDB admin database for each of the LDAP groups we are going to be using.
For example, we can create groups for users that require read-only or read-write privileges respectively:
db.getSiblingDB("admin").createRole(
{
role: "CN=myapp_rw,CN=Users,DC=example,DC=com",
privileges: [],
roles: [
{ role: "readWrite", db: "myapp" }
]
}
)
db.getSiblingDB("admin").createRole(
{
role: "CN=myapp_ro,CN=Users,DC=example,DC=com",
privileges: [],
roles: [
{ role: "read", db: "myapp" }
]
}
)
In this case, any authenticating users that are members of the myapp_ro group in LDAP will automatically get read-only permissions against the myapp database.
Testing Access
To authenticate using LDAP, the following form can be used:
mongo --username myuser@example.com --password mypwd --authenticationMechanism=PLAIN --authenticationDatabase='$external' --host 127.0.0.1
Since we left the SCRAM-SHA options in the config file, we are still able to authenticate using MongoDB built-in users as well:
mongo --username root --password mypwd --authenticationDatabase admin --authenticationMechanism=SCRAM-SHA1 --host 127.0.0.1
Final Words
We’ve seen how to use the native method to configure LDAP integration. The main benefit of this method is that it requires fewer moving parts than the proxy-based approach.
Keep in mind that Percona Server for MongoDB offers LDAP authentication (and authorization) free of charge in all versions. These features are not available in the MongoDB Community Edition.
You might also want to check the official documentation on this topic, as there are some additional options to deal with things like LDAP-referrals, connection pool sizes, etc.
27
2018
Setup Compatible OpenLDAP Server for MongoDB and MySQL

By the end of this article, you should be able to have a Percona Server for MongoDB and Percona Server for MySQL instance able to authenticate on an OpenLDAP backend. While this is mostly aimed at testing scenarios, it can be easily extended for production by following the OpenLDAP production best practices i.e. attending to security and high availability.
The first step is to install OpenLDAP via the
slapd
package in Ubuntu.
sudo apt update sudo apt install slapd ldap-utils
During installation, it will ask you for a few things listed below:
- DNS Domain Name:
ldap.local
- Organization Name:
Percona
- Administrator password:
percona
All these values are arbitrary, you can choose whatever suits your organization—especially the password.
Once
slapd
is running, we can create our logical groups and actual users on the LDAP server. To make it simple, we use LDIF files instead of GUIs. Our first file,
perconadba.ldif
contains our
perconadba
group definition. Take note of the root name part
dc=ldap,dc=local
it is simply the broken down value of our DNS Domain Name during the installation of
slapd
.
dn: ou=perconadba,dc=ldap,dc=local objectClass: organizationalUnit ou: perconadba
We can add this definition into LDAP with the command shown below. With the
-W
option, it will prompt you for a password.
ldapadd -x -W -D "cn=admin,dc=ldap,dc=local" -f perconadba.ldif
The next step is to create our user in LDAP, this user will be looked up by both MongoDB and MySQL during authentication to verify their password. Our LDIF file (
percona.ldif
) would look like this:
dn: uid=percona,ou=perconadba,dc=ldap,dc=local
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
cn: percona
uid: percona
uidNumber: 1100
gidNumber: 100
homeDirectory: /home/percona
loginShell: /bin/bash
gecos: percona
userPassword: {crypt}x
shadowLastChange: -1
shadowMax: -1
shadowWarning: -1
The
-1
values for the
shadow*
fields are important, we set them to negative to mean the password shadow does not expire. If these are set to zero (0), then MySQL will not be able to authenticate since PAM will complain that the password has expired and needs to be changed.
We can then add this user into LDAP, again the command below will ask for the admin password we entered during slapd’s installation.
ldapadd -x -W -D "cn=admin,dc=ldap,dc=local" -f percona.ldif
To verify, we can search for the user we just entered using the command below. Notice we used the -w parameter to specify the admin password inline.
ldapsearch -x -D 'cn=admin,dc=ldap,dc=local' -w percona \ -b 'ou=perconadba,dc=ldap,dc=local' '(uid=percona)'
As last step on setting up our LDAP user properly is to give it a valid password. The -s parameter below is the actual password we will set for this user.
ldappasswd -s percona -D "cn=admin,dc=ldap,dc=local" -w percona \ -x "uid=percona,ou=perconadba,dc=ldap,dc=local"
At this point you should have a generic LDAP server that should work for both MongoDB and MySQL.
PAM Configuration for MySQL
To make this work for a MySQL and support PAM authentication, take note of the following configuration files. Instructions on setting up PAM for MySQL is aplenty on this blog I just need to specify Ubuntu Bionic specific configuration files to make it work.
/etc/nslcd.conf
The only important difference with this configuration—compared to Jaime’s post for example—is the values for
filter
. If you are using Windows Active Directory, the map values are also important (posixAccount objectClass has been deprecated on recent release of Windows Active Directory).
uid nslcd gid nslcd uri ldap:///localhost base ou=perconadba,dc=ldap,dc=local filter passwd (&(objectClass=account)(objectClass=posixAccount)) filter group (&(objectClass=shadowAccount)(objectClass=account)) map passwd uid uid map passwd uidNumber uidNumber map passwd gidNumber gidNumber map passwd homeDirectory "/home/$uid" map passwd gecos uid map passwd loginShell "/bin/bash" map group gidNumber gidNumber binddn cn=admin,dc=ldap,dc=local bindpw percona tls_cacertfile /etc/ssl/certs/ca-certificates.crt
/etc/nsswitch.conf
Also for nsswitch.conf, make sure that passwd, group and shadow does LDAP lookups.
... passwd: compat systemd ldap group: compat systemd ldap shadow: compat systemd ldap gshadow: files ldap ...
SASL for MongoDB
Adamo’s excellent post on MongoDB LDAP Authentication has all the details on configuring MongoDB itself. To complement that, if you use this LDAP test setup, you need the take note of the following configuration files with specific differences.
/etc/mongod.conf
In the
mongod.conf
configuration file, I explicitly added the saslauthd socket path.
security: authorization: enabled setParameter: saslauthdPath: /var/run/saslauthd/mux authenticationMechanisms: PLAIN,SCRAM-SHA-1
/etc/saslauthd.conf
For the saslauthd daemon configuration, the configuration has no actual difference – just take note I used differing values based on the LDAP setup above. Specifically, the
ldap_filter
and
ldap_search_base
are key options here which are concatenated during an LDAP search to come up with the
percona
user’s account information.
ldap_servers: ldap://localhost:389/ ldap_search_base: ou=perconadba,dc=ldap,dc=local ldap_filter: (uid=%u) # Optional: specify a user to perform ldap queries ldap_bind_dn: CN=admin,DC=ldap,DC=local # Optional: specify ldap user’s passwordi ldap_password: percona
Enterprise quality features should not be complex and expensive. Tell us about your experience with our software and external authentication in the comments below!
21
2017
How to Setup and Troubleshoot Percona PAM with LDAP for External Authentication

In this blog, we’ll look at how to setup and troubleshoot the Percona PAM authentication plugin.
We occasionally get requests from our support clients on how to get Percona Server for MySQL to authenticate with an external authentication service via LDAP or Active Directory. However, we normally do not have access to client’s infrastructure to help troubleshoot these cases. To help them effectively, we need to setup a testbed to reproduce their issues and guide them on how to get authentication to work. Fortunately, we only need to install Samba to provide an external authentication service for both LDAP and AD.
In this article, I will show you how to (a) compile and install Samba, (b) create a domain environment with Samba, (c) add users and groups to this domain and (d) get Percona Server to use these accounts for authentication via LDAP. In my follow-up article, I will discuss how to get MySQL to authenticate credentials with Active Directory.
My testbed environment consists of two machines
Samba PDC
OS: CentOS 7
IP Address: 172.16.0.10
Hostname: samba-10.example.com
Domain name: EXAMPLE.COM
DNS: 8.8.8.8(Google DNS), 8.8.4.4(Google DNS), 172.16.0.10(Samba)
Firewall: none
Percona Server 5.7 with LDAP authentication
OS: CentOS 7
IP Address: 172.16.0.20
Hostname: ps-ldap-20.example.com
and have several users and groups:
Domain Groups and Users
Support: jericho, jervin and vishal
DBA: sidd, paul and arunjith
Search: ldap
Compile and Install Samba
We will install an NTP client on the Samba PDC/samba-10.example.com machine because time synchronization is a requirement for domain authentication. We will also compile and install Samba from source because the Samba implementation in the official repository doesn’t include the Active Directory Domain Controller role. Hence, samba-tool is not included in the official repository. For our testbed, we need this tool because it makes it easier to provision a domain and manage users and groups. So, for CentOS 7, you can either build from source or use a trusted 3rd party build of Samba (as discussed in Samba’s wiki).
For more information, please read Setting up Samba as an Active Directory Domain Controller as well.
- Install, configure, and run the NTP client. Ensure that this client service runs when the server boots up:
[root@samba-10 ~]# yum -y install ntp * * * Installed: ntp.x86_64 0:4.2.6p5-25.el7.centos.1 Dependency Installed: autogen-libopts.x86_64 0:5.18-5.el7 ntpdate.x86_64 0:4.2.6p5-25.el7.centos.1 [root@samba-10 ~]# ntpdate 0.centos.pool.ntp.org 7 Apr 06:06:07 ntpdate[9788]: step time server 202.90.132.242 offset 0.807640 sec [root@samba-10 ~]# systemctl enable ntpd.service Created symlink from /etc/systemd/system/multi-user.target.wants/ntpd.service to /usr/lib/systemd/system/ntpd.service. [root@samba-10 ~]# systemctl start ntpd.service
- Install compilers and library dependencies for compiling Samba:
[root@samba-10 ~]# yum -y install gcc perl python-devel gnutls-devel libacl-devel openldap-devel * * * Installed: gcc.x86_64 0:4.8.5-11.el7 gnutls-devel.x86_64 0:3.3.24-1.el7 libacl-devel.x86_64 0:2.2.51-12.el7 openldap-devel.x86_64 0:2.4.40-13.el7 perl.x86_64 4:5.16.3-291.el7 python-devel.x86_64 0:2.7.5-48.el7 Dependency Installed: cpp.x86_64 0:4.8.5-11.el7 cyrus-sasl.x86_64 0:2.1.26-20.el7_2 cyrus-sasl-devel.x86_64 0:2.1.26-20.el7_2 glibc-devel.x86_64 0:2.17-157.el7_3.1 glibc-headers.x86_64 0:2.17-157.el7_3.1 gmp-devel.x86_64 1:6.0.0-12.el7_1 gnutls-c++.x86_64 0:3.3.24-1.el7 gnutls-dane.x86_64 0:3.3.24-1.el7 kernel-headers.x86_64 0:3.10.0-514.10.2.el7 ldns.x86_64 0:1.6.16-10.el7 libattr-devel.x86_64 0:2.4.46-12.el7 libevent.x86_64 0:2.0.21-4.el7 libmpc.x86_64 0:1.0.1-3.el7 libtasn1-devel.x86_64 0:3.8-3.el7 mpfr.x86_64 0:3.1.1-4.el7 nettle-devel.x86_64 0:2.7.1-8.el7 p11-kit-devel.x86_64 0:0.20.7-3.el7 perl-Carp.noarch 0:1.26-244.el7 perl-Encode.x86_64 0:2.51-7.el7 perl-Exporter.noarch 0:5.68-3.el7 perl-File-Path.noarch 0:2.09-2.el7 perl-File-Temp.noarch 0:0.23.01-3.el7 perl-Filter.x86_64 0:1.49-3.el7 perl-Getopt-Long.noarch 0:2.40-2.el7 perl-HTTP-Tiny.noarch 0:0.033-3.el7 perl-PathTools.x86_64 0:3.40-5.el7 perl-Pod-Escapes.noarch 1:1.04-291.el7 perl-Pod-Perldoc.noarch 0:3.20-4.el7 perl-Pod-Simple.noarch 1:3.28-4.el7 perl-Pod-Usage.noarch 0:1.63-3.el7 perl-Scalar-List-Utils.x86_64 0:1.27-248.el7 perl-Socket.x86_64 0:2.010-4.el7 perl-Storable.x86_64 0:2.45-3.el7 perl-Text-ParseWords.noarch 0:3.29-4.el7 perl-Time-HiRes.x86_64 4:1.9725-3.el7 perl-Time-Local.noarch 0:1.2300-2.el7 perl-constant.noarch 0:1.27-2.el7 perl-libs.x86_64 4:5.16.3-291.el7 perl-macros.x86_64 4:5.16.3-291.el7 perl-parent.noarch 1:0.225-244.el7 perl-podlators.noarch 0:2.5.1-3.el7 perl-threads.x86_64 0:1.87-4.el7 perl-threads-shared.x86_64 0:1.43-6.el7 unbound-libs.x86_64 0:1.4.20-28.el7 zlib-devel.x86_64 0:1.2.7-17.el7 Complete!
- Download, compile and install Samba:
[root@samba-10 ~]# yum -y install wget * * * [root@samba-10 ~]# wget https://www.samba.org/samba/ftp/samba-latest.tar.gz * * * 2017-04-07 06:16:59 (337 KB/s) - 'samba-latest.tar.gz' saved [21097045/21097045] [root@samba-10 ~]# tar xzf samba-latest.tar.gz [root@samba-10 ~]# cd samba-4.6.2/ [root@samba-10 samba-4.6.2]# ./configure --prefix=/opt/samba Checking for program gcc or cc : /usr/bin/gcc Checking for program cpp : /usr/bin/cpp Checking for program ar : /usr/bin/ar Checking for program ranlib : /usr/bin/ranlib * * * Checking compiler for PIE support : yes Checking compiler for full RELRO support : yes Checking if toolchain accepts -fstack-protector : yes 'configure' finished successfully (39.119s) [root@samba-10 samba-4.6.2]# make WAF_MAKE=1 python ./buildtools/bin/waf build Waf: Entering directory `/root/samba-4.6.2/bin' symlink: tevent.py -> python/tevent.py * * * [3773/3775] Linking default/source3/modules/libvfs_module_acl_xattr.so [3774/3775] Linking default/source3/modules/libvfs_module_shadow_copy.so [3775/3775] Linking default/source3/modules/libvfs_module_dirsort.so Waf: Leaving directory `/root/samba-4.6.2/bin' 'build' finished successfully (6m58.144s) [root@samba-10 samba-4.6.2]# make install WAF_MAKE=1 python ./buildtools/bin/waf install Waf: Entering directory `/root/samba-4.6.2/bin' * creating /opt/samba/etc * creating /opt/samba/private * * * * installing bin/default/source3/nmbd/nmbd.inst as /opt/samba/sbin/nmbd * installing bin/default/file_server/libservice_module_s3fs.inst.so as /opt/samba/lib/service/s3fs.so Waf: Leaving directory `/root/samba-4.6.2/bin' 'install' finished successfully (1m44.377s)
Please take note that when I downloaded Samba, the latest version was 4.6.2. If you have a problem with compiling the latest version of Samba, try using version 4.6.2.
- Include executable path of Samba to the PATH variable so we can call samba binaries without specifying its absolute path:
[root@samba-10 samba-4.6.2]# echo "PATH=/opt/samba/sbin:/opt/samba/bin:/usr/sbin:/usr/bin" >> /etc/environment [root@samba-10 samba-4.6.2]# PATH=/opt/samba/sbin:/opt/samba/bin:/usr/sbin:/usr/bin [root@samba-10 samba-4.6.2]# which samba-tool /opt/samba/bin/samba-tool
- Setup systemd script for Samba and ensure that this service auto starts on server boot
[root@samba-10 samba-4.6.2]# echo "[Unit] Description=Samba PDC After=syslog.target network.target [Service] Type=forking PIDFile=//opt/samba/var/run/samba.pid ExecStart=/opt/samba/sbin/samba -D ExecReload=/usr/bin/kill -HUP $MAINPID ExecStop=/usr/bin/kill $MAINPID [Install] WantedBy=multi-user.target" > /etc/systemd/system/samba.service [root@samba-10 samba-4.6.2]# systemctl enable samba.service Created symlink from /etc/systemd/system/multi-user.target.wants/samba.service to /etc/systemd/system/samba.service.
- Remove existing /etc/krb5.conf, because the existing configuration prevents us from provisioning a new domain.
[root@samba-10 samba-4.6.2]# rm -f /etc/krb5.conf [root@samba-10 samba-4.6.2]# cd [root@samba-10 ~]#
- Done.
Create a domain environment with Samba
- To setup a domain, all we need to do is to run “samba-tool domain provision” and pass the following details:
Realm: EXAMPLE.COM
Domain: EXAMPLE
Server Role: dc(domain controller)
DNS backend: SAMBA_INTERNAL
DNS forwarder IP address: 8.8.8.8
You will also need to supply the Administrator password. This account is used to join a workstation or server to a domain:
[root@samba-10 ~]# samba-tool domain provision Realm [EXAMPLE.ORG]: EXAMPLE.COM Domain [EXAMPLE]: EXAMPLE Server Role (dc, member, standalone) [dc]: dc DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE) [SAMBA_INTERNAL]: SAMBA_INTERNAL DNS forwarder IP address (write 'none' to disable forwarding) [8.8.8.8]: 8.8.8.8 Administrator password: Retype password: Looking up IPv4 addresses Looking up IPv6 addresses No IPv6 address will be assigned Setting up secrets.ldb Setting up the registry Setting up the privileges database Setting up idmap db Setting up SAM db Setting up sam.ldb partitions and settings Setting up sam.ldb rootDSE Pre-loading the Samba 4 and AD schema Adding DomainDN: DC=example,DC=com Adding configuration container Setting up sam.ldb schema Setting up sam.ldb configuration data Setting up display specifiers Modifying display specifiers Adding users container Modifying users container Adding computers container Modifying computers container Setting up sam.ldb data Setting up well known security principals Setting up sam.ldb users and groups Setting up self join Adding DNS accounts Creating CN=MicrosoftDNS,CN=System,DC=example,DC=com Creating DomainDnsZones and ForestDnsZones partitions Populating DomainDnsZones and ForestDnsZones partitions Setting up sam.ldb rootDSE marking as synchronized Fixing provision GUIDs A Kerberos configuration suitable for Samba AD has been generated at /opt/samba/private/krb5.conf Once the above files are installed, your Samba4 server will be ready to use Server Role: active directory domain controller Hostname: samba-10 NetBIOS Domain: EXAMPLE DNS Domain: example.com DOMAIN SID: S-1-5-21-1337223342-1741564684-602463608
Please take note that if you get the error below, it’s likely due to not removing the existing /etc/krb5.conf before using samba-tool:
ERROR(ldb): uncaught exception - operations error at ../source4/dsdb/samdb/ldb_modules/password_hash.c:2820
File "/opt/samba/lib64/python2.7/site-packages/samba/netcmd/__init__.py", line 176, in _run
return self.run(*args, **kwargs)
File "/opt/samba/lib64/python2.7/site-packages/samba/netcmd/domain.py", line 471, in run
nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
File "/opt/samba/lib64/python2.7/site-packages/samba/provision/__init__.py", line 2175, in provision
skip_sysvolacl=skip_sysvolacl)
File "/opt/samba/lib64/python2.7/site-packages/samba/provision/__init__.py", line 1787, in provision_fill
next_rid=next_rid, dc_rid=dc_rid)
File "/opt/samba/lib64/python2.7/site-packages/samba/provision/__init__.py", line 1447, in fill_samdb
"KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
File "/opt/samba/lib64/python2.7/site-packages/samba/provision/common.py", line 55, in setup_add_ldif
ldb.add_ldif(data, controls)
File "/opt/samba/lib64/python2.7/site-packages/samba/__init__.py", line 225, in add_ldif
self.add(msg, controls)
You could also get an error if you entered a simple password for the Administrator account.
- Create a symlink of the generated krb5.conf in /etc. This configuration is used authenticate machines, accounts and services:
[root@samba-10 ~]# ln -s /opt/samba/private/krb5.conf /etc
- Start the Samba service:
[root@samba-10 ~]# systemctl start samba.service
- Check network ports to see if Samba is running:
[root@samba-10 ~]# yum -y install net-tools * * * [root@samba-10 ~]# netstat -tapn Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:464 0.0.0.0:* LISTEN 13296/samba tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 13302/samba tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 875/sshd tcp 0 0 0.0.0.0:88 0.0.0.0:* LISTEN 13296/samba tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 1327/master tcp 0 0 0.0.0.0:636 0.0.0.0:* LISTEN 13294/samba tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN 13307/smbd tcp 0 0 0.0.0.0:1024 0.0.0.0:* LISTEN 13291/samba tcp 0 0 0.0.0.0:1025 0.0.0.0:* LISTEN 13291/samba tcp 0 0 0.0.0.0:3268 0.0.0.0:* LISTEN 13294/samba tcp 0 0 0.0.0.0:3269 0.0.0.0:* LISTEN 13294/samba tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN 13294/samba tcp 0 0 0.0.0.0:135 0.0.0.0:* LISTEN 13291/samba tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN 13307/smbd
- Done.
Add users and groups to this domain
Now that Samba is running we can add users and groups, and assign users to groups with samba-tool.
- Add groups by running “samba-tool group add group_name”:
[root@samba-10 ~]# samba-tool group add support Added group support [root@samba-10 ~]# samba-tool group add dba Added group dba [root@samba-10 ~]# samba-tool group add search Added group search
- Add users by running “samba-tool user create username”:
[root@samba-10 ~]# samba-tool user create jericho New Password: Retype Password: User 'jericho' created successfully [root@samba-10 ~]# samba-tool user create jervin New Password: Retype Password: User 'jervin' created successfully [root@samba-10 ~]# samba-tool user create vishal New Password: Retype Password: User 'vishal' created successfully [root@samba-10 ~]# samba-tool user create sidd New Password: Retype Password: User 'sidd' created successfully [root@samba-10 ~]# samba-tool user create paul New Password: Retype Password: User 'paul' created successfully [root@samba-10 ~]# samba-tool user create arunjith New Password: Retype Password: User 'arunjith' created successfully [root@samba-10 ~]# samba-tool user create ldap New Password: Retype Password: User 'ldap' created successfully
- Add users to their corresponding groups with “samba-tool group addmembers group_name user,user2,usern”:
[root@samba-10 ~]# samba-tool group addmembers support jericho,jervin,vishal Added members to group support [root@samba-10 ~]# samba-tool group addmembers dba sidd,paul,arunjith Added members to group dba [root@samba-10 ~]# samba-tool group addmembers search ldap Added members to group search
- Verify that users, groups and memberships exist with commands “samba-tool user list”, “samba-tool group list” and “samba-tool group listmembers group_name”:
[root@samba-10 ~]# samba-tool user list Administrator arunjith jericho jervin krbtgt vishal Guest ldap paul sidd [root@samba-10 ~]# samba-tool group list Allowed RODC Password Replication Group Enterprise Read-Only Domain Controllers Denied RODC Password Replication Group Pre-Windows 2000 Compatible Access Windows Authorization Access Group Certificate Service DCOM Access Network Configuration Operators Terminal Server License Servers Incoming Forest Trust Builders Read-Only Domain Controllers Group Policy Creator Owners Performance Monitor Users Cryptographic Operators Distributed COM Users Performance Log Users Remote Desktop Users Account Operators Event Log Readers RAS and IAS Servers Backup Operators Domain Controllers Server Operators Enterprise Admins Print Operators Administrators Domain Computers Cert Publishers DnsUpdateProxy Domain Admins Domain Guests Schema Admins Domain Users Replicator IIS_IUSRS DnsAdmins Guests Users support search dba [root@samba-10 ~]# samba-tool group listmembers support jervin jericho vishal [root@samba-10 ~]# samba-tool group listmembers dba arunjith sidd paul [root@samba-10 ~]# samba-tool group listmembers search ldap
For more information on using samba-tool, just run
samba-tool --help
.
- Done.
How to get Percona Server to use these accounts for authentication via LDAP
We will be using the machine ps-ldap-20.example.com to offer MySQL service with LDAP authentication via Percona PAM. If you’re not familiar with Percona PAM, please have a look at this before moving forward.
At this point, our Samba service is running with users, groups and memberships added. We can now query Samba via LDAP ports 389 and 636. We will configure the server to do LDAP lookups when searching for users and groups. This is necessary because we use the name service to validate group membership. We will then install Percona Server for MySQL and configure our PAM plugin to use
nss-pam-ldapd
to authenticate to LDAP. Finally, we will test LDAP authentication on Percona Server for MySQL using a regular user and proxy user.
- Install
nss-pam-ldapd
and
nscd
. We will use these packages to query LDAP server from our server:
[root@ps-20 ~]# yum -y install nss-pam-ldapd
- Configure
nss-pam-ldapd
by incorporating our Samba’s LDAP settings:
[root@ps-20 ~]# echo "uid nslcd gid ldap pagesize 1000 referrals off idle_timelimit 800 filter passwd (&(objectClass=user)(objectClass=person)(!(objectClass=computer))) map passwd uid sAMAccountName map passwd uidNumber objectSid:S-1-5-21-1337223342-1741564684-602463608 map passwd gidNumber objectSid:S-1-5-21-1337223342-1741564684-602463608 map passwd homeDirectory "/home/$cn" map passwd gecos displayName map passwd loginShell "/bin/bash" filter group (|(objectClass=group)(objectClass=person)) map group gidNumber objectSid:S-1-5-21-1337223342-1741564684-602463608 uri ldaps://172.16.0.10 base dc=example,dc=com tls_reqcert never binddn cn=ldap,cn=Users,dc=example,dc=com bindpw MyLdapPasswordDontCopyIt2017" > /etc/nslcd.conf
As you can see above, this config contains LDAP settings, mapping custom LDAP attributes, and LDAP credentials. The value of objectSid was taken from “DOMAIN SID” that was generated when I created a new domain. So, be sure to use the value of “DOMAIN SID” generated on your end. Otherwise, your LDAP queries will not match any record. However, if you’re authenticating from an existing Windows AD server, you can obtain the value of “DOMAIN SID” by running “Get-ADDomain”. Also, you can take a look at this link to get to know more about other configurations for nslcd.conf.
- Add LDAP lookup to nsswitch service by editing /etc/nsswitch.conf:
Find:
passwd: files sss
shadow: files sss
group: files sss
Replace with:
passwd: files sss ldap
shadow: files sss ldap
group: files sss ldap
- Run nslcd in debug mode:
[root@ps-20 ~]# nslcd -d
nslcd: DEBUG: add_uri(ldaps://172.16.0.10)
nslcd: DEBUG: ldap_set_option(LDAP_OPT_X_TLS_REQUIRE_CERT,0)
nslcd: version 0.8.13 starting
nslcd: DEBUG: unlink() of /var/run/nslcd/socket failed (ignored): No such file or directory
nslcd: DEBUG: initgroups("nslcd",55) done
nslcd: DEBUG: setgid(55) done
nslcd: DEBUG: setuid(65) done
nslcd: accepting connections
- Test if LDAP lookups work by running “id ” and “getent passwd” on another terminal:
[root@ps-20 ~]# id jervin uid=1107(jervin) gid=1107(jervin) groups=1107(jervin),1103(support) [root@ps-20 ~]# id paul uid=1110(paul) gid=1110(paul) groups=1110(paul),1104(dba) [root@ps-20 ~]# getent passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin systemd-bus-proxy:x:999:997:systemd Bus Proxy:/:/sbin/nologin systemd-network:x:998:996:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin polkitd:x:997:995:User for polkitd:/:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin user:x:1000:1000:user:/home/user:/bin/bash mysql:x:27:27:Percona Server:/var/lib/mysql:/bin/false nscd:x:28:28:NSCD Daemon:/:/sbin/nologin nslcd:x:65:55:LDAP Client User:/:/sbin/nologin Administrator:*:500:500::/home/Administrator:/bin/bash arunjith:*:1111:1111::/home/arunjith:/bin/bash jericho:*:1106:1106::/home/jericho:/bin/bash jervin:*:1107:1107::/home/jervin:/bin/bash krbtgt:*:502:502::/home/krbtgt:/bin/bash vishal:*:1108:1108::/home/vishal:/bin/bash Guest:*:501:501::/home/Guest:/bin/bash ldap:*:1112:1112::/home/ldap:/bin/bash paul:*:1110:1110::/home/paul:/bin/bash sidd:*:1109:1109::/home/sidd:/bin/bash
If you take a look at the nslcd terminal again, you will see that it’s trying to resolve the user and group identification with LDAP searches:
* * *
nslcd: [7b23c6] <passwd=1107> DEBUG: ldap_simple_bind_s("cn=ldap,cn=Users,dc=example,dc=com","***") (uri="ldaps://172.16.0.10")
nslcd: [7b23c6] <passwd=1107> DEBUG: ldap_result(): CN=jervin,CN=Users,DC=example,DC=com
nslcd: [7b23c6] <passwd=1107> DEBUG: ldap_result(): end of results (1 total)
nslcd: [3c9869] DEBUG: connection from pid=10468 uid=0 gid=0
nslcd: [3c9869] <passwd=1107> DEBUG: myldap_search(base="dc=example,dc=com", filter="(&(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))(objectSid=?1?5?0?0?0?0?0?515?0?0?0ae68b44f?c2bce6778dde8...
* * *
nslcd: [5558ec] <passwd="paul"> DEBUG: myldap_search(base="dc=example,dc=com", filter="(&(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))(sAMAccountName=paul))")
nslcd: [5558ec] <passwd="paul"> DEBUG: ldap_result(): CN=paul,CN=Users,DC=example,DC=com
nslcd: [5558ec] <passwd="paul"> DEBUG: ldap_result(): end of results (1 total)
* * *
nslcd: [e2a9e3] <passwd(all)> DEBUG: myldap_search(base="dc=example,dc=com", filter="(&(objectClass=user)(objectClass=person)(!(objectClass=computer)))")
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=Administrator,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=arunjith,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=jericho,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=jervin,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=krbtgt,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=vishal,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=Guest,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=ldap,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=paul,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): CN=sidd,CN=Users,DC=example,DC=com
nslcd: [e2a9e3] <passwd(all)> DEBUG: ldap_result(): end of results (10 total)
Now that we know nslcd is working, shut it down by running “Ctrl-C”.
- Run nslcd normally and make sure it starts up on boot:
[root@ps-20 ~]# systemctl start nslcd.service [root@ps-20 ~]# systemctl enable nslcd.service Created symlink from /etc/systemd/system/multi-user.target.wants/nslcd.service to /usr/lib/systemd/system/nslcd.service.
- Install and run Percona Server for MySQL 5.7 and make sure it runs when the server boots up:
[root@ps-20 ~]# rpm -Uvh https://www.percona.com/redir/downloads/percona-release/redhat/percona-release-0.1-4.noarch.rpm Retrieving https://www.percona.com/redir/downloads/percona-release/redhat/percona-release-0.1-4.noarch.rpm Preparing... ################################# [100%] Updating / installing... 1:percona-release-0.1-4 ################################# [100%] [root@ps-20 ~]# yum -y install Percona-Server-server-57 * * * [root@ps-20 ~]# mysqld --initialize-insecure --user=mysql [root@ps-20 ~]# systemctl start mysqld.service [root@ps-20 ~]# systemctl enable mysqld.service Created symlink from /etc/systemd/system/mysql.service to /usr/lib/systemd/system/mysqld.service. Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld.service to /usr/lib/systemd/system/mysqld.service.
- Login to MySQL and change the root password:
[root@ps-20 ~]# mysql -uroot
mysql> SET PASSWORD=PASSWORD('MyNewAndImprovedPassword');
- Install the Percona PAM plugin:
mysql> delete from mysql.user where user=''; Query OK, 0 rows affected (0.00 sec) mysql> INSTALL PLUGIN auth_pam SONAME 'auth_pam.so'; Query OK, 0 rows affected (0.01 sec) mysql> INSTALL PLUGIN auth_pam_compat SONAME 'auth_pam_compat.so'; Query OK, 0 rows affected (0.00 sec)
- Configure Percona PAM to authenticate to LDAP by creating /etc/pam.d/mysqld with this content:
auth required pam_ldap.so account required pam_ldap.so
- Create a MySQL user that will authenticate via auth_pam:
mysql> CREATE USER jervin@'%' IDENTIFIED WITH auth_pam; Query OK, 0 rows affected (0.00 sec) mysql> GRANT ALL PRIVILEGES ON support.* TO jervin@'%'; Query OK, 0 rows affected (0.00 sec) mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec)
- Login as this user and check grants:
[root@ps-20 ~]# mysql -u jervin Password: Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 22 Server version: 5.7.17-13 Percona Server (GPL), Release 13, Revision fd33d43 Copyright (c) 2009-2016 Percona LLC and/or its affiliates Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. mysql> SHOW GRANTS; +-----------------------------------------------------+ | Grants for jervin@% | +-----------------------------------------------------+ | GRANT USAGE ON *.* TO 'jervin'@'%' | | GRANT ALL PRIVILEGES ON `support`.* TO 'jervin'@'%' | +-----------------------------------------------------+ 2 rows in set (0.00 sec)
It works! However, if you have 100 support users who have the same MySQL privileges, creating 100 MySQL users is tedious and can be difficult to maintain. If belonging to a group has certain MySQL privileges, setup proxy users instead to map a user’s privilege to its defined group. We will implement this for both dba and support users in the next step.
For now, delete the user we just created:
mysql> DROP USER jervin@'%'; Query OK, 0 rows affected (0.00 sec)
- Create proxy user and proxied accounts:
mysql> CREATE USER ''@'' IDENTIFIED WITH auth_pam as 'mysqld,support=support_users,dba=dba_users'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE USER support_users@'%' IDENTIFIED BY 'some_password'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE USER dba_users@'%' IDENTIFIED BY 'some_password'; Query OK, 0 rows affected (0.00 sec) mysql> GRANT ALL PRIVILEGES ON support.* TO support_users@'%'; Query OK, 0 rows affected (0.00 sec) mysql> GRANT ALL PRIVILEGES ON *.* TO dba_users@'%'; Query OK, 0 rows affected (0.00 sec) mysql> GRANT PROXY ON support_users@'%' TO ''@''; Query OK, 0 rows affected (0.00 sec) mysql> GRANT PROXY ON dba_users@'%' TO ''@''; Query OK, 0 rows affected (0.00 sec) mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec)
To know more about setting up proxy users, see this article written by Stephane.
- Let’s try logging in as “jericho” and “paul” and see if they inherit the privileges of their group.
[root@ps-20 ~]# mysql -ujericho -p Enter password: Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 25 Server version: 5.7.17-13 Percona Server (GPL), Release 13, Revision fd33d43 Copyright (c) 2009-2016 Percona LLC and/or its affiliates Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. mysql> SELECT user(), current_user(), @@proxy_user; +-------------------+-----------------+--------------+ | user() | current_user() | @@proxy_user | +-------------------+-----------------+--------------+ | jericho@localhost | support_users@% | ''@'' | +-------------------+-----------------+--------------+ 1 row in set (0.00 sec) mysql> SHOW GRANTS; +------------------------------------------------------------+ | Grants for support_users@% | +------------------------------------------------------------+ | GRANT USAGE ON *.* TO 'support_users'@'%' | | GRANT ALL PRIVILEGES ON `support`.* TO 'support_users'@'%' | +------------------------------------------------------------+ 2 rows in set (0.00 sec) mysql> quit Bye [root@ps-20 ~]# mysql -upaul -p Enter password: Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 27 Server version: 5.7.17-13 Percona Server (GPL), Release 13, Revision fd33d43 Copyright (c) 2009-2016 Percona LLC and/or its affiliates Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement. mysql> SELECT user(), current_user(), @@proxy_user; +----------------+----------------+--------------+ | user() | current_user() | @@proxy_user | +----------------+----------------+--------------+ | paul@localhost | dba_users@% | ''@'' | +----------------+----------------+--------------+ 1 row in set (0.00 sec) mysql> SHOW GRANTS; +------------------------------------------------+ | Grants for dba_users@% | +------------------------------------------------+ | GRANT ALL PRIVILEGES ON *.* TO 'dba_users'@'%' | +------------------------------------------------+ 1 row in set (0.00 sec)
As you can see, they did inherit the MySQL privileges of their groups.
- Done.
Conclusion
To be honest, setting up Percona PAM with LDAP can be challenging if you add this functionality with existing infrastructure. But hopefully, by setting this up in a lab environment from scratch, and doing some tests, you’ll be confident enough to incorporate this feature in production environments.