linux_course_doc/modules/qualifying/learning_centralized_accoun...

23 KiB

Centralized account management

Up until now most of the services and servers we installed did not need a lot of user accounts to be shared a crossed devices. Once we venture into NFS it will become quite essential to have some sort of shared database to manage users and permissions. We'll dive into this from the bottom up so let's create a problem first!

The problem

To create the problem you'll need at least three virtual machines running Debian bullseye. All three machines should have the root password unset during install and should have the same username for the first user created. They can be as minimal as you want but I would advise to install one with the tools you like, such as vim-nox, htop, zsh etc and make clones from that one. We don't need a graphical environment for this exercise. Put the hostnames as follows:

  • nas for the NFS server
  • client1 for the first client
  • client2 for the second client

The server

Let's install an NFS server on the VM. This is very easy to do on Debian. The command below is enough have an NFS server up and running.

➜  ~ sudo apt install nfs-kernel-server
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
nfs-kernel-server is already the newest version (1:1.3.4-6).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜  ~ 

But we need to define which folders are shared on the network. I created a folder /home/shared for all shared files and folders and chown it to my main user.

➜  ~ ls -l /home 
total 8
drwxr-xr-x 2 waldek waldek 4096 Sep 15 16:21 shared
drwxr-xr-x 4 waldek waldek 4096 Sep 15 16:33 waldek
➜  ~ touch /home/shared/hello
➜  ~ cat /etc/exports                     
# /etc/exports: the access control list for filesystems which may be exported
#		to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/home/shared       192.168.122.0/24(no_root_squash,rw,sync,no_subtree_check)
➜  ~ sudo exportfs -ar                    
➜  ~ 

The first client

On the client we need to mount the network share. This is done with mount -t nfs and a source and destination. Let's observe the out of the box behavior.

➜  ~ mkdir -p media/nfs                                      
➜  ~ sudo mount -t nfs 192.168.122.100:/home/shared media/nfs
mount: /home/waldek/media/nfs: bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program.
➜  ~ 

The command is correct but we're missing the helper program to mount NFS shares. This can be installed with the nfs-common package.

➜  ~ sudo apt install nfs-common                             
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
nfs-common is already the newest version (1:1.3.4-6).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜  ~ sudo mount -t nfs 192.168.122.100:/home/shared media/nfs
➜  ~ ls -l media/nfs 
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 15 16:21 hello

Wonderful! We can now copy files to this network share from all connected clients. Now do the same for the second client. It should all work as expected, nothing weird here. But what happens when we add more users?

Creating the conflict

On the first client we add a user bob. Once bob is added, let's have him try to write a file to the network share. It's pretty obvious that bob can't just write to the network share because he doesn't have the right permissions. We can change that by being super loose and set the directory to 777!

➜  ~ sudo adduser bob
adduser: The user `bob' already exists.
➜  ~ su bob
Password: 
bob@client1:/home/waldek/media/nfs$ touch hello.bob
touch: cannot touch 'hello.bob': Permission denied
bob@client1:/home/waldek/media/nfs$ exit
exit
➜  ~ chmod 777 media/nfs 
➜  ~ su bob
Password: 
bob@client1:/home/waldek$ touch media/nfs/hello.bob
bob@client1:/home/waldek$ ls -l media/nfs/
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 bob    bob    0 Sep 20 19:17 hello.bob
bob@client1:/home/waldek$ 

But what happens on the nfs server? There is no user named bob there!

➜  ~ ls -l /home/shared 
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1   1001   1001 0 Sep 20 19:17 hello.bob
➜  ~ 

You can already see hint of the problem to come. The unknown user is references by a user id number. Let's right our wrong and add bob to the nfs server.

➜  ~ hostname
nas
➜  ~ sudo adduser bob
adduser: The user `bob' already exists.
➜  ~ ls -l /home/shared
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 bob    bob    0 Sep 20 19:17 hello.bob
➜  ~ 

Now let's add alice as well to the nfs server, and to the first client, and add a file owned by her. First the nfs server, next the client.

➜  ~ hostname && ls -l /home/shared                 
nas
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 alice  alice  0 Sep 20 20:36 hello.alice
-rw-r--r-- 1 bob    bob    0 Sep 20 19:17 hello.bob
➜  ~ 
➜  ~ hostname && ls -l media/nfs
client1
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 alice  alice  0 Sep 20 20:36 hello.alice
-rw-r--r-- 1 bob    bob    0 Sep 20 19:17 hello.bob
➜  ~ 

