Wednesday, June 5, 2013

Self Service Password

I decided to allow users on the server to change their passwords, when they want, through a web based tool. I chose LTB's Self Service Password. A simple php tool with lots of neat features like SMS reset, security questions, etc.  I only plan to enable the simple form to reset the password. To install, download the latest .deb file, (0.8 in my case). Next install the dependencies, and restart php5.
sudo apt-get install apache2 php5 php5-ldap php5-mcrypt
sudo service php5-fpm restart
Then the .deb
sudo dpkg -i self-service-password_0.8-1_all.deb
You will need to modify php config file at /usr/share/self-service-password/conf/config.inc.php and make some changes to LDAP.

In my case, the server runs on the localhost.
$ldap_url = "ldap://localhost";
ldap_binddn and ldap_bindpw are made blank ("") to not use admin credentials.
$ldap_binddn = "";
$ldap_bindpw = "";
ldap_base is set to your domain.
$ldap_base = "dc=domain,dc=com";
I'm using simple posix schema for users.
$ldap_filter = "(&(objectClass=posixAccount)($ldap_login_attribute={login}))";   
Next up, modify your nginx config file at /etc/nginx/sites-enabled/default,and add the following sections.
#Self Service Password Section
location /self-service-password {
        alias /usr/share/self-service-password;
        index index.html index.php;
}
location ~ ^/self-service-password/.*\.php$ {
        root /usr/share;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        include /etc/nginx/fastcgi_params;
}
Restart nginx and browse to https://www.domain.com/self-service-password
sudo service nginx restart 

Tuesday, June 4, 2013

Add Authentication to Nginx

The plan is to expose certain web apps behind Basic HTTP Authentication.  This is why the web server is only available via HTTPS, we do not want LDAP user/passwords going over plaintext.  The way we have setup LDAP and PAM earlier, it is very easy to secure subdomains using it. Note: Basic HTTP Authentication has uses no session cookies or persistence, so the user remains logged in until they close their browser. Be warned!

Make sure that you have nginx-extras installed, and not nginx.  Extras includes the PAM module.
sudo apt-get install nginx-extras
The following is taken from this readme.  
To protect everything under /secure you will add the following to the nginx.conf file: This is secure enough for many purposes.
location /secure {
    auth_pam              "Secure Zone";
    auth_pam_service_name "nginx";
}
Note that the module runs as the web server user, so the PAM modules used must be able to authenticate the users without being root; that means that if you want to use the pam_unix.so module to authenticate users you need to let the web server user to read the /etc/shadow file if that does not scare you (on Debian like systems you can add the www-data user to the shadow group).

As an example, to authenticate users against an LDAP server (using the pam_ldap.so module) you will use an /etc/pam.d/nginx like the following:
auth    required pam_ldap.so
account required pam_ldap.so
If you also want to limit the users from LDAP that can authenticate you can use the pam_listfile.so module; to limit who can access resources under /restricted add the following to the nginx.conf file:
location /restricted {
    auth_pam              "Restricted Zone";
    auth_pam_service_name "nginx_restricted";
}
Use the following /etc/pam.d/nginx_restricted file:
auth    required pam_listfile.so onerr=fail item=user \
                 sense=allow file=/etc/nginx/restricted_users
auth    required pam_ldap.so
account required pam_ldap.so
And add the users allowed to authenticate to the /etc/nginx/restricted_users (remember that the web server user has to be able to read this file).

Build your ownCloud

ownCloud is a way to bring your own cloud storage to the internets.  You may want to use this to control your own data, use open standards provided by ownCloud, or just save yourself the monthly subscription costs of the commercial options. The only limitation on storage size is the size of your connected storage, which can actually include other cloud storage services such as Dropbox and Google Drive, in addition to external storage.

