Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – III

In the first blog post
Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – I
of this series about virtual networking between network namespaces I had discussed some basic CLI Linux commands to set up and enter network namespaces on a Linux system. In a second post
Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – II
I suggested and described several networking experiments which can quickly be set up by these tools. As containers are based on namespaces we can study virtual networking between containers on a host in principle just by connecting network namespaces. Makes e.g. the planning of firewall rules and VLANs a bit easier ...

The virtual environment we want to build up and explore step by step is displayed in the following graphics:

In this article we shall cover experiment 1 and experiment 2 discussed in the last article - i.e. we start with the upper left corner of the drawing.

Experiment 1: Connect two network namespaces directly

This experiments creates the dotted line between netns1 and netns2. Though simple this experiments lays the foundation for all other experiments.

We place the two different Ethernet interfaces of a veth device in the two (unnamed) network namespaces (with hostnames) netns1 and netns2. We assign IP addresses (of the same network class) to the interfaces and check a basic communication between the network namespaces. The situation corresponds to the following simple picture:

What shell commands can be used for achieving this? You may put the following lines in a file for keeping them for further experiments or to create a shell script:

unshare --net --uts /bin/bash &
export pid_netns1=$!
nsenter -t $pid_netns1 -u hostname netns1
unshare --net --uts /bin/bash &
export pid_netns2=$!
nsenter -t $pid_netns2 -u hostname netns2
ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2
nsenter -t $pid_netns1 -u -n /bin/bash
ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
ip link set veth11 up
ip link set lo up
ip a s
exit
nsenter -t $pid_netns2 -u -n /bin/bash
ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
ip link set veth22 up
ip a s
exit
lsns -t net -t uts

If you now copy these lines to the prompt of a root shell of some host "mytux" you will get something like the following:

mytux:~ # unshare --net --uts /bin/bash &
[2] 32146
mytux:~ # export pid_netns1=$!

[2]+  Stopped                 unshare --net --uts /bin/bash
mytux:~ # nsenter -t $pid_netns1 -u hostname netns1
mytux:~ # unshare --net --uts /bin/bash &
[3] 32154
mytux:~ # export pid_netns2=$!

[3]+  Stopped                 unshare --net --uts /bin/bash
mytux:~ # nsenter -t $pid_netns2 -u hostname netns2
mytux:~ # ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2
mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
netns1:~ # ip link set veth11 up
netns1:~ # ip link set lo up
netns1:~ # ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth11: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether da:34:49:a6:18:ce brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.1/24 brd 192.168.5.255 scope global veth11
       valid_lft forever preferred_lft forever
netns1:~ # exit
exit
mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
netns2:~ # ip link set veth22 up
netns2:~ # ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth22: <NO-CARRIER,BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether f2:ee:52:f9:92:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.2/24 brd 192.168.5.255 scope global veth22
       valid_lft forever preferred_lft forever
    inet6 fe80::f0ee:52ff:fef9:9240/64 scope link tentative 
       valid_lft forever preferred_lft forever
netns2:~ # exit
exit
mytux:~ # lsns -t net -t uts
        NS TYPE NPROCS   PID USER  COMMAND
4026531838 uts     387     1 root  /usr/lib/systemd/systemd --switched
4026531963 net     385     1 root  /usr/lib/systemd/systemd --switched
4026532178 net       1   581 root  /usr/sbin/haveged -w 1024 -v 0 -F
4026540861 net       1  4138 rtkit /usr/lib/rtkit/rtkit-daemon
4026540984 uts       1 32146 root  /bin/bash
4026540986 net       1 32146 root  /bin/bash
4026541078 uts       1 32154 root  /bin/bash
4026541080 net       1 32154 root  /bin/bash
rux:~ # 

Of course, you recognize some of the commands from my first blog post. Still, some details are worth a comment:

Unshare, background shells and shell variables:
We create a separate network (and uts) namespace with the "unshare" command and background processes.

unshare --net --uts /bin/bash &

Note the options! We export shell variables with the PIDs of the started background processes [$!] to have these PIDs available in subshells later on. Note: From our original terminal window (in my case a KDE "konsole" window) we can always open a subshell window with:

mytux:~ # konsole &>/dev/null

You may use another terminal window command on your system. The output redirection is done only to avoid KDE message clattering. In the subshell you may enter a previously created network namespace netnsX by

nsenter -t $pid_netnsX -u -n /bin/bash

Hostnames to distinguish namespaces at the shell prompt:
Assignment of hostnames to the background processes via commands like

nsenter -t $pid_netns1 -u hostname netns1

This works through the a separation of the uts namespace. See the first post for an explanation.

Create veth devices with the "ip" command:
The key command to create a veth device and to assign its two interfaces to 2 different network namespaces is:

ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2

Note, that we can use PIDs to identify the target network namespaces! Explicit names of the network namespaces are not required!

The importance of a running lo-device in each network namespace:
We intentionally did not set the loopback device "lo" up in netns2. This leads to an interesting observation, which many admins are not aware of:

The lo device is required (in UP status) to be able to ping network interfaces (here e.g. veth11) in the local namespace!

This is standard: If you do not specify the interface to ping from via an option "-I" the ping command will use device lo as a default! The ping traffic runs through it! Normally, we just do not realize this point, because lo almost always is UP on a standard system (in its root namespace).

For testing the role of "lo" we now open a separate terminal window:

mytux:~ # konsole &>/dev/null 

There:

mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
^C
--- 192.168.5.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms

netns2:~ # ip link set lo up
netns2:~ # ping 192.168.5.2 -c2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.017 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.033 ms

--- 192.168.5.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 998ms
rtt min/avg/max/mdev = 0.017/0.034 ms

And: Within the same namespace and "lo" down you cannot even ping the second Ethernet interface of a veth device from the first interface - even if they belong to the same network class:
Open anew sub shell and enter e.g. netns1 there:

netns1:~ # ip link add vethx type veth peer name vethy 
netns1:~ # ip addr add 192.168.20.1/24 brd 192.168.20.255 dev vethx 
netns1:~ # ip addr add 192.168.20.2/24 brd 192.168.20.255 dev vethy 
netns1:~ # ip link set vethx up
netns1:~ # ip link set vethy up
netns1:~ # ping 192.168.20.2 -I 192.168.20.1
PING 192.168.20.2 (192.168.20.2) from 192.168.20.1 : 56(84) bytes of data.
^C
--- 192.168.20.2 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3000ms
netns1:~ # ip link set lo up
netns1:~ # ping 192.168.20.2 -I 192.168.20.1                                                                               
PING 192.168.20.2 (192.168.20.2) from 192.168.20.1 : 56(84) bytes of data.                                                 
64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.019 ms                                                                
64 bytes from 192.168.20.2: icmp_seq=2 ttl=64 time=0.052 ms                                                                
^C                                                                                                                         
--- 192.168.20.2 ping statistics ---                                                                                       
2 packets transmitted, 2 received, 0% packet loss, time 999ms                                                              
rtt min/avg/max/mdev = 0.019/0.035/0.052/0.017 ms                                                                          
netns1:~ #                                           