As you can see it kind of works out but is very cumbersome and prone to errors. Let's create the error now on purpose. On the second client we add both bob and alice but the wrong way around meaning first we add alice, next we add bob. The result is a pretty big misunderstanding!

➜  ~ hostname
client2
➜  ~ tail -n 2 /etc/passwd
alice:x:1001:1001:,,,:/home/alice:/bin/bash
bob:x:1002:1002:,,,:/home/bob:/bin/bash
➜  ~ ls -l media/nfs
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 bob    bob    0 Sep 20 20:36 hello.alice
-rw-r--r-- 1 alice  alice  0 Sep 20 19:17 hello.bob
➜  ~ 

The solution

At the core of the system, Linux does not really care about usernames, but users are referred to by their $UID. This can quickly become a huge mess, especially when you start adding groups, group permissions and SETGUID's. There are a multitude of solutions to this.

A manual solution

The simplest solution is actually a stupidly simple one. You keep track of your users and group ID's in a file or spreadsheet and manually set the ID's when adding users to your systems. Obviously this is very labor intensive and not practical on a large scale deployment but I'm mentioning it out of completeness. Not every organisation needs a full blown LDAP back end. Sometimes, easy is the better option.

We can rectify our problem on the second client by swapping the UID's of both alice and bob. This is done as follows. It's a two step procedure for both the user and the group. This will change the ID's of both alice and bob, plus it will chown all files under their home to the correct user ID.

➜  ~ id bob
uid=1001(alice) gid=1001(alice) groups=1001(alice)
uid=1002(bob) gid=1002(bob) groups=1002(bob)
➜  ~ sudo usermod -u 1001 bob
usermod: UID '1001' already exists
➜  ~ sudo usermod -u 1003 bob
➜  ~ sudo usermod -u 1002 alice
➜  ~ sudo usermod -u 1001 bob  
➜  ~ id bob
uid=1001(bob) gid=1002(bob) groups=1002(bob)
➜  ~ id alice
uid=1002(alice) gid=1001(alice) groups=1001(alice)
➜  ~ ls -l media/nfs
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 alice  bob    0 Sep 20 20:36 hello.alice
-rw-r--r-- 1 bob    alice  0 Sep 20 19:17 hello.bob
➜  ~ sudo groupmod -g 1003 bob
➜  ~ sudo groupmod -g 1002 alice
➜  ~ sudo groupmod -g 1001 bob  
➜  ~ ls -l media/nfs            
total 0
-rw-r--r-- 1 waldek waldek 0 Sep 20 19:12 hello
-rw-r--r-- 1 alice  alice  0 Sep 20 20:36 hello.alice
-rw-r--r-- 1 bob    bob    0 Sep 20 19:17 hello.bob
➜  ~ 

An old school centralized solution

The first centralized account management solution we'll discover is called NIS, an oldie but goodie. It's not widely used anymore, mostly in favor of openLDAP, but it's a good entry point to understand how all user verification systems integrate into a Linux client. It's worth reading through the Debian specific installation page. It gives modern setup instructions for an old service.

The server

There is a meta package available called nis which installs all necessary components for both the server and the client. We'll do the server first. The configuration changed drastically between Debian 10 and 11 but the fundamentals still apply.

➜  ~ sudo apt install nis
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
nis is already the newest version (4.4).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜  ~ 

Now NIS is installed but we need to configure it to offer the local accounts onto the LAN. The installation added a few interesting configuration files to you server. The following four files determine how your server and/or client behave.

➜  ~ ls /etc/default/nis 
/etc/default/nis
➜  ~ ls /etc/yp*        
/etc/yp.conf  /etc/ypserv.conf  /etc/ypserv.securenets
➜  ~ 

One file that is not added but essential for NIS to function is /etc/defaultdomain. Each server exposes a domain onto the network and its name has to be defined in the latter file. The domain can be anything you like but most administrators would make it the same as the hostname of the server. I'll set it differently just for demonstration purposes.