Installation is fairly easy with Ubuntu as a repository with packages is available. The following directories are taken from the installation page on ownCloud. Run the following as root
echo 'deb http://download.opensuse.org/repositories/isv:ownCloud:community/xUbuntu_12.04/ /' >> /etc/apt/sources.list.d/owncloud.list
apt-get update
apt-get install owncloud
If you want to add the key to apt-get to avoid a warning
wget http://download.opensuse.org/repositories/isv:ownCloud:community/xUbuntu_12.04/Release.key
apt-get add - < Release.key
Once installed, you need to add ownCloud to Nginx.  You will presumably already setup php5-fpm during the instructions in my LDAP server post.  With ownCloud 5, some slightly more complicated Nginx rules are needed. Again, we are putting ownCloud in a subdomain on the webserver for clean separation of server services. Note: make sure your fastcgi_pass matches the mechanism you are using for FastCGI, either unix socket or tcp socket.  The following was taken from this post. 

#owncloud settings
#Some rewrite rules, more to come later
rewrite ^/owncloud/caldav((/|$).*)$ /owncloud/remote.php/caldav$1 last;
rewrite ^/owncloud/carddav((/|$).*)$ /owncloud/remote.php/carddav$1 last;
rewrite ^/owncloud/webdav((/|$).*)$ /owncloud/remote.php/webdav$1 last;

location ~ ^/owncloud/(data|config|\.ht|db_structure.xml|README) {
    deny all;
}
# Configure the root location with proper rewrite rule
location /owncloud/ {
    rewrite ^/owncloud/.well-known/host-meta /public.php?service=host-meta last;
    rewrite ^/owncloud/.well-known/host-meta.json /public.php?service=host-meta-json last;
    rewrite ^/owncloud/.well-known/carddav /remote.php/carddav/ redirect;
    rewrite ^/owncloud/.well-known/caldav /remote.php/caldav/ redirect;
    rewrite ^/owncloud/apps/calendar/caldav.php /remote.php/caldav/ last;
    rewrite ^/owncloud/apps/contacts/carddav.php /remote.php/carddav/ last;
    rewrite ^/owncloud/apps/([^/]*)/(.*\.(css|php))$ /index.php?app=$1&getfile=$2 last;
    rewrite ^(/owncloud/core/doc[^\/]+/)$ $1/index.html;
    try_files $uri $uri/ index.php;
}
# Configure PHP-FPM stuff
location ~ ^(?<script_name>.+?\.php)(?<path_info>/.*)?$ {
    try_files $script_name = 404;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_param PATH_INFO $path_info;
    fastcgi_param HTTPS on;
    # This one is a little bit tricky, you need to pass all parameters in a single line, separating them with newline (\n)
    fastcgi_param PHP_VALUE "upload_max_filesize = 1024M \n post_max_size = 1024M"; # This finishes the max upload size settings
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # On some systems OC will work without this setting, but it doesn't hurt to leave it here
    include /etc/nginx/fastcgi_params;
}
Make sure to restart nginx as usual, sudo service nginx restart. You should now be able to create a default admin account at your domain.com/owncloud

Next step is to link ownCloud to your Ldap server for authentication. Login with the admin account, click the settings button, and go to "Apps".  Enable the app for "Ldap User and Group Backend". 


Click the settings button, and go to "Admin".  Under the LDAP section, set your LDAP host, your domain, and the user and group attributes. Test the configuration and save.


Again, if you are using ownCloud 4.5 everything should work out of the box as is, and users can login and share files with group members.  In my case, all web users are a member of the group 'webuser'.  However, ownCloud 5.0 requires some additional configuration, or the users are not associated with their groups.  The solution is to add the memberUid attribute to the associated group in ldap-account-manager, manually add the users to this group, then tell ownCloud to use this attribute.

Login to ldap-account-manager and click on "Tree View".  From here, select the group, and click "Add New Attribute".  Select "memberUid".  Add the name of at least one user.  The new attribute should be visible in the group in tree view.  From here, you can manually add members by clicking "Modify Group Members" under memberUid.  You can add the users in a batch, instead of manually typing them out.



Log back in to ownCloud as admin.  Click on the settings button, and go back into "Admin".  In the LDAP section, select the "Advanced" tab. Under "Directory Settings" set "Group-member association" to "memberUid".  Save.  You may need to remove the users so that ownCloud can repopulate the list with the new group association.


Sunday, March 3, 2013

Setting up LDAP Server