Connection test:
Now back to our experiment. Let us now try to ping netns1 from netns2:

netns2:~ # ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether f2:ee:52:f9:92:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.2/24 brd 192.168.5.255 scope global veth22
       valid_lft forever preferred_lft forever
    inet6 fe80::f0ee:52ff:fef9:9240/64 scope link 
       valid_lft forever preferred_lft forever
netns2:~ # ping 192.168.5.1
PING 192.168.5.1 (192.168.5.1) 56(84) bytes of data.
64 bytes from 192.168.5.1: icmp_seq=1 ttl=64 time=0.030 ms
64 bytes from 192.168.5.1: icmp_seq=2 ttl=64 time=0.033 ms
64 bytes from 192.168.5.1: icmp_seq=3 ttl=64 time=0.036 ms
^C
--- 192.168.5.1 ping statistics ---                                                                  
3 packets transmitted, 3 received, 0% packet loss, time 1998ms                                       
rtt min/avg/max/mdev = 0.030/0.033/0.036/0.002 ms                                                    
netns2:~ #     

OK! And vice versa:

mytux:~ #  nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ #  nsenter -t $pid_netns2 -u -n /bin/bash
netns1:~ # ping 192.168.5.2 -c2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.023 ms

--- 192.168.5.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms
netns1:~ # 

Our direct communication via veth works as expected! Network packets are not stopped by network namespace borders - this would not make much sense.

Experiment 2: Connect two namespaces via a bridge in a third namespace

We now try a connection of netns1 and netns2 via a Linux bridge "brx", which we place in a third namespace netns3:

Note:

This is a standard way to connect containers on a host!

LXC tools as well as libvirt/virt-manager would help you to establish such a bridge! However, the bridge would normally be place inside the host's root namespace. In my opinion this is not a good idea:

A separate 3rd namespace gets the the bridge and related firewall and VLAN rules outside the control of the containers. But a separate namespace also helps to isolate the host against any communication (and possible attacks) coming from the containers!

So, let us close our sub terminals from the first experiment and kill the background shells:

mytux:~ # kill -9 32146
[2]-  Killed                  unshare --net --uts /bin/bash
mytux:~ # kill -9 32154
[3]+  Killed                  unshare --net --uts /bin/bash

We adapt our setup commands now to create netns3 and bridge "brx" there by using "brctl bradd". Futhermore we add two different veth devices; each with one interface in netns3. We attach the interface to the bridge via "brctl addif":

unshare --net --uts /bin/bash &
export pid_netns1=$!
nsenter -t $pid_netns1 -u hostname netns1
unshare --net --uts /bin/bash &
export pid_netns2=$!
nsenter -t $pid_netns2 -u hostname netns2
unshare --net --uts /bin/bash &
export pid_netns3=$!
nsenter -t $pid_netns3 -u hostname netns3
nsenter -t $pid_netns3 -u -n /bin/bash
brctl addbr brx  
ip link set brx up
exit 
ip link add veth11 netns $pid_netns1 type veth peer name veth13 netns $pid_netns3
ip link add veth22 netns $pid_netns2 type veth peer name veth23 netns $pid_netns3
nsenter -t $pid_netns1 -u -n /bin/bash
ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
ip link set veth11 up
ip link set lo up
ip a s
exit
nsenter -t $pid_netns2 -u -n /bin/bash
ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
ip link set veth22 up
ip a s
exit
nsenter -t $pid_netns3 -u -n /bin/bash
ip link set veth13 up
ip link set veth23 up
brctl addif brx veth13
brctl addif brx veth23
exit

It is not necessary to show the reaction of the shell to these commands. But note the following:

  • The bridge has to be set into an UP status.
  • The veth interfaces located in netns3 do not get an IP address. Actually, a veth interface plays a different role on a bridge than in normal surroundings.
  • The bridge itself does not get an IP address.

Bridge ports
By attaching the veth interfaces to the bridge we create a "port" on the bridge, which corresponds to some complicated structures (handled by the kernel) for dealing with Ethernet packets crossing the port. You can imagine the situation as if e.g. the veth interface veth13 corresponds to the RJ45 end of a cable which is plugged into the port. Ethernet packets are taken at the plug, get modified sometimes and then are transferred across the port to the inside of the bridge.

However, when we assign an Ethernet address to the other interface, e.g. veth11 in netns1, then the veth "cable" ends in a full Ethernet device, which accepts network commands as "ping" or "nc".

No IP address for the bridge itself!
We do NOT assign an IP address to the bridge itself; this is a bit in contrast to what e.g. happens when you set up a bridge for networking with the tools of virt-manager. Or what e.g. Opensuse does, when you setup a KVM virtualization host with YaST. In all these cases something like

ip addr add 192.168.5.100/24 brd 192.168.5.255 dev brx 

happens in the background. However, I do not like this kind of implicit politics, because it opens ways into the namespace surrounding the bridge! And it is easy to forget this bridge interface both in VLAN and firewall rules.

Almost always, there is no necessity to provide an IP address to the bridge itself. If we need an interface of a namespace, a container or the host to a Linux bridge we can always use a veth device. This leads to a much is much clearer situation; you see the Ethernet interface and the port to the bridge explicitly - thus you have much better control, especially with respect to firewall rules.

Enter network namespace netns3:
Now we open a terminal as a sub shell (as we did in the previous example) and enter netns3 to have a look at the interfaces and the bridge.

mytux:~ # nsenter -t $pid_netns3 -u -n /bin/bash
netns3:~ # brctl show brx
bridge name     bridge id               STP enabled     interfaces
brx             8000.000000000000       no
netns3:~ # ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ce:fa:74:92:b5:00 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::1c08:76ff:fe0c:7dfe/64 scope link 
       valid_lft forever preferred_lft forever
3: veth13@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master brx state UP group default qlen 1000
    link/ether ce:fa:74:92:b5:00 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::ccfa:74ff:fe92:b500/64 scope link 
       valid_lft forever preferred_lft forever