➜  ~ ls -l /etc/defaultdomain 
-rw-r--r-- 1 root root 12 Sep 27 11:18 /etc/defaultdomain
➜  ~ cat /etc/defaultdomain 
waldekworld
➜  ~ 

Clients work in broadcast by default. The documentation suggests to change the /etc/default/rpcbind file to accommodate this. The change is the -r argument to the OPTIONS="-w" line. What does this argument do? A quick man rpcbind offers the following explanation.

 -r		Turn on remote calls. Cause rpcbind to open up random listening ports. Note that rpcinfo need this feature turned on
        for work properly. (This flag is a Debian extension.)

➜  ~ cat /etc/default/rpcbind    
# /etc/init.d/rpcbind

OPTIONS=""

# Cause rpcbind to do a "warm start" utilizing a state file (default)
OPTIONS="-w -r"

# Uncomment the following line to restrict rpcbind to localhost only for UDP requests
# OPTIONS="${OPTIONS} -h 127.0.0.1 -h ::1"

# Uncomment the following line to enable libwrap TCP-Wrapper connection logging
# OPTIONS="${OPTIONS} -l "
➜  ~ 

Now that the domain is defined we can enable and start the services related to NIS.

➜  ~ sudo /usr/lib/yp/ypinit -m

At this point, we have to construct a list of the hosts which will run NIS
servers.  debiannis.lan is in the list of NIS server hosts.  Please continue to add
the names for the other hosts, one per line.  When you are done with the
list, type a <control D>.
	next host to add:  debiannis.lan
	next host to add:  
The current list of NIS servers looks like this:

debiannis.lan

Is this correct?  [y/n: y]  y
We need a few minutes to build the databases...
Building /var/yp/waldekworld/ypservers...
Running /var/yp/Makefile...
gmake[1]: Entering directory '/var/yp/waldekworld'
Updating passwd.byname...
Updating passwd.byuid...
Updating group.byname...
Updating group.bygid...
Updating hosts.byname...
Updating hosts.byaddr...
Updating rpc.byname...
Updating rpc.bynumber...
Updating services.byname...
Updating services.byservicename...
Updating netid.byname...
Updating protocols.bynumber...
Updating protocols.byname...
Updating netgroup...
Updating netgroup.byhost...
Updating netgroup.byuser...
Updating shadow.byname...
gmake[1]: Leaving directory '/var/yp/waldekworld'

debiannis.lan has been set up as a NIS master server.

Now you can run ypinit -s debiannis.lan on all slave server.
➜  ~ 

The first client

On the clients we'll have to install the same nis package as we did on the server. We'll also have to define the /etc/defaultdomain the same way we did on the server.

➜  ~ sudo apt install nis
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
nis is already the newest version (4.4).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜  ~ cat /etc/defaultdomain     
waldekworld
➜  ~ 

To start the client service we need to enable and start ypbind.service. But by default it will give errors!

➜  ~ sudo systemctl start ypbind.service          
Job for ypbind.service failed because the control process exited with error code.
See "systemctl status ypbind.service" and "journalctl -xe" for details.
➜  ~ sudo journalctl -e --unit ypbind.service --no-pager
-- Journal begins at Mon 2021-09-27 13:40:56 CEST, ends at Mon 2021-09-27 13:52:10 CEST. --
Sep 27 13:48:12 debianclient1 systemd[1]: Starting NIS Binding Service...
Sep 27 13:48:12 debianclient1 ypbind[3494]: No NIS server and no -broadcast option specified.
Sep 27 13:48:12 debianclient1 ypbind[3494]: Add a NIS server to the /etc/yp.conf configuration file,
Sep 27 13:48:12 debianclient1 ypbind[3494]: or start ypbind with the -broadcast option.
Sep 27 13:48:12 debianclient1 systemd[1]: ypbind.service: Control process exited, code=exited, status=1/FAILURE
Sep 27 13:48:12 debianclient1 systemd[1]: ypbind.service: Failed with result 'exit-code'.
Sep 27 13:48:12 debianclient1 systemd[1]: Failed to start NIS Binding Service.
➜  ~ 

We can solve the problem in two ways:

  1. add our NIS server address to the client's /etc/yp.conf file
  2. let the client broadcast it's requests.

I'll opt for the latter because I find it easier. This configuration is done in the /etc/default/nis file. The argument needed is the -broadcast to YPBINDARGS

