2.6. User Authentication

NetSpyGlass has the following modes of operation:

  • “unprotected”
  • “protected”
  • “reverseProxy”

The mode of operation is controlled by the configuration parameter ui.authentication.type that can have value “none”, “reverseProxy” or “internal”. The default setting of this parameter is none, which turns authentication off and makes the system run in unprotected mode. Setting it to internal turns on authentication internal using internal user accounts and setting it to reverseProxy activates support for reverse proxy with authentication.

2.6.1. Unprotected mode

there is no user authentication for the UI and access to the API is also unprotected.

2.6.2. Protected mode

In protected mode the system requires user authentication to access all resources (currently using internal passwords or external LDAP server). API access requires either valid session or access token.

Protected mode is activated when configuration parameter ui.authentication.type is set to “internal”:

ui {
        authentication {
        type = internal
    }
}

Protected mode requires UI backend server to be configured to run using https protocol. See Running NetSpyGlass via encrypted HTTP connection for more details on how to do this.

API access is allowed after valid session has been established (we are using cookie NSGSESSION). Alternatively, API access is allowed when the query provides access_token, this is intended for third-party scripts and does not require valid session (cookie NSGSESSION is not used). Access token can be passed either as query parameter access_token or using HTTP header with name X-NSG-Auth-API-Token. See Using access tokens for script access to API below.

User logins are supported by Java JAAS framework that uses configuration file login.conf to determine the method it should use for authentication. The file is located in the directory /opt/netspyglass/home/etc/. At this time the only supported methods of authentication are through local accounts or LDAP.

Note

Files used by the authentication framework are located in directory etc inside of the server’s home directory, which is defined by the parameter home in the config file nw2.conf. If you installed NetSpyGlass using distributed rpm or deb package, it is /opt/netspyglass/home/etc.

2.6.2.1. Using locally stored user names and passwords

Administrator can configure NetSpyGlass to authenticate users using local files. In this case, user names and hashed passwords are stored locally. To use this method, put the following into the file /opt/netspyglass/home/etc/login.conf:

nsg {
        org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
        debug="true"
        file="/opt/netspyglass/home/etc/users.properties";
};

Distribution package creates this file for you automatically. You do not need to modify this file if you plan to use local user accounts for authentication. User accounts are stored in the file /opt/netspyglass/home/etc/users.properties which has simple format:

#  <username>: <password>[,<rolename> ...]
test1: MD5:098f6bcd4621d373cade4e832627b4f6,user

Here user is the default role name. See Role Based Authorization. for more information.

You can store the password in plain text, obfuscated format or as MD5 hash. To generate passwords in obfuscated or md5-hash format, use script /opt/netspyglass/current/bin/nsgpasswd.sh that comes with NetSpyGlass:

$ /opt/netspyglass/current/bin/nsgpasswd.sh vadim test
test
OBF:1z0f1vu91vv11z0f
MD5:098f6bcd4621d373cade4e832627b4f6
CRYPT:va/5VIyG0bPak

Just copy and paste generated obfuscated password or md5 hash to the file users.properties as shown in an example of user account “test1” above. You do not need to restart NetSpyGlass when you add or modify user accounts.

2.6.2.2. Using external LDAP server (OpenLDAP)

To do this, put the following in the file /opt/netspyglass/home/etc/login.conf (This example was tested against OpenLDAP server):

nsg {
       org.eclipse.jetty.jaas.spi.LdapLoginModule required
       debug="true"
       contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
           hostname="ldap_server_host_name"
           port="389"
           bindDn="uid=ldap_user,cn=users,dc=lab,dc=happygears,dc=net"
           bindPassword="XXXXXXXXX"
           authenticationMethod="simple"
           forceBindingLogin="true"
           userBaseDn="cn=users,dc=lab,dc=happygears,dc=net"
           userRdnAttribute="uid"
           userIdAttribute="uid"
           userPasswordAttribute="userPassword"
           userObjectClass="inetOrgPerson"
           roleBaseDn="cn=groups,dc=lab,dc=happygears,dc=net"
           roleNameAttribute="cn"
           roleMemberAttribute="member"
           roleObjectClass="posixGroup";
};

Actual values of most parameters depend on your LDAP schema.

  • hostname - the name of your server
  • port - the port number, 389 is the default
  • forceBindingLogin - this setting will cause the user authenticating to bind to the directory server rather than a simple password comparison
  • bindDn - the DN of the user that will be used to bind to the server
  • bindPassword - password of the user bindDn
  • userIdAttribute - where to find in LDAP the username you type into the login dialog
  • userBaseDn - all users that should be able to login must live under this LDAP tree
  • roleBaseDn - the tree that will be searched for role groups.
  • roleObjectClass - groups must have this object class
  • roleNameAttribute - This is used to construct the value of the member attribute to determine if the user is a member the default user role group.