4: veth23@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master brx state UP group default qlen 1000
    link/ether fe:5e:0b:d1:44:69 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::fc5e:bff:fed1:4469/64 scope link 
       valid_lft forever preferred_lft forever
netns3:~ # bridge link
3: veth13 state UP @brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master brx state forwarding priority 32 cost 2 
4: veth23 state UP @brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master brx state forwarding priority 32 cost 2 

Let us briefly discuss some useful commands:

Incomplete information of "brctl show":
Unfortunately, the standard command

brctl show brx

does not work properly inside network namespaces; it does not produce a complete output. E.g., the attached interfaces are not shown. However, the command

ip a s

shows all interfaces and their respective "master". The same is true for the very useful "bridge" command :

bridge link

If you want to see even more details on interfaces use

ip -d a s

and grep the line for a specific interface.

Just for completeness: To create a bridge and add a veth devices to the bridge, we could also have used:

ip link add name brx type bridge
ip link set brx up
ip link set dev veth13 master brx
ip link set dev veth23 master brx

Connectivity test with ping
Now, let us turn to netns1 and test connectivity:

mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # ip a s 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth11@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 6a:4d:0c:30:12:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.1/24 brd 192.168.5.255 scope global veth11
       valid_lft forever preferred_lft forever                                                       
    inet6 fe80::684d:cff:fe30:1204/64 scope link                                                     
       valid_lft forever preferred_lft forever                                                       
netns1:~ # ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.039 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 192.168.5.2: icmp_seq=3 ttl=64 time=0.054 ms
^C
--- 192.168.5.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.039/0.046/0.054/0.006 ms
netns1:~ # nc -l 41234

Note that - as expected - we do not see anything of the bridge and its interfaces in netns1! Note that the bridge basically is a device on the data link layer, i.e. OSI layer 2. In the current configuration we did nothing to stop the propagation of Ethernet packets on this layer - this will change in further experiments.

Connectivity test with netcat
At the end of our test we used the netcat command "nc" to listen on a TCP port 41234. At another (sub) terminal we can now start a TCP communication from netns2 to the TCP port 41234 in netns1:

mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # nc 192.168.5.1 41234
alpha
beta

This leads to an output after the last command in netns1:

netns1:~ # nc -l 41234
alpha
beta

So, we have full connectivity - not only for ICMP packets, but also for TCP packets. In yet another terminal:

  
mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 *:41234                 *:*                     LISTEN      
tcp        0      0 192.168.5.1:41234       192.168.5.2:45122       ESTABLISHED 
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
netns1:~ # 

Conclusion

It is pretty easy to connect network namespaces with veth devices. The interfaces can be assigned to different network namespaces by using a variant of the "ip" command. The target network namespaces can be identified by PIDs of their basic processes. We can link to namespaces directly via the interfaces of one veth device.

An alternative is to use a Linux bridge (for Layer 2 transport) in yet another namespace. The third namespace provides better isolation; the bridge is out of the view and control of the other namespaces.

We have seen that the commands "ip a s" and "bridge link" are useful to get information about the association of bridges and their assigned interfaces/ports in network namespaces.

In the coming article we extend our efforts to creating VLANs with the help of our Linux bridge. Stay tuned ....

Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – II

The topics of this blog post series are

  • the basic handling of network namespaces
  • and virtual networking between different network namespaces.

One objective is a better understanding of the mechanisms behind the setup for future (LXC) containers on a host; containers are based on namespaces (see the last post of this series for a mini introduction). The most important Linux namespace for networking is the so called "network namespace".

As explained in the previous article
Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – I
it is interesting and worthwhile to perform network experiments without referring to explicit names for network namespaces. Especially, when you plan to administer LXC containers with libvirt/virt-manager. You then cannot use the standard LXC tools or "ip"-options for explicit network namespace names.

We, therefore, had a look at relevant options for the ip-command and other typical userspace tools. The basic trick was/is to refer to PIDs of the processes originally associated with network namespaces. I discussed commands for listing network namespaces and associated processes. In addition, I showed how one can use shells for entering new or existing unnamed network namespaces. We finalized the first post with the creation of a veth device inside a distinct network namespace.

Advanced experiments - communication scenarios between network namespaces and groups of namespaces (or containers)

Regarding networking a container is represented by a network namespace, associated network devices and rules. A network namespace provides an isolation of the network devices assigned to this namespace plus related packet filter and routing rules from/against other namespaces/containers.

But very often you may have to deal not only with one container on a host but a whole bunch of containers. Therefore, another objective of experiments with network namespaces is

  • to study the setup of network communication lines between different containers - i.e. between different network namespaces -
  • and to study mechanisms for the isolation of the network packet flow between certain containers/namespaces against packets and from communication lines of other containers/namespaces or/and the host.

The second point may appear strange at first sight: Didn't we learn that the fundamental purpose of (network) namespaces already is isolation? Yes, the isolation of devices, but not the isolation of network packet crossing the network namespace borders. In realistic situations we, in addition, need to establish and at the same time isolate communication paths in between different containers/namespaces and to their environment.

Typically, we have to address a grouping of containers/namespaces in this context:

  • Different containers on one or several hosts should be able to talk to each other and the Internet - but only if these containers are members of a defined group.
  • At the same time we may need to isolate the communication occurring within a group of containers/namespaces against the communication flow of containers/namespaces of another group and against communication lines of the host.
  • Still, we may need to allow namespaces/containers of different groups to use a common NIC to the Internet despite an otherwise isolated operation.

All this requires a confinement of the flow of distinguished network packages along certain paths between network namespaces. Thus, the question comes up how to achieve separated virtual communication circuits between network namespaces already on the L2 level and across possibly involved virtual devices.

Veth devices, VLAN aware Linux bridges (or other types of virtual Linux switches) and VLAN tagging play a key role in simple (virtual) infrastructure approaches to such challenges. Packet filter rules of Linux' netfilter components additionally support the control of packet flow through such (virtual) infrastructure elements. Note:

The nice thing about network namespaces is that we can study all required basic networking principles easily without setting up LXC containers.

Test scenarios - an overview

I want to outline a collection of interesting scenarios for establishing and isolating communication paths between namespaces/containers. We start with a basic communication line between 2 different network namespaces. By creating more namespaces and veth devices, a VLAN aware bridge and VLAN rules we extend the test scenario's complexity step by step to cover the questions posed above. See the graphics below.

