The following demonstrates how to create a strict SSL communication between client and server, using HTTP.
This setup could used when creating a web API which requires strong encryption and only allows clients which have a properly signed certificate.
The Apache configuration in the below example will actually require 2 web servers:
- one proxy host, which will accept the SSL connection, verify, check for ACLs and then forward the connection unencrypted internally
- one internal web server which will actually contain the WebAPI scripts
This article explains how to use Mojolicious for the WebAPI side and LWP::UserAgent to send and receive the WebAPI calls. We will furthermore use JSON to send and receive information.
First we need to have or create a set of OpenSSL certificates.
The below example uses self signed certificates, since they don’t cost any money and suit perfect for the purpose of this example.
There a million howto’s on the internet which explains these steps very thoroughly, so I won’t reinvent the wheel. I’m just going to post the steps I took to create:
- a CA certificate
- a client certificate
- a server certificate
cd /path/to/SSL cp /etc/ssl/openssl.cnf example.cnf vim example.cnf # Edit the file to your needs openssl genrsa -aes256 -out private/example_com_ca.key 4096 openssl req -config example.cnf -new -x509 -extensions v3_ca -key private/example_com_ca.key -out certs/example_com_ca.crt -days 3650 openssl req -config example.cnf -new -nodes -keyout private/client01.key -out client01.csr -days 365 openssl ca -config example.cnf -policy policy_anything -out certs/client01.crt -infiles client01.csr openssl req -config example.cnf -new -nodes -keyout private/server.key -out server.csr -days 365 openssl ca -config example.cnf -policy policy_anything -out certs/server.crt -infiles server.csr
Next we will need to configure our web server (this example uses the Apache web server) in order to use our self signed certificates, and to proxy forward our WebAPI calls.
SSLEngine on SSLCertificateFile /path/to/SSL/certs/server.crt SSLCertificateKeyFile /path/to/SSL/private/server.key SSLCertificateChainFile /path/to/SSL/certs/example_com_ca.crt SSLCACertificateFile /path/to/SSL/certs/example_com_ca.crt SSLVerifyClient require ProxyPass /send/ http://internal-host/send.pl/ ProxyPassReverse /send/ http://internal-host/send.pl/ <Proxy *> Options FollowSymLinks MultiViews AllowOverride All Order deny,allow allow from localhost allow from 8.8.8.8 # The client IP address deny from all </Proxy>
The above the configuration for the external proxy server. The internal web server should have a pretty straight-forward configuration:
- a cgi-handler for the Perl extension ‘.pl’
I could have also send those proxy requests to an internal Mojolicious application, listening on a specific port. I’ll leave that for another article.
The test client script is going to make a SSL connection to the external web server, send some JSON and wait for the server to send some JSON data back. The interesting part in the below script is how to set up the SSL connection.
#!/usr/bin/perl use strict; use warnings; use HTTP::Request; use LWP::UserAgent; use IO::Socket::SSL; use JSON; my $data = { username => 'skipper', password => 'secret', variable => 'value', }; my $uri = 'https://example.com/send/event'; my $json = encode_json( $data ); my $req = HTTP::Request->new( 'POST', $uri ); $req->header( 'Content-Type' => 'application/json' ); $req->content( $json ); my $lwp = LWP::UserAgent->new( ssl_opts => { SSL_use_cert => 1, SSL_version => 'TLSv12', SSL_verify_mode => SSL_VERIFY_PEER, SSL_ca_file => "/path/to/SSL/certs/example_com_ca.crt", SSL_cert_file => "/path/to/SSL/certs/client01.crt", SSL_key_file => "/path/to/SSL/private/client01.key", }, ) or die "SSL Connection failed: $!"; my $res = $lwp->request( $req ); if ($res->is_success) { print "RESPONSE:", $res->content . "\n"; } else { print "ERROR: ", $res->status_line . "\n"; }
The server example uses the Mojolicious frame work. Mojolicious is the porn for every Perl WebAPI developer. If you don’t know it, you should be ashamed and start reading about it right away.
#!/usr/bin/env perl use Mojolicious::Lite; # A helper to identify visitors helper whois => sub { my $c = shift; my $headers = $c->req->headers; my $agent = $c->req->headers->user_agent || 'Anonymous'; my $local_ip = $c->tx->remote_address; my $remote_ip = $headers->header('x-forwarded-for'); return { agent => $agent, local_ip => $local_ip, remote_ip => $remote_ip, }; }; any '/' => sub { my $c = shift; $c->render( text => "There is nothing to see here, move along" ); }; post '/event' => sub { my $c = shift; my $json = $c->req->json; my $data = { username => $json->{username}, password => $json->{password}, whois => $c->whois, }; $c->render( json => $data ); }; ### IMPORTANT app->secret('some_cool_secret'); app->start;
Example output of the test client script:
$ perl test_send.pl RESPONSE: {"whois":{"remote_ip":"176.9.64.17","agent":"libwww-perl\/6.04","local_ip":"176.9.64.17"},"password":"secret","username":"skipper"}