• Home
  • The Adventures
  • CODE (GitHub)
\\server-adventures
Miles Gratz |  milesgratz@gmail.com

Installing PWM (Open Source Password Self Service) in 2016

8/14/2016

8 Comments

 
[ The Overview  ]

This blog was written to provide a step-by-step tutorial for IT administrators on deploying a web-based password reset portal following best practices. The guide was written for a broad audience but will make reasonable assumptions on your knowledge of certain technologies. Feel free to ask questions in the comment section!
Picture
[ The Backstory ]

Three years ago, I was working at a school district with ~3000 users that were suddenly outnumbering our Windows computers with their "BYOD" cellphones, tablets, laptops, etc. We were also purchasing less and less traditional computing devices. We were moving to Google Chromebooks and the ability to press "Ctrl Alt Del" to initiate a password reset wasn't realistic anymore. We had moved off Outlook in favor of Gmail and did not have access to leveraging OWA (Outlook Web Access) as a method of resetting passwords.

When I was doing some research about password self service, I stumbled upon an open source "PWM" project hosted on Google Code. It seemed a little over my head (I had never setup a Linux server before) but I was obsessed with Google and figured my boss would be more likely to entertain my ideas if I had a working proof of concept. It took me a few tries but eventually I wrote one of my first well-received blog posts. It was a detailed guide to installing PWM v1.7.0 on Ubuntu 14.04 LTS. 