Everything actually happens on one host. But the elements of lower part (below the horizontal black line) also could be placed on a different host. The RJ45 symbols represent Ethernet interfaces of veth devices. These interfaces, therefore, appear mostly in pairs (as long as we do not define sub interfaces). The colors represent IDs (VIDs) of VLANs. Three standard Linux bridges are involved; on each of these bridges we shall activate VLAN filtering. We shall learn that we can, but do not need to tag packets outside of VLAN filtering bridges - with a few interesting exceptions.

I suggest 10 experiments to perform within the drawn virtual network. We cannot discuss details of all experiments in one blog post; but in the coming posts we shall walk through this graphics in several steps from the top to the bottom and from the left to the right. Each step will be accompanied by experiments.

I use the abbreviation "netns" for "network namespace" below. Note in advance that the processes (shells) underlying the creation of network namespaces in our experiments always establish "uts" namespaces, too. Thus we can assign different hostnames to the basic shell processes - this helps us to distinguish in which network namespace shell we operate by just looking at the prompt of a shell. All the "names" as netns1, netns2, ... appearing in the examples below actually are hostnames - and not real network namespace names in the sense of "ip" commands or LXC tools.

I should remark that I did the experiments below not just for fun, but because the use of VLAN tags in environments with Linux bridges are discussed in many Internet articles in a way which I find confusing and misleading. This is partially due to the fact that the extensions of the Linux kernel for VLAN definitions with the help of Linux bridges have reached a stable status only with kernel 3.9 (as far as I know). So many articles before 2014 present ideas which do not fit to the present options. Still, even today, you stumble across discussions which claim that you either do VLANs or bridging - but not both - and if, then only with different bridges for different VLANs. I personally think that today the only reason for such approaches would be performance - but not a strict separation of technologies.

Experiments

I hope the following experiments will provide readers some learning effects and also some fun with veth devices and bridges:

Experiment 1: Connect two namespaces directly
First we shall place the two different Ethernet interfaces of a veth device in two different (unnamed) network namespaces (with hostnames) netns1 and netns2. We assign IP addresses (of the same network class) to the interfaces and check a basic communication between the network namespaces. Simple and effective!

Experiment 2: Connect two namespaces via a bridge in a third namespace
Afterwards we instead connect our two different network namespaces netns1 and netns2 via a Linux bridge "brx" in a third namespace netns3. Note: We would use a separate 3rd namespace also in a scenario with containers to get the the bridge and related firewall and VLAN rules outside the control of the containers. In addition such a separate namespace helps to isolate the host against any communication (and possible attacks) coming from the containers.

Experiment 3: Establish isolated groups of containers
We set up two additional network namespaces (netns4, netns5). We check communication between all four namespaces attached to brx. Then we put netns1 and netns2 into a group ("green") - and netns4 and netns5 into another group ("rosa"). Communication between member namespaces of a group shall be allowed - but not between namespaces of different groups. Despite the fact that all namespaces are part of the same IP address class! We achieve this on the L2 level by assigning VLAN IDs (VIDs) to the bridge ports to which we attach netns1, netns2, em>netns2 and netns5.

We shall see how "PVIDs" are assigned to a specific port for tagging packets that move into the bridge through this port and how we untag outgoing packets at the very same port. Conclusion: So far, no tagging is required outside the Linux bridge brx for building simple virtual VLANs!

Experiment 4: Tagging outside the bridge?
Although not required we repeat the last experiment with defined subinterfaces of two veth devices (used for netns2 and netns5) - just to check that packet tagging occurs correctly outside the bridge. This is done in preparation for other experiments. But for the isolation of VLAN communication paths inside the bridge only the tagging of packets coming into the bridge through a port is relevant: A packet coming from outside is first untagged and then retagged when moving into the bridge. The reverse untagging and retagging for outgoing packets is done correctly, too - but the tag "color" outside the bridge actually plays no role for the filtered communication paths inside the bridge.

Experiment 5: Connection to a second independent environment - with keeping up namespace grouping
In reality we may have situations in which some containers of a defined group will be placed on different hosts. Can we extend the concept of separating container/namespace groups by VLAN tagging to a different hosts via two bridges? Bridge brx on the first host and a new bridge bry on the second (netns8)? Yes, we can!

In reality we would connect two hosts by Ethernet cards. We simulate this situation in our virtual environment again with a veth interface pair between "netns3" and "netns8". But as we absolutely do not want to mix packets of our two groups we now need to tag the packets on their way between the bridges. We shall see how to use subinterfaces of the (veth) Ethernet interfaces to achieve this. Note, that the two resulting communication paths between bridges may potentially lead to loops! We shall deal with this problem, too.

Experiment 6: Two tags on a bridge port? Members of two groups?
Now, we could have containers (namespaces) that should be able to communicate with both groups. Then we would need 2 VIDs on a bridge port for this special container/namespace. We establish netns9 for this test. We shall see that it is no problem to assign two VIDs to a port to filter the differently tagged packets going from the bridge outwards. Nevertheless we run into problems - not because of the assignment of 2 VIDs, but due the fact that we can only assign one PVID to each bridge port. This seems to limit our possibilities to tag incoming packets if we choose its value to be among the VIDS defined already on other ports. Then we cannot direct packages to 2 groups for existing VIDs.

We have to solve this by defining new additional paths inside the bridge for packages coming in through the port for netns9: We assign a PVID to the this port, which is different from all VIDs defined so far. Then we assign additional VIDs with the value of this new PVID to the ports of the members of our existing groups. An interesting question then is: Are the groups still isolated? Is pinging interrupted? And how to stop man-in-the-middle-attacks of netns9?

The answer lies in some firewall rules which must be established on the bridge! In case we use iptables (instead of the more suited ebtables) these rules MUST refer to the ports of the bridge via physdev options and IP addresses. However, ARP packets - coming from netns9 should pass to all interfaces of members of our groups.

Experiment 7: Separate the network groups by different IP address class
If we wanted a total separation of two groups we would also separate them on L3 - i.e we would assign IP addresses of separate IP address networks to the members of the different groups. Will transport across our bridges still work correctly under this condition? It should .... However, netns9 will get a problem then. We shall see that he could still communicate with both groups if we used subinterfaces for his veth interface - and defined two routes for him.

Experiment 8: Connection of container groups and the host to the Internet
Our containers/namespaces of group "green", which are directly or indirectly attached to bridge brx shall be able reach the Internet. The host itself, too. Normally, you would administer the host via an administration network, to which the host would connect via a specific network card separate from the card used to connect the containers/namespaces to the Internet. However, what can we do, if we only have exactly one Ethernet card available?