LDAP Server Setup

Lightweight Directory Access Protocol or LDAP, is a high-level application protocol for managing directory services in a hierarchical manner.  It's most common use is to manage domain related information, such as an email directory or user information.  In my case, I will use the Unix-related structures for managing users and their system access to my services.  The same user name and login will be used for Subsonic, Owncloud, COPS (e-book server), SSH-SFTP logins, etc.  

To start installing OpenLDAPServer, you can use 2 guides over at Ubuntu, here and here.  The second link actual corrects a few things in the guide, but most of it is unnecessary for an initial LDAP server.  

First off, install the packages.
sudo apt-get install slapd ldap-utils
In my case, my host is already joined to a domain, so I didn't need the next step, but just to make sure, reconfigure slapd to add the ldap domain and reset the password.
sudo dpkg-reconfigure slapd
That's pretty much all you need to get running.  The latest builds of Ubuntu handle the inclusion of various basic schemas, but to verify your ldap is up and running, run the following.
sudo ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config
Initially this command should list 10-15 entries and is a good first check.

User Configuration

A great utility for managing users and settings includes a web app called LAM, or ldap-account-manager.  There is another great write-up regarding using LAM with Nginx here

Install the necessary packages 
sudo apt-get install php5-fpm php5 php5-ldap php-apc php5-gd php-fpdf ldap-account-manager
Normally php5-fpm is configured listening on 127.0.0.1 port 9000.  We're going to change this to a Unix socket, just to clean up the ports a bit and potentially increases performance under load. In general it won't help much, but theoretically removes some of the TCP overhead.
sudo vi /etc/php5/fpm/pool.d/www.conf
Look for 
listen = 127.0.0.1:9000
Change to
listen = /var/run/php5-fpm.sock
Restart the service
sudo service php5-fpm restart
Add the following section to /etc/nginx/sites-enabled/default to create a sub-domain for the account manager, and will point it to the main launch page.
location /ldap-account-manager {
        alias /usr/share/ldap-account-manager;
        index index.html index.php;
}
Add the following section to /etc/nginx/sites-enabled/default to point Nginx to the LAM directory, the php Unix socket, and tweak a couple of fastcgi parameters.
location ~ ^/ldap-account-manager/.*\.php$ {
        root /usr/share;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        include /etc/nginx/fastcgi_params;
}
Restart Nginx
sudo service nginx restart
You should be able to now browse to LAM https://www.domain.com/ldap-account-manager. At this point, I refer you to the ducky-pond.com post for how to initially setup LAM. 

Client Access

First, configure your client config for ldap client apps.  
sudo vi /etc/ldap.conf
Make sure the domain is properly specified
base dc=domain,dc=com
and the uri is correct
uri ldap://127.0.0.1:389
I refer you to this thread for the instructions I used to setup local and ssh logins for your users.  This will automatically create their home directories if they do not exist. There is a correction by a later contributer, which I have included in my quick setup below

First install the packages
sudo apt-get install ldap-utils libpam-ldap libnss-ldap nslcd
Next edit /etc/nsswitch.conf and change the lines for passwd, group, and shadow
passwd: compat ldap
group : compat ldap
shadow: compat ldap
Edit /etc/pam.d/lightdm and add 
session required pam_mkhomedir.so skel=/etc/skel umask=0022
Edit /etc/pam.d/common-session and add
session required pam_mkhomedir.so skel=/etc/skel umask=0022
Apply changes
sudo update-rc.d nslcd enable
Configure lightdm to allow user to specify a username for login
sudo /usr/lib/lightdm/lightdm-set-defaults -m true
And reboot.  If the user logs in locally or via ssh, her home directory will be created automatically.

Saturday, March 2, 2013

Plex Web Proxy

Plex Media Server works excellently by itself as a server, and the various client interfaces they have.  DLNA, Android, Windows, Mac all have excellent clients for this server.  In addition, they provide a web interface which can be served from the same system as the server.  

