Setting up an Active Directory authenticated Mumble Server

Basic Setup

Setting up a Mumble server is fairly simple, but I had a few problems to get it to play nice with my Active Directory. And since there wasn’t exactly a great deal of documentation on the subject, I figured that I should write a short How To.

I started with a new Debian Wheezy OpenVZ machine. I Installed the Mumble Server package from the Debian repository onto that:

apt-get install mumble-server

After the Installation is complete, you can configure the basics of the Mumble Server in its configuration file:

nano /etc/mumble-server.ini

If you configure it the way I did, only people in a specific Active Directory Group will be able to connect to the Server. However the Active Directory Authentication runs as its own Service and should that be down, anybody would be able to connect. If that is not something you are ok with, you could set a Server password in addition to the suggested configuration changes here.

1. Change the ice passwords. It is generally a good idea to have passwords on sensitive services. But these particular passwords will not be needed often, so there is really no reason not to use long randomly generated Passwords here:

icesecretread=Password1
icesecretwrite=Password2

2. You might want to change the maximum allowed bandwidth per user. Since my Server has a pretty decent Internet connection, I choose to set it to the maximum for option. I did not test if this value can be set higher, but I saw this described as the maximum value on several sites. This change does not effect clients with a low bandwidth Internet connection. When a connection is established, Mumble will check the server side and client side bandwidth settings and use the lower one of the two.

bandwidth=128000

That’s it. There is a bunch of other settings in the configuration, that allow you to customize your Server and quite a bit more, but you don’t have to touch them. The defaults are ok.

You also need to set the Superuser password with following command:

dpkg-reconfigure mumble-server

Now its time to start the mumble Server and test it with a client:

/etc/init.d/mumble-server start

Connecting with the client is easy:

1. Open Mumble and select Server –>Connect

Mumble Server Connect

2. Select Add New and enter the details of your server and use superuser as user:

Mumble Add Server

3. A certificate error will pop up, you can accept this certificate.

Mumble certificate error

4. You will be asked for a password. This is the Password you set in the dpkg-reconfigure step.

Mumble Password Request

5. You should be logged in right now.

Mumble Connected

Integrating it into the Active Directory

With the basics working, it is time to get to the part that caused me a little headache. There is one Open Source Project that provides a bunch of scripts for Mumble including one for LDAP Authentication. The Script and config file are well commented, but there is very little documentation other than those comments. The Mumble Scripts Project can be found on this Github page.

Unfortunately it did not work out of the box for me. User authentication generally failed when I used the “cn=username,ou=users,dc=example,dc=com” way of specifying the user Objects. Tough I figured out fairly fast, that the NTDOMAIN\username way of providing usernames worked fine.

The next problem was, that this script doesn’t work great when the users are in different OUs in your AD. And to top it off the Group Membership check did not work for me with its original filter. This means I had to change a few parts of the script to get it to work with my Active Directory. These changes work great for me, but with those changes the script will probably not work with non Active Directory LDAP Backends.

I downloaded the Scripts and put the file LDAPauth.py in the directory “/opt/mumble-scripts” and the LDAPauth.ini into the directory “/etc/mumble-scripts”.

For the LDAPauth script to work correctly I needed 2 additional Python modules. So I installed them:

apt-get install python-ldap python-daemon

Now I will save you a little bit of headache and show you the parts of the script, I modified in order to get this working with my AD:

The first change I made was in the way the username was put together for the initial bind. With that change I can use the NTDOMAIN\user name scheme to authenticate against the AD. The original implementation was also limiting the usefulness of the script, if the users are in multiple OUs. This change has to be done twice in the file LDAPauth.py at Line 463 and 516:

original:
bind_dn = "%s=%s,%s" % (cfg.ldap.username_attr, name, cfg.ldap.users_dn)

replaced by:
bind_dn = "%s\%s" % (cfg.ldap.nt_domain, name)

Next up is the search filter for the user. The filter in the original script will work, but is a little basic. With the change, the filter only matches user objects that have not been deactivated. This is in line 478 of the LDAPauth.py:

original:
res = ldap_conn.search_s(cfg.ldap.users_dn, ldap.SCOPE_SUBTREE, '(%s=%s)' % (cfg.ldap.username_attr, name), [cfg.ldap.number_attr, cfg.ldap.display_attr])

replaced by:
res = ldap_conn.search_s(cfg.ldap.users_dn, ldap.SCOPE_SUBTREE, '(&(%s=%s)(objectCategory=user)(!(userAccountControl=514)))' % (cfg.ldap.username_attr, name), [cfg.ldap.number_attr, cfg.ldap.display_attr])

The last change that I needed to make was the LDAP search string for the group membership. The Original would definitly not work with users in different OUs and im not quite sure, if Active Directory even tracks group memberships that way. The new searchstring starts at the users_dn which I treat as a basedn and looks for any not deactivated user accounts that have the group listed in their memberOf attribute. This change has to be made at line 499:

original:
res = ldap_conn.search_s(cfg.ldap.group_cn, ldap.SCOPE_SUBTREE, '(%s=%s=%s,%s)' % (cfg.ldap.group_attr, cfg.ldap.username_attr, name, cfg.ldap.users_dn), [cfg.ldap.number_attr, cfg.ldap.display_attr])

replaced by:
res = ldap_conn.search_s(cfg.ldap.users_dn, ldap.SCOPE_SUBTREE, '(&(%s=%s)(&(objectCategory=user)(!(userAccountControl=514))(memberof=%s)))' % (cfg.ldap.username_attr, name, cfg.ldap.group_cn),[cfg.ldap.number_attr, cfg.ldap.display_attr])

Next is the configuration file “/etc/mumble-scripts/LDAPauth.ini”. Here I left the user settings as they were.

The ice settings needed adjustments. The slice path was initially not correct and the secret should be set to the ice write password.

The LDAP settings needed most adjustments. The first change is the ldap uri, this should be set to the address of your Domain Controller.

The next change is the users_dn with the changes I made to the script, this is more or less the same as the base dn in other ldap scripts. Choose a path from which all relevant users can be found.

After the users_dn I inserted a new option: nt_domain. The changes I made to the authentication script, require this option to be here and to be set.

The username_attr is the filed which contains the logon name. In Active Directory this is sAMAccountName.

The number_attr requires a filed with a unique Integer as content, because mumble uses this as user id and maps its own groups to this ID. I choose the pager field, since I don’t know anyone who owns a Pager. Since the field is usually free you have to remember enter a unique number in this field every time you add a user to mumble. Alternately you could try using the uSNCreated field in small Active Directories. This field should be unique as long as you only use one Domain Controller to create users.

The display_attr field points to the field containing the display name. This is pretty much the purpose of the LDAP displayName attribute. Alternately you could use sAMAccountName for this again.

The next option group_cn is the ldap path to the group mumble users should be members of.

The group_attr option on the other hand has no function anymore since the new search string for groups has this hard coded.

The rest of the Options can be left as they are. Here is an example ini file for reference:

;Player configuration
[user]
;If you do not already know what it is just leave it as it is
id_offset       = 1000000000
;Reject users if the authenticator experiences an internal error during authentication
reject_on_error = True
;Reject users that are not found when bind_dn is used with non-user credentials.
;Setting this to False will cause a fall-through when the user is not found in LDAP.
reject_on_miss  = True

;Ice configuration
[ice]
host            = 127.0.0.1
port            = 6502
slice           = /usr/share/slice/Murmur.ice
secret          = cPB3sjD5c_grkWEZOoxg
watchdog        = 30

; LDAP specific configuration
[ldap]
; Use bind_dn and bind_pass if you use non-user credentials for searches.
;bind_dn = SKELLDOM\searchuser
;bind_pass = password
ldap_uri = ldap://dc2.skelleton.net
users_dn = ou=Local,dc=skelleton,dc=net
nt_domain = SKELLDOM
username_attr = sAMAccountName
number_attr = pager
display_attr = displayName
group_cn = cn=Mumble_Users,ou=Groups,ou=Remote,dc=skelleton,dc=net
group_attr = memberOf
;group_cn =

