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. .. _unprotected_mode: Unprotected mode ================ there is no user authentication for the UI and access to the API is also unprotected. .. _protected_mode: 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": .. code-block:: none ui { authentication { type = internal } } Protected mode requires UI backend server to be configured to run using `https` protocol. See :ref:`ssl_config` 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 :ref:`access_tokens` 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`. 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`: .. code-block:: none 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: .. code-block:: none # : [, ...] test1: MD5:098f6bcd4621d373cade4e832627b4f6,user Here `user` is the default role name. See :ref:`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: .. code-block:: none $ /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. Using external LDAP server -------------------------- To do this, put the following in the file `/opt/netspyglass/home/etc/login.conf`: .. code-block:: none nsg { org.eclipse.jetty.jaas.spi.LdapLoginModule required 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"; }; Actual values of most parameters depend on your LDAP schema. This example was tested against OpenLDAP server. - 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`): #. 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` #. then it searches for the user in `baseDn` ( ``cn=users,dc=lab,dc=happygears,dc=net`` ) with search filter ``(&(objectClass=inetOrgPerson)(uid=vadim))`` #. 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 #. 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. #. 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 :ref:`role_based_authorization`. To connect to the LDAP server via SSL, add parameter `useLdaps="true"` and change the port to `636`: .. code-block:: none 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 .. code-block:: bash openssl s_client -connect ldat_server_host:636 ldat_server_host.cert *gnutls-cli*: To save certificate using `gnutls-cli`, use .. code-block:: bash 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`): .. code-block:: bash 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: .. code-block:: bash keytool -keystore /opt/netspyglass/home/etc/trusted.jks -list 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: .. code-block:: none 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. .. _role_based_authorization: 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: .. code-block:: none # : [, ...] 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`: .. code-block:: none 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 :ref:`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 :ref:`logged_in_display` 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. .. 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. 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. .. _access_tokens: 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: .. code-block:: none /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`: .. code-block:: none # 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: .. code-block:: none api.accessTokens.api = "YourOwnSecretToken" or, if you have multiple tokens: .. code-block:: none api { accessTokens { api = "YourOwnSecretToken" test_access_1 = abcdefgh, another_script = 5a105e8b9d40e1329780d62ea2265d8a, script5 = "token$3167 % & with_special_chars or spaces" } } .. _rev_proxy_mode: 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`. 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: .. code-block:: none 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.: .. code-block:: none 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. .. _logged_in_display: 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: .. image:: images/authenticated_user_screenshot.png Example: using Apache web server as proxy ----------------------------------------- To use Apache as authenticating proxy in front of NetSpyGlass, configure it as follows: .. code-block:: none ServerAdmin webmaster@localhost Order deny,allow Allow from all Deny from all AuthType Basic AuthName "Password Required" AuthUserFile password.file Require valid-user 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 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". 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.