Then some extra care is required. There are several possible solutions for an isolation of the host's traffic to the Internet from the rest of the system. I present one which makes use of what we have learned so far about VLAN tagging. We set up a namespace netns10 with a third bridge "brz". We apply VLAN tagging in this namespace - inside the bridge, but also outside. Communication to the outside requires routing, too. Still, we need some firewall rules - including the interfaces of the bridge. The bridge can be interpreted as an IN/OUT interface plane to the firewall; there is of course only one firewall although the drawing indicates two sets of rules.

"netns11" just represents the Internet with some routing. We can replace the Ethernet card drawn in netns10 by a veth interface to achieve a connection to netns11; the second interface inside netns11 then represents some host on the Internet. It can be simulated by a tap device. We can check, how signals move to and from this "host".

Purely academic?

The scenarios discussed above seem to be complicated. Actually, they are not as soon as we get used to the involved elements and rules. But, still the whole setup may seem a bit academic ... However, if you think a bit about it, you may find that on a development system for web services you may have

  • two containers for frontend apache systems with load balancing,
  • two containers for web service servers,
  • two or three containers for a MySQL-systems with different types of replication,
  • one container representing a user system,
  • one container to simulate OWASP and other attacks on the servers and the user client.

If we want to simulate attacks on a web-service system with such a configuration on one host only, you are not so far from the scenario presented. Modern PC-systems (with a lot of memory) do have the capacity to host a lot of containers - if the load is limited.

Anyway, enough stuff for the coming blog posts ... During the posts I shall present the commands to set up the above network. These commands can be used in a script which gets longer with each post.

SFTP mit Key-Authentication auf (gehosteten) Linux-Servern für Web-Entwickler mit unterschiedlichen Privilegien – II

Im ersten Artikel der Serie zur SFTP-Einrichtung auf gehosteten Servern

SFTP mit Key-Authentication auf (gehosteten) Linux-Servern für Web-Entwickler mit unterschiedlichen Privilegien – I

hatten wir erste grundlegende Aspekte der SSH-Einrichtung diskutiert. Im jetzigen Artikel diskutiere ich zunächst einige Verbesserungen der SSH-Einstellungen und wende mich dann der Einrichtung von 2 Usergruppen in der "/etc/sshd_config" zu.

Sicherheits-Hinweis für den Umgang mit der sshd-config auf Remote Servern

Fehler sind menschlich. Durch Zerstören der SSHD-Konfiguration kann man sich von gehosteten Servern selbst aussperren. Bevor man also an der sshd_config rumspielt, sollte man sich immer einen weiteren Zugangsweg zum Server für Notfälle sichern.

Server-Hoster bieten für den Ernstfall ein Booten in einen Maintenance-Modus an. Dass das funktioniert, sollte man mal getestet haben. Ein Gleiches gilt für evtl. Backup-Verfahren. Ferner sollte man bei sich Kopien aller wichtigen Konfigurationseinstellungen haben. Ich selbst lege vor Änderungen immer eine Kopie der funktionierenden ssd_config an. Die kann man im Ernstfall im Maintenance-Modus wieder zurückspielen.

Ferner sollte man 2 bis 3 andere ssh-Verbindungen offen halten, bevor man den sshd-Dämon neu startet. Der Server setzt normalerweise eine Grace-Time für bereits geöffnete Verbindungen. Dieses Zeitintervall kann man dann im Ernstfall noch für Änderungen der sshd_config oder ein Zurückspielen einer funktionierenden Konfigurationsdatei nutzen!

Von Vorteil ist es auch, eine von der Gruppe, für die die Einstellungen manipuliert werden, unabhängige, SSH-fähige UserID zur Verfügung zu haben.

Verbesserungen der SSH-Enrichtung

Aufgrund der schon seit einiger Zeit erhöhten Sicherheitsanforderungen Anforderungen müssen wir die SSH-Einrichtung verbessern. Ich kann an dieser Stelle leider nicht auf Details eingehen - es sind aber vor allem bekannte Probleme im Bereich des initialen "Key Exchange" [KEX] zu beheben:

Einerseits sind Standardparameter und Schlüssellängen für bestimmte zugehörige asymmetrische Algorithmen, die auf Primfaktorzerlegung und Modulo-Verfahren beruhen, unzulänglich. Leider sieht der Standard selbst Verfahren als Fallback-Optionen verbindlich vor, die aktuellen Anforderungen nicht mehr genügen. Andererseits muss man leider auch hinter Standard-Parameter für elliptische Kryptographie große Fragezeichen hinsichtlich ihrer Zufälligkeit setzen.

Ein Teil der Probleme wurde bereits 2015 adressiert; siehe z.B.:
https://weakdh.org/imperfect-forward-secrecy-ccs15.pdf.
Informationen bzgl. möglicher Maßnahmen findet man etwa hier:
https://stribika.github.io/2015/01/04/secure-secure-shell.html.

Der erste Schritt zur Aufrüstung ist, dass wir uns die aktuelle Version von OpenSSH (z.Z. 7.2p2) beschaffen. Für Opensuse (ab der Version 13.1) nutzt man hierzu das "network"-Repository ; siehe hierzu http://download.opensuse.org/repositories/network/.

Danach lassen wir nur die Protokollvariante 2 und lediglich zwei z.Z. noch als sicher eingeschätzte initiale Schlüsselaustausch-Verfahren des SSH-Protokolls zu. Ferner schränken wir die Klassen der für die Serveridentifikation möglichen Schlüssel ein. Hierzu dienen die folgenden Statements in der Datei "/etc/ssh/sshd_config" unseres Servers "serv":

# We only allow for SSH protocol version 2 
Protocol 2

# We restrict the Key Exchange Algorithms !!!
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256

# Minimum length in DH 
KexDHMin 2048

# We restrict HostKeys types for Host authentification for protocol version 2
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# We restrict Ciphers 
#RekeyLimit default none
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# UsePam can be set to "yes" to get more control options via PAM 
# siehe hierzu die Diskussion in einem kommenden Artikel
UsePAM  yes 

 
Man sollte für Zwecke im privaten oder Geschäftsumfeld zudem zugehörige flankierende Einstellungen in der lokalen Client-Konfigurations-Datei "/etc/ssh_config" vornehmen. Siehe den oben angegebenen Link.

Zwei Usergruppen und zweier Beispieluser

Im Folgenden verschaffen wir zwei exemplarischen SFTP-Usern

  • alpha : Mitglied der Entwicklergruppe "devgrp1" und der Gruppe "devgrp2"
  • beta : Mitglied der Entwicklergruppe "devgrp2".