➜  ~ cat /etc/default/nis                               
#
# /etc/defaults/nis	Optional configuration settings for the NIS programs.
#

#
# The following two variables are still used in the init script, but
# ignored by systemd. See `nis.debian.howto` in the documentation
# directory for more information.
#
# Are we a NIS server and if so what kind (values: false, slave, master)?
NISSERVER=false
# Are we a NIS client?
NISCLIENT=false

# Location of the master NIS password file (for yppasswdd).
# If you change this make sure it matches with /var/yp/Makefile.
YPPWDDIR=/etc

# Do we allow the user to use ypchsh and/or ypchfn ? The YPCHANGEOK
# fields are passed with -e to yppasswdd, see it's manpage.
# Possible values: "chsh", "chfn", "chsh,chfn"
YPCHANGEOK=chsh

# NIS master server.  If this is configured on a slave server then ypinit
# will be run each time NIS is started.
NISMASTER=

# Additional options to be given to ypserv when it is started.
YPSERVARGS=

# Additional options to be given to ypbind when it is started.
YPBINDARGS="-broadcast"

# Additional options to be given to yppasswdd when it is started.  Note
# that if -p is set then the YPPWDDIR above should be empty.
YPPASSWDDARGS=

# Additional options to be given to ypxfrd when it is started.
YPXFRDARGS=
➜  ~ 

When we restart ypbind.service now we won't get any errors!

➜  ~ sudo systemctl start ypbind.service 
➜  ~ 

Last thing to do is to initialize the client. This is done by running the ypinit program. By default it's not in your PATH but you can specifically call it as follows.

➜  ~ sudo /usr/lib/yp/ypinit -s debiannis.lan
We will need a few minutes to copy the data from debiannis.lan.
Transferring group.bygid...
Trying ypxfrd ... success

Transferring protocols.byname...
Trying ypxfrd ... success

Transferring ypservers...
Trying ypxfrd ... success

Transferring rpc.byname...
Trying ypxfrd ... success

Transferring passwd.byname...
Trying ypxfrd ... success

Transferring hosts.byname...
Trying ypxfrd ... success

Transferring netgroup.byuser...
Trying ypxfrd ... success

Transferring protocols.bynumber...
Trying ypxfrd ... success

Transferring hosts.byaddr...
Trying ypxfrd ... success

Transferring passwd.byuid...
Trying ypxfrd ... success

Transferring services.byname...
Trying ypxfrd ... success

Transferring shadow.byname...
Trying ypxfrd ... success

Transferring netid.byname...
Trying ypxfrd ... success

Transferring group.byname...
Trying ypxfrd ... success

Transferring services.byservicename...
Trying ypxfrd ... success

Transferring netgroup...
Trying ypxfrd ... success

Transferring netgroup.byhost...
Trying ypxfrd ... success

Transferring rpc.bynumber...
Trying ypxfrd ... success


debianclient1's NIS data base has been set up.
If there were warnings, please figure out what went wrong, and fix it.

At this point, make sure that /etc/passwd and /etc/group have
been edited so that when the NIS is activated, the data bases you
have just created will be used, instead of the /etc ASCII files.
➜  ~ 
Querying the server

A couple of additional programs got installed when installing the nis package. Try a yp plus tab complete in your shell to get a list of them.

  ~ yp
ypcat         ypchfn        ypchsh        ypdomainname  ypmatch       yppasswd      ypwhich     

ypwhich can be used to see which ypserver your client is connected to. You can also list you domain and get a peak look at the available accounts on the server.

➜  ~ ypwhich     
192.168.0.185
➜  ~ ypdomainname 
waldekworld
➜  ~ ypcat passwd 
bob:x:1001:1001:,,,:/home/bob:/bin/bash
alice:x:1002:1002:,,,:/home/alice:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
waldek:x:1000:1000:waldek,,,:/home/waldek:/usr/bin/zsh
➜  ~ 

Adding more accounts on the server

I'll add two new accounts on the server to test whether we can actually log in on the client machine.

➜  ~ sudo adduser bert 
adduser: The user `bert' already exists.
➜  ~ sudo adduser naomi
adduser: The user `naomi' already exists.
➜  ~ 

Let's see how this reflects on the client.