Here is what happens when I try to log in, assuming all steps are successful (my user name is vadim):

  1. NetSpyGlass binds to the LDAP server as user with name provided with the parameter bindDn ( uid=ldap_user,cn=users,dc=lab,dc=happygears,dc=net ) using password provided with parameter bindPassword
  2. then it searches for the user in baseDn ( cn=users,dc=lab,dc=happygears,dc=net ) with search filter (&(objectClass=inetOrgPerson)(uid=vadim))
  3. it disconnects and binds again, this time as user uid=vadim,cn=users,dc=lab,dc=happygears,dc=net using password I have typed into the login form
  4. it tries to get a list of all groups I am a member of using search query for cn=groups,dc=lab,dc=happygears,dc=net with filter (&(objectClass=posixGroup)(member=uid=vadim,cn=users,dc=lab,dc=happygears,dc=net)). Note that the filter has been composed using values of parameters roleBaseDn, roleMemberAttribute, userRdnAttribute and my user name.
  5. this process succeeds if I am a member of a group with the name that comes from the value of the configuration parameter requiredRole. Default name of this group is user. Tthis step is used for authotization: a user must be a member of a required group to be allowed to log in even if they provide valid user name and password. See Role Based Authorization.

To connect to the LDAP server via SSL, add parameter useLdaps=”true” and change the port to 636:

nsg {
       org.eclipse.jetty.jaas.spi.LdapLoginModule required
       debug="true"
       contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
           hostname="synas1"
           port="636"
           useLdaps="true"

Next, you need to download and import the certificate used by the LDAP server. First, download the certificate and save it to a file. This can be done using openssl or gnutls-cli utilities.

openssl:

To print the certificate, use

openssl s_client -connect ldat_server_host:636 </dev/null

The certificate begins with the line —–BEGIN CERTIFICATE—– and ends with the line —–END CERTIFICATE—– (inclusive). You can save it to a file automatically using

openssl s_client -connect ldat_server_host:636 </dev/null | \
    sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-.*/p' > ldat_server_host.cert

gnutls-cli:

To save certificate using gnutls-cli, use

gnutls-cli --port 636 --save-cert ldat_server_host.cert ldat_server_host < /dev/null

Now we can add this certificate to the store of trusted certificates used by NetSpyGlass server (it expects to find it in the file /opt/netspyglass/home/etc/trusted.jks):

keytool -keystore /opt/netspyglass/home/etc/trusted.jks -import -alias Ldap_Server -file ldat_server_host.cert

keytool is going to ask you for a password when it creates or modifies store trusted.jks, but NetSpyGlass server can access certificates even if it does not have this password.

The following command can be used to list all certificates in the keystore:

keytool -keystore /opt/netspyglass/home/etc/trusted.jks -list

2.6.2.3. Using external LDAP server (Active Directory)

Since AD LDAP schema is usually different from that used with OpenLDAP-based servers, configuration placed in the the file login.conf should reflect this. The following example demonstrates LDAP configuration that has been tested against Active Directory server. Here is the summary of attributes that have different values compared to the OpenLDAP based configuration:

  • SSL is always turned on (useLdaps)
  • bindDn has value cn=ldap_user,cn=Users,dc=corp,dc=acme,dc=com
  • userRdnAttribute has value cn
  • userObjectClass has value organizationalPerson
  • roleBaseDn has value cn=users,dc=corp,dc=acme,dc=com
  • roleObjectClass has value group

Notice also changes in bindDn and userBaseDn. These actually follow the same schema, except dc names are different. These differ often in different organizations anyway.

nsg {
       org.eclipse.jetty.jaas.spi.LdapLoginModule required
       debug="true"
       contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
           hostname="domain-controller.acme.com"
           port="636"
           useLdaps="true"
           bindDn="cn=ldap_user,cn=Users,dc=corp,dc=acme,dc=com"
           bindPassword="XXXXXXXXXXX"
           authenticationMethod="simple"
           forceBindingLogin="true"
           userBaseDn="cn=users,dc=corp,dc=acme,dc=com"
           userRdnAttribute="cn"
           userIdAttribute="uid"
           userPasswordAttribute="userPassword"
           userObjectClass="organizationalPerson"
           roleBaseDn="cn=users,dc=corp,dc=acme,dc=com"
           roleNameAttribute="cn"
           roleMemberAttribute="member"
           roleObjectClass="group";
};

2.6.2.4. Using Combination of Authentication Modules

You can combine external LDAP authentication with local accounts by adding two LoginModule configurations to the same realm in the login.conf file. Here is how it looks like:

nsg {
       org.eclipse.jetty.jaas.spi.LdapLoginModule sufficient
       debug="true"
       contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
           hostname="synas1"
           port="389"
           bindDn="uid=ldap_user,cn=users,dc=lab,dc=happygears,dc=net"
           bindPassword="XXXXXXXXX"
           authenticationMethod="simple"
           forceBindingLogin="true"
           userBaseDn="cn=users,dc=lab,dc=happygears,dc=net"
           userRdnAttribute="uid"
           userIdAttribute="uid"
           userPasswordAttribute="userPassword"
           userObjectClass="inetOrgPerson"
           roleBaseDn="cn=groups,dc=lab,dc=happygears,dc=net"
           roleNameAttribute="cn"
           roleMemberAttribute="member"
           roleObjectClass="posixGroup";

       org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
           debug="true"
           file="/var/tmp/nw2/etc/users.properties";

};

Notice that the first parameter to the LdapLoginModule is sufficient rather than required. This means the server will authenticate a user if it can do this using LDAP server alone. However, if the user could not be authenticated with LDAP, then the server will try the next login module which is PropertyFileLoginModule. If it finds the record in the file /var/tmp/nw2/etc/users.properties that matches the user, this user will be allowed to log in. Given the order of the modules in this configuration, if the same user name exists in LDAP and file /var/tmp/nw2/etc/users.properties, LDAP will be consulted first and the user will log in if the password they have provided matches what is in LDAP. However if the password does not match LDAP record but matches record in the file users.properties, this user can still log in.

2.6.2.5. Role Based Authorization

All users that need to log in to NetSpyGlass must have a role. There are two roles at this time, one has default name user and the other role is admin. The non-admin role name is user by default but you can change it using configuration parameter ui.authentication.requiredRole.

The role is used for authorization: even if the password provided by the user matches, the user can only use NetSpyGlass if it has corresponding role. At a minimum, default role user is recuired.

In case of LDAP authentication, a group with the name matching default role name must exist and all users who log in to NetSpyGlass must be its members.

If you use file users.properties, then all user records should end with the default role name after the comma, like so:

#  <username>: <password>[,<rolename> ...]
test1: MD5:098f6bcd4621d373cade4e832627b4f6,user

NetSpyGlass provides simple RBAC (Role Based Access Control) system where administrator can assign roles to user accounts using configuration block ui.authentication.roles. Currently this are two roles, admin and user. To make a user an admin, add corresponding user name to the list ui.authentication.roles.admin:

ui {
   authentication {

       roles {
         admin: [ "user@company.com", another_user ]
       }
   }
}

All users who are not explicitly assigned role admin are considered to only have one role user.

User names should be added exactly as they appear in the etc/users.properties file (if parameter authentication.type has value internal, see Protected mode). To check what you should add to the configuration statement, look at the user name that appears in the upper right corner of the UI pages where it says “Logged in:”. See UI indication of successful authentication This works with both internal and reverse proxy based authentication methods.

If authentication is turned off (i.e. the server works in the unprotected mode), then all users are given admin level access.

The default role name user can be changed using configuration parameter ui.authentication.requiredRole. You may want to change it if you use LDAP server for authentication. LDAP authentication module requires that a group with the name matching default role must exist and all users who should be able to log in to NetSpyGlass must be its members. If you do not have group with name user and do not want to create it, then you can change the name of the default role to the name of a group that you already have.

There are few functions in the UI that require role admin, such as ability to start network discovery by clicking a button in the UI and ability to run reconfiguration actions from the UI. If logged in user is an admin, corresponding UI controls are visible and clickable. If logged in user is not an admin, these controls are hidden. If authentication is turned off, these controls is always enabled.

You can assign role “admin” to all users by adding “*” to the list of user names in the parameter roles:

ui {
   authentication {

       roles {
         admin: [ "*" ]
       }
   }
}

Note

Role admin is defined in NetSpyGlass configuration file rather than as LDAP group or suffix in the users.properties file because we needed to make it also work with reverse proxy based authentication. In this case, NetSpyGlass relies on the HTTP header added by the proxy, which can reliably provide only one piece of information: user name. Since there is no LDAP call or file users.properties to consult to determine this user’s roles, the only practical choice was to put roles into NetSpyGlass configuration. We did not want to have different ways to assign user roles that would depend on the authentication mechanism.

2.6.2.6. Debugging User Authentication and Authorization Problems

If a user can not log in, even though they say they have provided correct user name and password, look in the server log. The server makes several log records whenever a user tries to log in. Successful log in attempt looks like this (you may need to scroll horizontally because these lines are long):

2017-07-23 12:31:54,858 INFO  qtp1987485775-151 [o.e.j.j.s.LdapLoginModule]: Searching for users with filter: '(&(objectClass={0})({1}={2}))' from base dn: cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:31:54,867 INFO  qtp1987485775-151 [o.e.j.j.s.LdapLoginModule]: Found user?: true
2017-07-23 12:31:54,867 INFO  qtp1987485775-151 [o.e.j.j.s.LdapLoginModule]: Attempting authentication: uid=vadim,cn=users,dc=lab,dc=happygears,dc=net
  • First, server recorded its attempt to find this user in LDAP directory. The result was a success: “Found user?: true”
  • then it attempted to authenticate. If this is the last record, then the attempt was successful.

Here is what happens when user name is invalid:

2017-07-23 12:35:40,126 INFO  qtp1987485775-172 [o.e.j.j.s.LdapLoginModule]: Searching for users with filter: '(&(objectClass={0})({1}={2}))' from base dn: cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:35:40,129 INFO  qtp1987485775-172 [o.e.j.j.s.LdapLoginModule]: Found user?: false
2017-07-23 12:35:40,131 WARN  qtp1987485775-172 [ o.e.j.j.JAASLoginService]:
javax.security.auth.login.FailedLoginException

the relevant part for us here is “Found user?: false”.

Here is another attempt, this time with valid user name but incorrect password:

2017-07-23 12:34:00,814 INFO  qtp1987485775-149 [o.e.j.j.s.LdapLoginModule]: Searching for users with filter: '(&(objectClass={0})({1}={2}))' from base dn: cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:34:00,820 INFO  qtp1987485775-149 [o.e.j.j.s.LdapLoginModule]: Found user?: true
2017-07-23 12:34:00,820 INFO  qtp1987485775-149 [o.e.j.j.s.LdapLoginModule]: Attempting authentication: uid=vadim,cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:34:01,228 WARN  qtp1987485775-149 [ o.e.j.j.JAASLoginService]:
javax.security.auth.login.FailedLoginException

the last line is followed by Java stack trace which we can ignore. What is important is that it logged FailedLoginException. This usually means incorrect password.

Even if user provides valid password that matches LDAP record, they may not be able to log in if they are not a member of the required group (that is, do not have required role). Unfortunately the UI does not provide detailed diagnostic for this situation, it just redirects back to the login page. Look in the server log, if authorization is indeed the problem, then there should be records that look like this:

2017-07-23 12:25:40,050 INFO  qtp1727971245-149  [o.e.j.j.s.LdapLoginModule]: Searching for users with filter: '(&(objectClass={0})({1}={2}))' from base dn: cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:25:40,059 INFO  qtp1727971245-149  [o.e.j.j.s.LdapLoginModule]: Found user?: true
2017-07-23 12:25:40,060 INFO  qtp1727971245-149  [o.e.j.j.s.LdapLoginModule]: Attempting authentication: uid=vadim,cn=users,dc=lab,dc=happygears,dc=net
2017-07-23 12:25:40,139 WARN  qtp1727971245-149  [.a.h.NsgApiv2ErrorHandler]: User 'vadim' is unauthorized (check required role group)

The line User ‘vadim’ is unauthorized (check required role group) tells us the problem is with user’s role.

2.6.3. Using access tokens for script access to API

This is only required when the server is running in protected mode.

Normally, UI running in the browser is allowed to access API if it presents valid session cookie NSGSESSION. Third party scripts, such as Nagios plugin or other scripts using our JSON API, do not have the session and can not use this mechanism to access the API. Instead, these scripts must provide valid access token as a request header X-NSG-Auth-API-Token or query parameter access_token. Examples:

/v2/query/net/1/data/?access_token=5a105e8b9d40e1329780d62ea2265d8a
curl -H "X-NSG-Auth-API-Token: 5a105e8b9d40e1329780d62ea2265d8a" https://server:9100/v2/query/net/1/data/

Attempts to access API without access token or with invalid one end with response 401 “Unauthorized response from the server”.

Access tokens are defined in the configuration file, parameter api.accessTokens:

# here you can add access tokens for third-party API access
api {
    accessTokens {
        test_access_1 = abcdefgh,
        another_script = 5a105e8b9d40e1329780d62ea2265d8a,
        script5 = "token$3167 % & with_special_chars or spaces"
    }

Each record consists of the name and token string. These form “user_name = token_string” pairs, that is, the server treats tokens as pseudo-users that can not log in through the UI but can use the token to authenticate. You can assign “admin” access level to the token by adding its name to the list in the parameter ui.authentication.roles.

Token string can be any text that conforms to the rules of the configuration file syntax (see document config.md).

Tokens that contain special characters and white space must be url-encoded when used with query parameter access_token.

Default configuration includes access token called api, it is used for the API calls between NetSpyGlass servers when NetSpyGlass runs in the cluster configuration. You can change this token to make it something different from the default by adding the following to the nw2.conf file in all cluster members:

api.accessTokens.api = "YourOwnSecretToken"

or, if you have multiple tokens:

api {
    accessTokens {
        api = "YourOwnSecretToken"
        test_access_1 = abcdefgh,
        another_script = 5a105e8b9d40e1329780d62ea2265d8a,
        script5 = "token$3167 % & with_special_chars or spaces"
    }
}

2.6.4. Running NetSpyGlass behind reverse proxy with authentication

NetSpyGlass can work behind reverse proxy with authentication and can use HTTP headers inserted by the proxy to distinguish logged in users who use the UI to improve their experience. In particular, NetSpyGlass can make map layout specific to the user so that changes in map layout made by one user are not visible to others.

There is no authentication in this mode because we expect the proxy to perform it. API access is also unprotected, however the server expects each request to have HTTP header configured via parameter ui.authentication.reverseProxyAuthenticationHeader. This parameter defines regular expression used to match the header line to extract authenticated user name. This assumes the server works behind authenticating reverse proxy that inserts this HTTP header.

This mode is activated when configuration parameter ui.authentication.type is set to reversePoxy.

2.6.4.1. How to configure NetSpyGlass to parse HTTP header to extact user name

This is done by adding configuration parameter ui.authentication.reverseProxyAuthenticationHeader, its value is header name and regular expression to match its value. Example:

ui {
    authentication {
        # authenticaton is done by reverse proxy in front of NetSpyGlass
        type = reverseProxy
        # proxy injects HTTP header that includes authenticated user's name.
        # Parameter reverseProxyAuthenticationHeader is a regular expression
        # with a group match that should match user name
        reverseProxyAuthenticationHeader = "X-Forwarded-User: ([^ ]+)"
    }
}

Here NetSpyGlass expects HTTP header X-Forward-User with user name as a value, e.g.:

X-Forwarded-User: test1

Configured this way, NetSpyGlass is going to make map layout data specific to each user, so each user can have their own map layout.

2.6.5. UI indication of successful authentication

When authentication is turned on and user has successfully authenticated, the UI displays user name with “Loggen in:” prompt in the upper right corner of the page. This works for both intrnal and reverse proxy authentication and looks like this:

_images/authenticated_user_screenshot.png

2.6.5.1. Example: using Apache web server as proxy

To use Apache as authenticating proxy in front of NetSpyGlass, configure it as follows:

<VirtualHost *:9101>
        ServerAdmin webmaster@localhost

        <Proxy *>
                Order deny,allow
                Allow from all
                Deny from all

                AuthType Basic
                AuthName "Password Required"
                AuthUserFile password.file
                Require valid-user

        </Proxy>

        ProxyRequests On
        ProxyVia On
        ProxyPreserveHost On
        ProxyPass / http://127.0.0.1:9100/
        ProxyPassReverse / http://127.0.0.1:9100/

        RewriteEngine On
        RewriteCond %{LA-U:REMOTE_USER} (.+)
        RewriteRule . - [E=RU:%1]
        RequestHeader add X-Forwarded-User %{RU}e

        ErrorLog /var/log/apache2/nw2error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog /var/log/apache2/nw2access.log combined

</VirtualHost>

In this example we assume that Apache runs on the same server with NetSpyGlass, NetSpyGlass UI backend listens on port 9100 and Apache listens on port 9101. Apache will inject HTTP header “X-Forwarded-User”.

2.6.5.2. How to limit access to NetSpyGlass running behind the proxy

If NetSpyGlass runs behind the proxy for security reasons, it should be configured to listen on the address or port that is not accessible to the users directly to make sure they don’t circumvent the protection by connecting directly to NetSpyGlass instead of going through proxy.

To do this, use address that is only accessible by the proxy in ui.url configuration parameter. If proxy runs on the same host, then loopback address 127.0.0.1 is probably good choice. If proxy runs on a different machine, firewall rules may provide needed protection.