A lot has changed since I wrote that blog post. The PWM source code is now a different major revision (v1.8) hosted on GitHub and Ubuntu 14.04 LTS is not the current LTS build. Even worse yet, my original guide had some wacky suggestions like using port 8080/8443 and self-signed certificates (to be fair... LetsEncrypt certificates weren't available until 2015.) It occurred to me that my aging PWM tutorial was no longer relevant. 
[ Considerations ]

Hosting: You will need to make a decision about whether or not you want to host the web server on-premises. If you decide to go with a "cloud" service (e.g. Google Cloud, Amazon AWS, DigitalOcean, etc.) or in a colocation, you will still need to communicate with your LDAP server. If your LDAP server is only accessible on-premises, then you will likely need to tunnel the traffic through VPN. When I was working at the school district, it made sense to host on-premises since we had the existing server infrastructure, existing static IP addresses, and an LDAP server that was not externally accessible. 

Risk vs. Reward: Hosting an open source password reset portal saves you a bunch of money because it's free, right? Not necessarily. It may be worth the money to offload the burden of security to a paid product (e.g. ServiceNow, ManageEngine, Azure AD Password Reset.) By designing your own custom solution, you also have a responsibility to routinely patch and secure it. The estimated cost of that responsibility should be taken into consideration. 

[ Prerequisites ]
  • Fresh installation of Ubuntu 16.04 LTS
  • Ability to communicate with existing LDAP server
  • Established an external IP address / DNS record for your web server 

I will be demonstrating these prerequisites using an example server. In my case, I created a new Ubuntu LTS 16.04.1 x64 DigitalOcean "droplet". Upon creation, it was automatically assigned a static public IPv4 address (​138.68.2.0). To create a new DNS record, I went to my domain registrar website (Domain.com) and added a new A record called "demo" that points to the IP address of 138.68.2.0. It will take some time for DNS to propagate, but eventually I should be able to confirm that demo.milesgratz.com resolves to 138.68.2.0.
nslookup demo.milesgratz.com
Server:  192.168.1.1
Address:  192.168.1.1#53

Non-authoritative answer:
Name:    demo.milesgratz.com
Address:  138.68.2.0
Picture
[ Components for PWM ]

  • Tomcat8  (Java web server framework for pwm application)
  • LetsEncrypt  (Free HTTPS certificate for Apache2)
  • Apache2  (SSL Proxy between client and Tomcat8)
  • ufw  (Firewall to secure web server)
  • MySQL Server  (Database for pwm application)
​
​​​Let's update our package lists and download some necessary utilities first:
​sudo apt-get update
sudo apt-get install git unzip
[ Installing Tomcat8 ]

A better tutorial of this section is available here: Installing Apache Tomcat 8 on Ubuntu 16.04
Let's start off by installing the Java Development Kit. 
sudo apt-get install default-jdk
​Now we need to create a user and group for the Tomcat service:
sudo groupadd tomcat
sudo useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat
At time of writing, the current stable build is Apache Tomcat 8.5.4 (available on http://tomcat.apache.org/) but it is recommended you download the latest core build available on their website.
cd /tmp
wget http://www-us.apache.org/dist/tomcat/tomcat-8/v8.5.4/bin/apache-tomcat-8.5.4.tar.gz
sudo mkdir /opt/tomcat
sudo tar xzvf apache-tomcat*.tar.gz -C /opt/tomcat --strip-components=1

​
The next step is to grant permissions to the Tomcat user/group: 
cd /opt/tomcat
sudo chown -R tomcat *
sudo chgrp -R tomcat conf/
​sudo chmod g+rwx conf/
sudo chmod g+r conf/*
We are now ready to configure the Tomcat service. First we determine the path to our Java Development Kit and then create a new systemd file in /etc/systemd/system. 
sudo update-java-alternatives -l
java-1.8.0-openjdk-amd64       1081       /usr/lib/jvm/java-1.8.0-openjdk-amd64
​sudo vim /etc/systemd/system/tomcat.service
Make sure the JAVA_HOME variable is correct. You can also adjust the amount of memory available to Java. 
​[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

Environment=JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

User=tomcat
Group=tomcat
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target
Alright, we are ready to start our Tomcat service. If all goes well, we will enable it permanently. Assuming your Ubuntu server allows port 8080 inbound, you should be able to access the Tomcat welcome page in your web browser: 

​http://your_server_IP_address:8080
sudo systemctl daemon-reload
sudo systemctl start tomcat
​​sudo systemctl enable tomcat
Picture
sudo systemctl status tomcat
​[ Installing LetsEncrypt ]

LetsEncrypt is a new Certificate Authority that provides free HTTPS certificates by using their client that runs locally on the web server and uses a combination of safety mechanisms (e.g. DNS resolution) to confirm that you own the domain that you are requesting a certificate for. As I mentioned earlier in my guide, this option did not exist prior to 2015. If you already have a wildcard certificate for your domain, you could use that instead. 
sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
​sudo vim /opt/letsencrypt/config.ini
Let's create a config file for our LetsEncrypt certificate creation and renewal. You will need to adjust the email and domain name accordingly.
​​authenticator = standalone
renew-by-default
agree-tos
email = milesgratz@gmail.com
domain = demo.milesgratz.com
We should be ready to generate our first certificate! If you are having issues with generating the certificate, make sure that the domain name you are providing (e.g. demo.milesgratz.com) resolves properly to your server. 
​sudo /opt/letsencrypt/letsencrypt-auto --config /opt/letsencrypt/config.ini certonly
The final step for LetsEncrypt is adjusting our crontab to auto-renew the certificate once a month. 
crontab -e
​​0 0 1 * * systemctl stop apache2 && /opt/letsencrypt/letsencrypt-auto --config /opt/letsencrypt/config.ini certonly && systemctl start apache2
[ Installing Apache2 ]

You might be wondering why you need both Apache2 and Tomcat8. By default, Tomcat listens on alternate ports (8080 and 8443) and was not designed to run natively on privileged ports like 80 and 443. Instead, we will be using Apache2 to proxy a secure connection between the client and Tomcat. Additionally, we will configure Apache2 to redirect all unencrypted HTTP requests to HTTPS. I have included a mediocre diagram of that concept in action. 
Picture
First, we will install the Apache2 package and create our new VirtualHost configuration. 
sudo apt-get install apache2
​sudo vim /etc/apache2/sites-available/tomcat.conf
Next step is to adjust the domain names.
NOTE​: The trailing forward slash on the end of the redirect URL is required.
​<VirtualHost *:80>
      # redirect to https
      Redirect permanent / https://demo.milesgratz.com/
</VirtualHost>

<VirtualHost *:443>
      # LogLevel info ssl:warn
      ErrorLog ${APACHE_LOG_DIR}/error.log
      CustomLog ${APACHE_LOG_DIR}/access.log combined

      # Enable SSL for this virtual host.
      SSLEngine on
      SSLCertificateKeyFile /etc/letsencrypt/live/demo.milesgratz.com/privkey.pem
      SSLCertificateFile /etc/letsencrypt/live/demo.milesgratz.com/cert.pem
      SSLCertificateChainFile /etc/letsencrypt/live/demo.milesgratz.com/chain.pem

      # Configure proxy with tomcat
      ProxyPreserveHost On
      ProxyRequests Off
      ProxyPass / http://localhost:8080/
      ProxyPassReverse / http://localhost:8080/
</VirtualHost>
The last step is to enable the SSL/proxy modules, enable the new VirtualHost configuration, and disable the default Apache2 configuration. 
​sudo a2enmod ssl proxy proxy_http
sudo a2dissite 000-default
sudo a2ensite tomcat
sudo systemctl restart apache2
Picture
[ Installing ufw ]

Now that we have a working web server, we need to properly secure it. Enabling the firewall can also accidentally disable your SSH access. The rules below allow SSH, HTTP, and HTTPS from all addresses. You will likely want to be more restrictive.
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
Here is an example of allowing SSH from all private network IPv4 addresses. You could also restrict it to a small range of IP addresses (e.g. 10.9.58.0/24) or even a single IP address.  
sudo ufw allow from 10.0.0.0/8 to any port 22
sudo ufw allow from 172.16.0.0/12 to any port 22
sudo ufw allow from 192.168.0.0/16 to any port 22
Once you are comfortable with your firewall rules, we can enable the firewall permanently. 
sudo ufw enable
[ Installing MySQL Server ]

Depending on what you are planning on doing with PWM, you may not need to install the MySQL database. Unless you are confident you will not need it, it may make more sense to install it. For example, a database is required to store secret questions/answers for "Forgot My Password" feature. 
sudo apt-get install mysql-server
The default installation of MySQL has a "test" database and enables remote root access. To purge these default settings, we will run the MySQL Secure Installation. It is recommended that you change your MySQL root password if your estimated password strength is below 100.
sudo mysql_secure_installation

Would you like to setup VALIDATE PASSWORD plugin? (Press y|Y for Yes, any other key for No) : 
Y

There are three levels of password validation policy:
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Estimated strength of the password: 100
​

Change the password for root? (Press y|Y for Yes, any other key for No) : 
N

Remove anonymous users? (Press y|Y for Yes, any other key for No) : 
Y

Disallow root login remotely? (Press y|Y for Yes, any other key for No) :
Y

Reload privilege tables now? (Press y|Y for Yes, any other key for No) :
Y
We will now login to MySQL and create the pwm user, pwm database, and grant privileges accordingly.  
sudo mysql --user="root" --password
mysql> 

CREATE USER 'pwm'@'localhost' IDENTIFIED BY '
YOUR_DB_PASSWORD';
CREATE DATABASE pwm;
​GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP ON pwm.* TO 'pwm'@'localhost';
FLUSH PRIVILEGES;

​exit


[ Installing PWM ]

You've made it to the easy part! Let's install the actual PWM application. You can download the latest build from their website (http://www.pwm-project.org/artifacts/pwm). This blog post was written using the "28 Aug 2016" build.
cd /tmp
wget http://www.pwm-project.org/artifacts/pwm/pwm-1.8.0-SNAPSHOT-2016-08-28T22%3A16%3A03Z-pwm-bundle.zip

unzip pwm*bundle.zip
Next, let's replace the default Tomcat "ROOT" directory with our PWM application. 
sudo rm -rf /opt/tomcat/webapps/ROOT
​sudo mv pwm.war /opt/tomcat/webapps/ROOT.war
Tomcat will detect the ".war" file and automatically unpack the contents into the /opt/tomcat/webapps/ROOT folder. Once it has been deployed, we need to modify the PWM "web.xml" file so that it has the correct applicationPath.
sudo vim /opt/tomcat/webapps/ROOT/WEB-INF/web.xml 

        <param-name>applicationPath</param-name>
        <param-value>/opt/tomcat/webapps/ROOT/WEB-INF</param-value>

The website should be working now!
Picture
[ Configuring PWM ]

The Configuration Guide is going to have you configure your LDAP settings and a handful of other "initial setup" settings. If you are having issues getting through this part, feel free to ask questions in the comment section. Other useful resources include the PWM Administration Guide or the PWM General forum. 

​Once you have completed the Configuration Guide, we also need to install the MySQL "Connector/J" JDBC driver that allows the PWM application to communicate with the MySQL database. I recommend downloading the latest version from their website (https://dev.mysql.com/downloads/connector/j) but I am using v5.1.39 in this guide. 
cd /tmp​
wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.39.tar.gz
tar -xzvf mysql-connector-java*.tar.gz
cd mysql-connector-java*
sudo mv mysql-connector-java*.jar /opt/tomcat/webapps/ROOT/WEB-INF/lib
Next, goto your "Configuration Editor" -> "Settings" -> "Database (Remote)" -> "Connection".

Database Class:
com.mysql.jdbc.Driver

​Database Connection String:
jdbc:mysql://localhost:3306/pwm

Database Username:
pwm

Database Password:
(enter PWM database password)

Database Vendor:
Other
Picture
[ Congratulations! ]

Well done, you made it! Thanks for reading my blog. I appreciate all the helpful feedback I've gotten from the community over the years. If you are struggling with any part of the guide, please comment below. 
8 Comments
Benjamin Maynard
12/18/2016 12:26:26 pm

One of the best guides for installing PWM out there. Thanks a lot.

Reply
Jared
5/17/2017 05:25:04 pm

From what I can tell, once the .war file is processed, then TomCat does not seem to load at reboot. I verify that Tomcat is not loading by issuing the command 'ps ax | greap catalina'. I am having to do /opt/tomcat/bin/startup.sh manually in order to start Tomcat. You might want to add a step to make sure that loads at startup.

Reply
Miles Gratz link
6/2/2017 11:23:10 am

Hi Jared,

Sorry for not following up on this sooner. After creating the tomcat service, we enable the service to start automatically on boot-up.

sudo systemctl daemon-reload
sudo systemctl start tomcat
​​sudo systemctl enable tomcat

Can you check if the service is enabled with this command?

sudo systemctl list-unit-files | grep enabled

If you are launching the /opt/tomcat/bin/startup.sh manually, I am guessing the service is not running either? You can confirm with this command:

sudo systemctl | grep running

These articles helped me a lot in writing the guide and might be able to offer more information:

https://www.digitalocean.com/community/tutorials/how-to-install-apache-tomcat-8-on-ubuntu-16-04

https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units

Let me know what you find out! I'm very curious.

Thanks,
Miles

Reply
Jared
6/2/2017 11:47:11 am

It turns out that Tomcat was loading but the OS was terminating it due to the fact that I didn't give the VM enough memory. It was trimming processes to try to keep the system stable. So... that was my bad. :)

For future readers, running PWM on 512 mb of ram is a no go, but 1 gig seems to work fine.

Jared
5/19/2017 02:10:05 pm

When configuring the database in the configuration wizard, the vendor should be other and not Oracle. I had the same problem as mentioned here:

https://groups.google.com/forum/embed/#!topic/pwm-general/Q1Qf2n0KQT8

I also had to hit retry a couple of times for PWM to set up the DB like it should.

Reply
Miles Gratz link
6/2/2017 11:15:55 am

Hi Jared,

Good catch! I updated the blog based on the feedback, I appreciate you taking the time to let me know it was inaccurate.

Thanks,
Miles

Reply
Stan Towers
11/10/2017 07:44:37 am

Hi,

first off thank you for your guide. It is quite comprehensive and easy to follow. However, I can't get the remote database connection to work.

The Database Connection Status show this warming message:

unable to initialize database: exception initializing database service: 5051 ERROR_DB_UNAVAILABLE ( unable to load database driver: ["AppPathFileLoader error: 5051 ERROR_DB_UNAVAILABLE (jdbc driver file not configured, skipping)","Classpath error: 5051 ERROR_DB_UNAVAILABLE (java.lang.ClassNotFoundException error loading JDBC database driver from classpath: com.mysql.jdbc.Driver)"])

Can you help me?

Reply
Stan Towers
11/10/2017 08:09:15 am

EDIT:

The actual error warning is this:

unable to initialize database: exception initializing database service: 5051 ERROR_DB_UNAVAILABLE (error creating new table PWM_META: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'value TEXT )' at line 3)

Reply



Leave a Reply.

    (miles gratz)

    Just another curious mind.

    (archives)

    November 2016
    October 2016
    September 2016
    August 2016
    July 2016
    June 2016
    March 2016
    January 2016
    October 2015
    August 2015
    July 2015
    June 2015
    May 2015
    April 2015
    March 2015
    February 2015
    January 2015
    December 2014
    November 2014
    September 2014
    August 2014
    July 2014
    June 2014
    May 2014
    April 2014
    March 2014
    February 2014
    January 2014
    December 2013
    October 2013
    September 2013
    August 2013
    July 2013

    (categories)

    All
    Active Directory
    Bash
    Cisco
    CMD
    DHCP
    Google Apps
    Group Policy
    Linux
    Mac OS X
    MDT
    Misc
    Networking
    Office 365
    PowerShell
    SCCM
    VBScript
    Virtualization
    Windows

Powered by
✕