Plex provides their own account management via their MyPlex website.  You can use these credentials to access other Plex members' libraries who have shared their servers with you.  Since these credentials are needed in addition to my users' usual LDAP solution, I was hoping to put the Plex Web client behind my Nginx proxy and use Basic HTTP Authentication.  I was successful, unfortunately, the Web client doubles as the server management client if the source ip address of the request matches the local system. Unless I want users managing my account, probably not a good idea. 

The second reason I wanted to put Plex Web behind the proxy was so that I could put it inside of a subdomain, like www.domain.com/plexweb.  Unfortunately, Plex does not yet provide a way to provide a different context path behind a proxy.

The third reason to put Plex Web behind the proxy was to secure it with my SSL certificate and https.  This is easily doable, as is proxying to the default Plex Web port of 32400, to keep the request URLs a little cleaner, and just use the same hole in the NAT as regular https.

The first thing that is necessary is to tweak the Nginx configuration to properly proxy all the necessary subdomains used by the Plex Web http API.  In addition, Plex Web HAS to remain at the root domain.  However, I still want to use the root domain my web server frontpage.  The idea is to look for http headers specific to the Plex Web requests, proxy those to the Plex server, and proxy home requests to the subdomain /home, where a simple homemade web page will reside. 

The first step is to properly redirect the root domain, so edit /etc/nginx/sites-enabled/default, add the home section, and change the root location to the following:
location ^~/home {
    root /var/www/home;
}