einen ersten elementaren SFTP-Zugang zu den Verzeichnissen "/srv/www/htdocs/webs/project/alpha", "/srv/www/htdocs/webs/project/adm", "/srv/www/htdocs/webs/project/test/beta".

Zugang zu unterschiedlichen chroot-Verzeichnissen

Wir legen in einem ersten Anlauf zunächst das Verzeichnis "/srv/www/htdocs/webs/project/" als Dachverzeichnis an.

Dieses Verzeichnis wird uns gleichzeitig als chroot-Verzeichnis für alle beteiligten SFTP-User der Gruppe "devgrp1" dienen.

Ein weiteres untergeordnetes Verzeichnis "/srv/www/htdocs/webs/project/test" wird dagegen als chroot-Verzeichnis für die Mitglieder der Gruppe "devgrp2" verwendet.

An dieser Stelle muss auf einen wichtigen Punkt hingewiesen werden.

Jedes Verzeichnis, das unter SFTP als chroot-Jail für eine Usergruppe dienen soll, muss root gehören und nur root darf darauf Schreibrechte besitzen.

Ansonsten läuft man bei der Einrichtung des SFTP-Zugangs in Probleme, wie sie etwa hier geschildert sind:
http://superuser.com/questions/394298/sftp-chroot-result-in-broken-pipe

Also geben wir als User root auf dem Web/SFTP-Server "serv" Folgendes ein :

mytux:~ # mkdir /srv/www/htdocs/webs/project
mytux:~ # chgrp devgrp1 /srv/www/htdocs/webs/project
mytux:~ # chmod 750 mkdir /srv/www/htdocs/webs/project
mytux:~ # mkdir /srv/www/htdocs/webs/project/test
mytux:~ # chgrp devgrp2 /srv/www/htdocs/webs/project/test
mytux:~ # chmod 750 mkdir /srv/www/htdocs/webs/project/test
mytux:~ # mkdir /srv/www/htdocs/webs/project/alpha
mytux:~ # chgrp devgrp1 /srv/www/htdocs/webs/project/alpha
mytux:~ # chmod 770 mkdir /srv/www/htdocs/webs/project/alpha
mytux:~ # mkdir /srv/www/htdocs/webs/project/adm
mytux:~ # chgrp devgrp1 /srv/www/htdocs/webs/project/adm
mytux:~ # chmod 770 mkdir /srv/www/htdocs/webs/project/adm
mytux:~ # mkdir /srv/www/htdocs/webs/project/test/beta
mytux:~ # chgrp devgrp2 /srv/www/htdocs/webs/project/test/beta
mytux:~ # chmod 770 mkdir /srv/www/htdocs/webs/project/test/beta

 
Mitglieder der Gruppe "devgrp1" legen wir später auch als Mitglieder der Gruppe "devgrp2" an. Sie dürfen daher mit SFTP alle unter "/srv/www/htdocs/webs/project" liegenden Verzeichnisse einsehen; u.a. auch solche, die "devgrp2" zugänglich sind, aber zusätzlich auch weitere Verzeichnisse. "devgrp1" hat gegenüber der Gruppe "devgrp2" also mehr Privilegien. Mitglieder der Gruppe "devgrp2" sehen theoretisch zunächst nur den Inhalt von Verzeichnissen unterhalb "/srv/www/htdocs/webs/project/test".

Dabei gilt:

Schreiben und Unterverzeichnisse anlegen dürfen Mitglieder von "devgrp1" bzw. "devgrp2" aber nur in den Unter-Verzeichnissen "alpha" bzw. "beta" !

Berücksichtigung der künftigen SFTP-User-Gruppen in der SSHD-Konfigurationsdatei

Wir müssen den Usern "alpha" und "beta" zur Nutzung von SFTP zunächst grundsätzlich die Nutzung von SSH zugestehen. Dies führt zur Modifikation des "AllowUsers"-Eintrags in der Konfigurationsdatei, der im letzten Artikel diskutiert wurde :

AllowUsers usu alpha beta

Bei wenigen einzelnen Usern und Gruppen kann man vielleicht so arbeiten. Bei steigender Useranzahl werden die User aber typischerweise in Gruppen angeordnet. Dann ist es wichtig zu wissen, dass es auch die Direktive "AllowGroups" gibt. Insgesamt werden vom SSH-Daemon 4 Direktiven in folgender Reihenfolge abgearbeitet:

DenyUsers
AllowUsers
DenyGroups
AllowGroups

Das zuerst getroffene Muster zählt dabei unabhängig von nachfolgenden Muster-Treffern! Siehe:
https://en.wikibooks.org/wiki/OpenSSH/Server
Nicht zutreffende Treffer führen automatisch zu einem Default-Ausschluss von der Nutzung.

Beachtet bitte auch, dass host-spezifische Zusätze der Form USER@HOST nur zu User-IDs - nicht aber (!) zu Gruppen-IDs - möglich sind. Siehe:
http://manpages.ubuntu.com/manpages/hardy/man5/sshd_config.5.html
Wildcards in Host-Ergänzungen sind unter obigem Link auch beschrieben:
http://manpages.ubuntu.com/manpages/hardy/man5/ssh_config.5.html

Ist der User "usu" ein Mitglied der Gruppe "adm", so hätten wir in unserem Fall also auch schreiben können:

AllowGroups adm devgrp?

Man beachte, dass kein Komma sondern ein Blank zur Abtrennung mehrerer User oder Usergruppen voneinander benutzt wird.

Begrenzung des Zugriffs auf CHROOT-Verzeichnisse

Nun müssen wir bestimmte Verzeichnisse vorgeben, auf die sich der Zugang beschränken soll. Hierfür sind zwei Direktiven erforderlich:

  • Zum einen eine Einstellung zur Nutzung des internen SFTP-Mechanismus durch den jeweiligen User
  • und zum anderen eine Einstellung zur Definition eines alle Aktionen begrenzenden und kapselnden CHROOT-Verzeichnisses für jeden User.

Ich nehme diese Einstellung in user- und/oder gruppenspezifischen Segmenten der Konfigurationsdatei "/etc/sshd_config" vor. Solche Bereiche leitet man am Ende der Konfiguationsdatei durch die Schlüsselworte "Match Group" (oder "Match User") ein.

Bei den nachfolgenden Direktiven für die Gruppe oder den User wiederhole ich dabei einen Teil der generellen sicherheitsrelevanten SSH-Einstellungen. Der Grund hierfür ist:

Muss ich mal auf die Schnelle und testweise grundlegende SSH-Einstellungen ändern, so setze ich die Direktiven für meine kritischen SFTP-User nicht automatisch außer Kraft.

Also ergänzen wir genau am Ende der Datei "/etc/ssh/sshd_config":

Match Group devgrp1
        ForceCommand internal-sftp
        # ForceCommand internal-sftp -u 0007</strong>
        ChrootDirectory /srv/www/htdocs/webs/project
        RSAAuthentication yes
        PubkeyAuthentication yes
        PasswordAuthentication no 
        X11Forwarding no
        AllowTcpForwarding no
        AllowAgentForwarding no
        GatewayPorts no
Match  Group devgrp2,!devgrp1 
        ForceCommand internal-sftp
        # ForceCommand internal-sftp -u 0007
        ChrootDirectory /srv/www/htdocs/webs/project/test
        RSAAuthentication yes
        PubkeyAuthentication yes
        PasswordAuthentication no 
        X11Forwarding no
        AllowTcpForwarding no
        AllowAgentForwarding no
        GatewayPorts no

 
Interessant ist hier zunächst die zweite Match-Vorgabe

Match Group devgrp2,!devgrp1

Hier drücken wir aus, dass die nachfolgenden Parameter grundsätzlich für die Mitglieder/User der Gruppe "devgrp2" gelten soll, aber nicht für Mitglieder der Gruppe "devgrp1". Die logische Negation erfolgt durch den Operator "!".

In unserem Beispiel gelten die Anweisungen nach der zweiten "Match"-Zeile also lediglich für den User "beta" und evtl. andere User der Gruppe "devgrp2".

Hinweis:

Zwischen den beiden Kriterien für die Gruppenmitgliedschaft ist ein Komma einzufügen, aber kein Blank vor oder nach dem trennenden Komma!

Interessant ist ferner die potentielle Option "-u" hinter der auskommentierten ForceCommand Anweisung:

ForceCommand internal-sftp -u 0007

Diese Direktive setzt für Open-SSH-Versionen ≥ 5.5 gezielt eine "umask", die angeblich systemweite umask-Definitionen überschreibt. Nun ja - stimmt das wirklich? Wir kommen darauf im nächsten Artikel dieser Serie zurück.

Home-Verzeichnisse der User "alpha" und "beta"- und Ausschluss des Shell-Zugangs

Auf die Schritte zur User-Anlage und User-Zuordnung zu Gruppen gehe ich hier nicht genauer ein. Interessanter ist die Frage, wo sich eigentlich die Home-Verzeichnisse der User alpha und beta befinden sollen.

Diverse Artikel im Internet, die sich mit dem Aufsetzen von User-bezogenen Verzeichnissen unterhalb eines Chroot-Verzeichnisses befassen (s. die Links am Ende des Artikels), enthalten für unser Szenario eher verwirrende Information.

Zudem gilt:
Der SSHD-Dämon erwartet später die Public Key Files unserer User bei Default-Einstellungen an bestimmten Stellen in der Verzeichnisstruktur. Experimente mit einer Verlagerung der Home-Verzeichnisse in andere Bereiche des Dateibaums führen nach meiner Erfahrung schnell ins Chaos und zu mühsamem Suchen nach Fehlern. Das gilt selbst dann, wenn man später Pfade zu den Autorisierungsfiles explizit setzt. Also:

Einfach die Home-Verzeichnisse da lassen, wo sie normalerweise erzeugt werden. Wir entziehen unseren Entwicklern sowieso den Shell-Zugriff und engen ihren Wirkungskreis weiter per Chroot ein.

serv:~ # useradd -g devgrp1 -s /sbin/nologin -m -d /home/alpha -k /etc/skel alpha
serv:~ # passwd alpha 
serv:~ # useradd -g devgrp2 -s /sbin/nologin -m -d /home/beta -k /etc/skel beta
serv:~ # passwd beta 
serv:~ # usermod -G devgrp2 alpha

 

Generieren eines SSH-Schlüsselpaars pro SFTP-User

Da wir sicherheitsbewusste Administratoren sind, erlauben wir den eben angelegten Usern SSH/SFTP-Zugang nur auf Basis von SSH-Key-Authentication.

Wir wählen für unsere künftigen SFTP-User natürlich die Erzeugung eines SSH-Schlüssel-Paars, bei der der private Schlüssel mit einem Passwort geschützt wird. Schon aus Gründen einer durchgehenden Sicherheitsphilosophie.

In Übereinstimmung mit den Sicherheitsrichtlinien von https://stribika.github.io/2015/01/04/secure-secure-shell.html führen wir zur Schlüsselgenerierung folgende Kommandos aus - und kopieren danach den jeweiligen Public Keys zum SSH/SFTP-Server.

Wir zeigen das am Beispiel des Users "alpha" auf dem Client "mytux". Wir erzeugen sowohl ein Key-Paar, das auf elliptischer Kryptographie basiert und eines, das bei hinreichender Schlüssel-Länge RSA unterstützt. Schlüssellängen unter 2048 Bit sind für RSA-angelehnte Verfahren (also nicht elliptische Verfahren) nicht mehr als sicher anzusehen.

Dabei setzen wir voraus, dass "alpha" ein Verzeichnis "~/.ssh" angelegt hat.

alpha@mytux:~/.ssh> ssh-keygen -t ed25519 -f ssh_host_ed25519_key
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ssh_host_ed25519_key.
Your public key has been saved in ssh_host_ed25519_key.pub.
The key fingerprint is:
SHA256:wri++5yVtLbMQeinXxdqiduWm2gGAbTNaB8YPFJkNV8 ufo@mytux.mydomain
The key's randomart image is:
+--[ED25519 256]--+
|   =*.o   E      |
|  ..+B o .       |
|   .=o+ .        |
|   . +.o         |
|    . =.S   .    |
|     o.+ + o .   |
|    . ..O =..    |
|   . . OoOoo     |
|    ++=+B.+.     |
+----[SHA256]-----+

alpha@mytux:~/.ssh> ls
known_hosts  ssh_host_ed25519_key  ssh_host_ed25519_key.pub