;Murmur configuration
[murmur]
;List of virtual server IDs, empty = all
servers      = 

;Logging configuration
[log]
; Available loglevels: 10 = DEBUG (default) | 20 = INFO | 30 = WARNING | 40 = ERROR
level   = 10
file    = /var/log/LDAPauth.log

[iceraw]
Ice.ThreadPool.Server.Size = 5

With all those changes you can start the script by entering:

/opt/mumble-script/LDAPauth.py

You should now be able to use the mumble client to log into the server with an authorized Active Directory account. If this works you are almost done. If not look in the logfile to see where the problem is.

Starting the Authentication Script as a Service

I am using a slightly modified version of the /etc/init.d/skeleton script to run the LDAP authentication as a service. One of the things I had to modify was the Required-Start line. I added “$all” to this line, to ensure, that the LDAPauth script starts after mumble. Otherwise it will not start. The major problem in this step was something else however. The start-stop-daemon created a pid file with the wrong process id in it. Because of this the init script was not able to shut down the running LDAP auth daemon. I added a small command to the script that creates the pid file with the correct process id to the script and now everything works as expected.

Here is the start/stop script I use:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          mumble-ldap
# Required-Start:    $remote_fs $syslog $network $all
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: LDAP Authentication for Mumble
# Description:       LDAP Authentication for Mumble
#                    placed in /etc/init.d.
### END INIT INFO

# Author: skelleton
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Mumble LDAP Authentication"
NAME=mumble-ldap
DAEMON=/opt/mumble-scripts/LDAPauth.py
DAEMON_ARGS="-d -i /etc/mumble-scripts/LDAPauth.ini"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
DAEMON_USER=mumble-server

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    start-stop-daemon --start --chuid $DAEMON_USER --pidfile $PIDFILE --startas $DAEMON --test > /dev/null \
        || return 1
    start-stop-daemon --start --chuid $DAEMON_USER --pidfile $PIDFILE --startas $DAEMON -- \
        $DAEMON_ARGS \
        || return 2
    # Add code here, if necessary, that waits for the process to be ready
    # to handle requests from services started subsequently which depend
    # on this one.  As a last resort, sleep for some time.
    pgrep -f /opt/mumble-scripts/LDAPauth.py > $PIDFILE
}

#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    start-stop-daemon --stop --retry=TERM/30/KILL/5 --pidfile $PIDFILE 
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
    # Wait for children to finish too if this is a daemon that forks
    # and if the daemon is only ever run from this initscript.
    # If the above conditions are not satisfied then add some other code
    # that waits for the process to drop all resources that could be
    # needed by services started subsequently.  A last resort is to
    # sleep for some time.
    start-stop-daemon --stop --oknodo --retry=0/30/KILL/5 --exec $DAEMON
    [ "$?" = 2 ] && return 2
    # Many daemons don't delete their pidfiles when they exit.
    rm -f $PIDFILE
    return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
    #
    # If the daemon can reload its configuration without
    # restarting (for example, when it is sent a SIGHUP),
    # then implement that here.
    #
    start-stop-daemon --stop --signal 1 --pidfile $PIDFILE 
    return 0
}

case "$1" in
  start)
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  stop)
    [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  status)
    status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
    ;;
  #reload|force-reload)
    #
    # If do_reload() is not implemented then leave this commented out
    # and leave 'force-reload' as an alias for 'restart'.
    #
    #log_daemon_msg "Reloading $DESC" "$NAME"
    #do_reload
    #log_end_msg $?
    #;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
            0) log_end_msg 0 ;;
            1) log_end_msg 1 ;; # Old process is still running
            *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
        # Failed to stop
        log_end_msg 1
        ;;
    esac
    ;;
  *)
    #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    exit 3
    ;;
esac

:

Once the script is created, there is only one thing left to do:

update-rc.d mumble-ldap defaults

This ensures, that the LDAP authentication is started when the server boots.

Leave a Reply

Your email address will not be published. Required fields are marked *