➜  ~ ypcat passwd
bob:x:1001:1001:,,,:/home/bob:/bin/bash
alice:x:1002:1002:,,,:/home/alice:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
waldek:x:1000:1000:waldek,,,:/home/waldek:/usr/bin/zsh
➜  ~ 

The accounts don't seem to be available? This is because when you make changes on the server you need to update the database on that end.

➜  ~ sudo /usr/lib/yp/ypinit -m

At this point, we have to construct a list of the hosts which will run NIS
servers.  debiannis.lan is in the list of NIS server hosts.  Please continue to add
the names for the other hosts, one per line.  When you are done with the
list, type a <control D>.
	next host to add:  debiannis.lan
	next host to add:  
The current list of NIS servers looks like this:

debiannis.lan

Is this correct?  [y/n: y]  t
We need a few minutes to build the databases...
Building /var/yp/waldekworld/ypservers...
Running /var/yp/Makefile...
gmake[1]: Entering directory '/var/yp/waldekworld'
Updating passwd.byname...
Updating passwd.byuid...
Updating group.byname...
Updating group.bygid...
Updating hosts.byname...
Updating hosts.byaddr...
Updating rpc.byname...
Updating rpc.bynumber...
Updating services.byname...
Updating services.byservicename...
Updating netid.byname...
Updating protocols.bynumber...
Updating protocols.byname...
Updating netgroup...
Updating netgroup.byhost...
Updating netgroup.byuser...
Updating shadow.byname...
gmake[1]: Leaving directory '/var/yp/waldekworld'

debiannis.lan has been set up as a NIS master server.

Now you can run ypinit -s debiannis.lan on all slave server.
➜  ~ 

On the client you don't have to do anything!

➜  ~ ypcat passwd
bob:x:1001:1001:,,,:/home/bob:/bin/bash
alice:x:1002:1002:,,,:/home/alice:/bin/bash
naomi:x:1004:1004:,,,:/home/naomi:/bin/bash
bert:x:1003:1003:,,,:/home/bert:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
waldek:x:1000:1000:waldek,,,:/home/waldek:/usr/bin/zsh
➜  ~ 

Both accounts are now available on the client! Let's try switching to the bert or naomi account.

➜  ~ su bert 
su: user bert does not exist or the user entry does not contain all the required fields
➜  ~ 

This does not seem to work! Everything is set in place to communicate with the nis server but the client machine has not been configured to use NIS as a valid authentication mechanism. This is why we're looking at this first centralized authentication process because every other system, such as LDAP, also need to play by these rules.

Setting up the client for authentication

On each Linux system you'll find a /etc/nsswitch.conf file. Let's have a look at it on our client.

➜  ~ cat /etc/nsswitch.conf 
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.

passwd:         files systemd
group:          files systemd
shadow:         files
gshadow:        files

hosts:          files dns
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis
➜  ~ 

This file describes where and how different services get there information from. I highly advise you to have a read of the man nsswitch.conf pages because it's a lot more flexible than you think. We can now either append nis to the necessary lines, or fully replace them. The necessary lines are the passwd, group and shadow lines.

➜  ~ cat /etc/nsswitch.conf 
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.

passwd:         files systemd nis
group:          files systemd nis
shadow:         files nis
gshadow:        files

hosts:          files dns
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis
➜  ~ 

The last thing to do is to add a special entry in the local (on each client) passwd, group and shadow files to make room for accounts coming from the NIS server.

➜  ~ sudo tail -n 2 /etc/passwd /etc/shadow /etc/group
==> /etc/passwd <==
+::::::


==> /etc/shadow <==
+::::::::


==> /etc/group <==
+:::

➜  ~ 

With this set and done we can use one centralized service to manage all of our account.

Some tips

In short, to add a second client you need to:

  1. install nis
  2. configure the ypbind.service to -broadcast
  3. enable and start the ypbind.service
    • test the connection with ypwhich
  4. add nis to the /etc/nsswitch.conf file as an authentication method
  5. add placeholder lines for external accounts
    • in the /etc/passwd file
    • in the /etc/shadow file
    • in the /etc/group file

If the account changes made on the server aren't reflected onto the clients, make sure your database is up to date. If it is and the client still doesn't behave like it should, have a look at the man nscd pages. Once you understand what it is, maybe try restarting it, or even disabling it all together.