location ^~ / {
    set $test "true";
    #If the web request contains either of these 2 headers, unset the flag
    if ($http_x_plex_product) {
        set $test "false";
    }
    
    if ($http_x_plex_protocol) {
        set $test "false";
    }

    #if the flag is still set, redirect all requests to /home location
    if ($test = "true") {
        rewrite ^(.*)$   /home$1 last;
    }

    #otherwise, we have a Plex header, redirect to plex
    proxy_pass http://www.domain.com:32400;
    proxy_redirect http:// https://;
}
After a little packet sniffing, I determined the set of subdomains needed by Plex, so that I only have to forward those requests. These may change as Plex updates their API. Add the following sections to  /etc/nginx/sites-enabled/default under the main server section. This could probably be done with a single location and an or'd regex, but from what I read, this may be faster.
#PlexWeb Section
location ^~ /:/ {
   proxy_pass http://www.domain.com:32400/:/;
   proxy_redirect http:// https://;
}
location ^~ /web {
   proxy_pass http://www.domain.com:32400/web;
   proxy_redirect http:// https:/
   proxy_redirect http:// https://;/;
}
location ^~ /system {
   proxy_pass http://www.domain.com:32400/system;
   proxy_redirect http:// https://;
}
location ^~ /library {
   proxy_pass http://www.domain.com:32400/library;
   proxy_redirect http:// https://;
}
location ^~ /servers {
   proxy_pass http://www.domain.com:32400/servers;
   proxy_redirect http:// https://;
}
location ^~ /channels {
   proxy_pass http://www.domain.com:32400/channels;
   proxy_redirect http:// https://;
}
location ^~ /identity {
   proxy_pass http://www.domain.com:32400/identity;
   proxy_redirect http:// https://;
}
location ^~ /photo {
   proxy_pass http://www.domain.com:32400/photo;
   proxy_redirect http:// https://;
}
location ^~ /pms {
   proxy_pass http://www.domain.com:32400/pms;
   proxy_redirect http:// https://;
}
location ^~ /video {
   proxy_pass http://www.domain.com:32400/video;
   proxy_redirect http:// https://;
}
This is unfortunately an incomplete solution.  The protocol that Plex uses over location /:/ actually uses WebSockets.  As such, the above solution to tunnel/proxy Plex kinda works, but the client keeps thinking it's disconnected.  Not sure what effect this has.  It will be necessary to use the feature just made available last month to version 1.3.13 of Nginx. Upgrading to this development version currently breaks my other proxying (subsonic, ldap-manager), so for now, I am disabling Plex proxying until they work out the kinks. However, if Plex proxy is all you need, just change the /:/ to 
location ^~ /:/ {
    proxy_pass http://www.domain.com:32400/:/;
    proxy_redirect http:// https://;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Subsonic Proxy

Today's goal is to update Subsonic and Nginx so that all requests for Subsonic come through Nginx.  The reason for this is two-fold.
  1. I can use the same port (ssl 443) and URL (www.domain.com) for all my server apps.  Thus I don't need to poke a hole in my NAT to forward new ports, and users don't have to remember special ports.
  2. I can use the same SSL certificate for all my server apps, and it is a officially signed certificate, unlike the self-signed that comes with subsonic.
So, first step is to configure Subsonic.  I know that my Subsonic is going to be under the subdomain https://www.domain.com/subsonic, so I need to specify the context-path variable in the configuration.  Also, I still need to run Subsonic on a different port, I will just have Nginx redirect requests to this port.  Lastly, I will increase the max-memory available to Subsonic a bit to have a few more resources. To start, open the startup script for Subsonic
sudo vi /etc/default/subsonic
and change the args to
SUBSONIC_ARGS="--context-path=/subsonic --port=8080 --https-port=0 --max-memory=300"
Finally, for security reasons, change the user for Subsonic from root to www-data, the default user for Nginx. Make sure the permissions on your media files are set to allow this user.
SUBSONIC_USER=www-data
Next step is to configure Nginx.  Open the config
sudo vi /etc/nginx/sites-enabled/default
Then add the following section to the server section for port 443. We need to fix up some headers, and make sure that https is properly redirected.
location ^~ /subsonic/ {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Host $http_host;
        proxy_max_temp_file_size 0;
        proxy_pass http://localhost:8080;
        proxy_redirect http:// https://;
}
Then just restart both services, and you should be able to access Subsonic via http://www.domain.com/subsonic
sudo service restart nginx
sudo service restart subsonic

Fail2ban

Ok, maybe it's paranoia because of what I see at my job.....or maybe it's all the attempted logins I have seen in my authentication log, but it's time to secure my system....at least a little bit.  Primarily I'm considered with what I see in /var/log/auth.log Many repeated (failed) attempts to login to my ssh daemon from IP addresses not related to myself.  Probably some script kiddies or something, but the last thing I want to do is open myself to brute force attacks, or denial-of-service.  

After some research I settled on a software called fail2ban. Basically, it monitors various system logs, and after a number of failed accesses from a certain user/ip/whatever it bans the IP address is associated with that access by making a rule in iptables.  Similar to denyhosts, fail2ban will work on many different services in addition to ssh, which is perfect for when I get my web authentication and LDAP server up and running.   There are pretty good guides already out there, but this is specific to Ubuntu 12.04 and my server. 

To install
sudo apt-get install fail2ban
Whew, with that out of the way you can modify the config file.
sudo vi /etc/fail2ban/jail.conf
Pretty straightforward, there are a couple particulars to Ubuntu, and myself.  First off, I 
think the 10 minute ban-time is a little short, so I bumped it to 60 minutes.
bantime  = 3600
Apparently Debian has some issues with python-gamin (not sure if this is true with 12.04, but what the hell) so set the following
backend = polling
Restart fail2ban
sudo service restart fail2ban 
And that's it!  By default, ssh is enabled, and checks /var/log/auth.log.  However, I did notice an issue while testing.  rsyslog is the service responsible authentication logging.  Upon quickly repeated attempts to access the service, it may only print 1 message for multiple logins and just says something like "Previous message repeated 3 times".  As such, fail2ban is under-counting the number of accesses.  To fix this, you need to change the rsyslog.conf.
sudo vi /etc/rsyslog.conf
change the value RepeatedMsgReduction to 
RepeatedMsgReduction = off
 And restart the logger
sudo service rsyslog restart
To check the banning, try logging in from another system, over 3 times.  Then do
sudo iptables -L 
You should see a rule for iptables-ssh in the INPUT chain.
Chain INPUT (policy ACCEPT)
target        prot opt source               destination
fail2ban-ssh  tcp  --  anywhere             anywhere             multiport dports ssh
And fail2ban-ssh section with 1 reference.
Chain fail2ban-ssh (1 references)
target     prot opt source               destination
DROP       all  --  192.168.100.100      anywhere
RETURN     all  --  anywhere             anywhere