alpha@mytux:~/.ssh> ssh-keygen -t rsa -b 4096 -f ssh_host_rsa_key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ssh_host_rsa_key.
Your public key has been saved in ssh_host_rsa_key.pub.
The key fingerprint is:
SHA256:cQfc/m7HrB1fLFsL8TA27FtiYYDJa2BIGoS3JRS607w ufo@mytux.mydomain
The key's randomart image is:
+---[RSA 4096]----+
|  +=..   ...     |
| ..o+.. . +..    |
| ...+. o.+.o.    |
|  +.  . .o..+    |
| o o    So   @   |
|  . .   .   + O. |
|   E         *.*+|
|            . B=O|
|             oo+o|
+----[

 
Analog erzeugen wir ein zweites separates Schlüsselpaar für die Entwickler der Gruppe "devgrp2". Wir legen dann als Schutz gegen Verlust Kopien dieser Schlüsselpaare in einem verschlüsselten Verzeichnis auf einem selbst kontrollierten Backup-Server an.

Dann bringen wir die Public (!) Key Datei für jeden User auf den Server. Dazu nutzen wir einen entsprechend privilegierten Benutzer (hier "usu"), der SSH-Zugang erhalten hat. Wir erinnern uns, dass wir bei der SSH-Einrichtung einen direkten SSH-Zugang des Users "root" verboten hatten. root auf dem System "mytux" kopiert den Public Key zwischenzeitlich in ein Verzeichnis "/home/usu/key_transfer" des Dummy Users "usu". Dann transferieren wir mittels "scp":

usu@mytux:~>scp -P 6xxxx -i ~/.ssh/id_rsa_usu /home/usu/key_transfer/ssh_host_ed25519_key.pub usu@serv.myhoster.net:/home/usu/key_transfer/
Enter passphrase for key '/home/ich/.ssh/id_rsa_usu': 
ssh_host_ed25519_key.pub    100%  390     0.4KB/s   00:00

 
"6xxxx" steht dabei für den verschobenen Port, unter dem der Server SSH anbietet. (Siehe hierzu den letzten Artikel dieser Serie).

Dann als root auf "serv":

serv:~ # mkdir /home/alpha/.ssh
serv:~ # chown alpha.devgrp1 /home/alpha/.ssh
serv:~ # cp /home/usu/key_transfer/ssh_host_ed25519_key.pub /home/alpha/.ssh/ssh_host_ed25519_key.pub
serv:~ # chown alpha.devgrp1 /home/alpha/.ssh/ssh_host_ed25519_key.pub 
serv:~ # rm /home/usu/key_transfer/ssh_host_ed25519_key.pub 
serv:~ # touch /home/alpha/.ssh/authorized_keys
serv:~ # chown alpha.devgrp1 /home/alpha/.ssh/authorized_keys
serv:~ # cat /home/alpha/.ssh/ssh_host_ed25519_key.pub >> /home/alpha/.ssh/authorized_keys
serv:~ # chmod 600 /home/alpha/.ssh/authorized_keys
serv:~ # chmod 600 /home/alpha/.ssh/ssh_host_ed25519_key.pub 
serv:~ # chmod 700 /home/alpha/.ssh

 
Analog für alle anderen Public Keys und User. Andere Verfahren - auch manuelle - um den Public key auf den Server zu bringen, werden hier diskutiert:
https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-freebsd-server

Bitte beachtet:

Die Rechtesetzungen sind wichtig! Bei unzureichendem Schutz wird SSH die Keys ggf. nicht akzeptieren.

Test

Wir sind nun so weit, dass wir einen ersten Test durchführen können. Bevor wir den SSH-Server auf unserem Testsystem neu starten, checken wir nochmal, dass die notwendigen Einstellungen für Key-Authentifzierung in der Datei schon vorgenommen wurden:

AllowUsers usu alpha beta
AllowGroups adm devgrp1 devgrp2 
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile      .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no

 
Dann erfolgt ein Restart von sshd :

serv:~ # systemctl restart sshd.service 

Wir probieren nun den Zugang mittels des Kommandos "sftp"; man beachte, dass die Option für den Port hier ein großes "P" erfordert !

ich@mytux:~> sftp -P 6xxxx -i ~/.ssh/ssh_host_ed25519_key beta@serv.myhoster.net
Enter passphrase for key '/home/ich/.ssh/ssh_host_ed25519_key': 
Connected to serv.myhoster.net
sftp> ls
beta 
sftp> pwd
Remote working directory: /
sftp> cd ../alpha
Couldn't stat remote file: No such file or directory
sftp> cd beta
sftp> pwd
Remote working directory: /beta
sftp> mkdir classes
sftp> ls -la
drwxr-xr-x    3 beta     devgrp2      4096 Feb 10 17:30 .
drwxr-xr-x    3 root     root         4096 Feb 10 16:30 ..
drwxr-xr-x    2 beta     devgrp2      4096 Feb 10 17:30 classes
sftp> cd classes
sftp> put /home/ich/classes/*
Uploading /home/ich/classes/class_ufo.php to /beta/classes/class_ufo.php
/home/ich/classes/class_ufo.php                                        100%    0     0KB/s   00:00    
Uploading /home/ich/classes/class_ufo2.php to /beta/classes/class_ufo2.php
/home/ich/classes/class_ufo2.php                                       100%    0     0KB/s   00:00    
sftp> ls
class_ufo.php    class_ufo2.php   
sftp>exit 
ich@mytux:~> 

 
Die letzten zwei Testfiles hatte ich als leere Files angelegt; daher die 0-Übertragungsrate!

Nun noch ein Kurztest für den User "alpha":

ich@mytux:~> sftp -P 6xxxx -i ~/.ssh/ssh_host_ed25519_key alpha@serv.myhoster.net
Enter passphrase for key '/home/ich/.ssh/ssh_host_ed25519_key': 
Connected to serv.myhoster.net
sftp> ls
adm    alpha  test 
sftp> ls /test/beta/classes
/test/beta/classes/class_ufo.php   /test/beta/classes/class_ufo2.php
sftp> mkdir /test/beta/uploads
sftp> ls /test/beta
/test/beta/classes   /test/beta/uploads   
sftp> exit
ich@mytux:~>

 
Damit genug für heute. Im nächsten Artikel dieser Serie gehe ich dann etwas genauer auf Rechtethemen beim Anlegen von Files per SFTP ein.

Links

Generelles zu SSH/SFTP
http://en.wikibooks.org/wiki/OpenSSH/Cookbook/SFTP
http://wiki.ubuntuusers.de/SSH
http://www.computerhope.com/unix/sftp.htm

Userbezogene Chroot-Verzeichnisse
https://www.mynakedgirlfriend.de/sichere-chroot-umgebung-fur-ssh-dateiubertragungen-sftp/
http://www.thegeekstuff.com/2012/03/chroot-sftp-setup/
https://wiki.archlinux.org/index.php/Talk:SFTP_chroot