Rails app redirects to wrong port?

Ran into a situation in which a rails application was redirecting to /login to force a user to log in, but the Location header said “http://site.com:8085/login”, because nginx was listening on port 8085 on that server. At first I looked to see if there was something in the application code that was doing this, or maybe some setting I could change to fix it, but came up blank. After some Googling I found the answer right in the Nginx docs (below is my slightly-modified solution that handles https urls as well):

   proxy_redirect ~^(http(s?)://[^:]+):d+(/.+)$ $1$3;

That simply removes the port number from the Location: header, so whatever kind of proxy magic you’re doing will “just work.”

Digital Ocean – First Impressions

For the past few years I’ve been hosting this site on an old desktop in my basement on my FiOS connection. This was one of the things I really liked when I switched from Cablevision to Verizon – they don’t block port 80 inbound, so I didn’t have to pay for separate hosting. My “server” was an old AMD desktop with 1 gig ram and a sata drive. It was ok; my site was slow but I was ok with that. I configured Nginx to cache the static assets which sped most things up to “ok” levels but it was never fast.

This setup had a bunch of problems though, and the biggest one was power. Namely, it goes out in my house all the time. I probably have 4 or 5 brief outages each month, and my old box doesn’t come back up properly on reboot (some bios conflict with an eSATA disk I have hooked up to it). Plus, since my basement became a huge bathtub during Sandy, my site was down for about a month, but that wasn’t really a big concern at the time.

Anyway, via a “Promoted Tweet” on Twitter I found Digital Ocean, a VPS provider with rates starting at $5/month for an SSD-backed VM. They also had a promo at the time for a $10 credit, so I figured I’d give it a try.

Account creation was simple and I didn’t need to enter my CC until I actually created a server (“droplet” in their parlance). Server creation was pretty trivial: select the OS image (I chose CentOS 6.4 but they offer Ubuntu, Arch, Debian and Fedora as well), the size (512 MB ram through 16 GB), the region (San Francisco, New York, or Amsterdam), enter a hostname and your SSH pubkey. In about 60 seconds your server is ready to go, with a public IP and everything. My VM has a 20 GB disk and the base OS install was about 900 MB. I installed Apache, Nginx, MySQL and some other stuff, then dumped my WordPress DB and uploaded it to the new VM and copied the entire Apache docroot over as well. Within about 30 minutes of spinning up the VM I had everything up on the new box, and I made the DNS changes shortly after that. Pretty straightforward.

It’s only been a couple of days but so far I’m really liking the performance. My site doesn’t get a lot of traffic to begin with, but since I cache most stuff to disk, and the disk is SSD, it’s really quick. I’ll keep an eye on it but so far this is looking like a great choice for small website hosting. The only thing is I’ll need to setup some sort of offsite backups, but I can just cron an rsync to my home machine for now.

evanhoffman_digitalocean

Making sure SSLv2 is disabled in Apache (and Nginx)


Edit Jan 24, 2012: Deleted all the crap from this story and just left the recommended Apache and Nginx SSL cipher suites for maximum security without SSLv2 and without BEAST vulnerability (at least according to Qualys).

Apache httpd

SSLProtocol -ALL +SSLv3 +TLSv1
SSLCipherSuite ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM;
SSLHonorCipherOrder on

nginx

        ssl_protocols  SSLv3 TLSv1;
        ssl_ciphers     ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM;
        ssl_prefer_server_ciphers   on;

Source:

Go Daddy $12.99 SSL Sale!

Logging RT username in Apache access_log

RT has its own internal accounting & tracking system for logging activity, but I was interested in even more granular stuff, like seeing who looked at which tickets. I figured it wouldn’t be that hard to log this in Apache. Well, I was kind of right, in that it wasn’t “hard,” but it took me a long time to find the right place to do it. I did finally get it though.
Continue reading “Logging RT username in Apache access_log”

Putting up a "down for maintenance" message using mod_rewrite

Putting this here for safekeeping so my future self can find it. Mod_rewrite is one of my favorite tools, but it’s easy to spend 30 minutes crafting a 2-line directive that actually does what you want. I put this in a .htaccess file in the DocumentRoot of the server, put a “We’re down” message in maintenance.html (or whatever), and all requests will get a 302 redirect to /maintenance.html, except requests for /maintenance.html (for obvious reasons). It appends the original request in case you want to do something with it but that’s not really important. It also doesn’t do the redirect for images/js/css so those can actually be used in the maintenance message.

RewriteEngine on
RewriteRule ^maintenance.html	-	[PT,L]

RewriteCond %{REQUEST_URI}	!(gif|jpg|css|js)$
RewriteRule (.*) /maintenance.html?redir=true&originalRequest=%{REQUEST_URI} [R=302]

Putting up a “down for maintenance” message using mod_rewrite

Putting this here for safekeeping so my future self can find it. Mod_rewrite is one of my favorite tools, but it’s easy to spend 30 minutes crafting a 2-line directive that actually does what you want. I put this in a .htaccess file in the DocumentRoot of the server, put a “We’re down” message in maintenance.html (or whatever), and all requests will get a 302 redirect to /maintenance.html, except requests for /maintenance.html (for obvious reasons). It appends the original request in case you want to do something with it but that’s not really important. It also doesn’t do the redirect for images/js/css so those can actually be used in the maintenance message.

RewriteEngine on
RewriteRule ^maintenance.html	-	[PT,L]

RewriteCond %{REQUEST_URI}	!(gif|jpg|css|js)$
RewriteRule (.*) /maintenance.html?redir=true&originalRequest=%{REQUEST_URI} [R=302]

Forcing WordPress administration over SSL

I never like typing a password into a non-SSL site, no matter how trivial it is. In order to give my own site this ability I simply used mod_rewrite to force requests to WordPress’s admin pages to go over SSL.

The .htaccess file for the site looks like this:

# BEGIN WordPress

RewriteEngine On
RewriteBase /evan/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /evan/index.php [L]

# END WordPress

To force the admin pages to SSL, just add these lines under RewriteEngine On:


RewriteCond %{HTTPS} !=on
RewriteRule ^wp-(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

Edit – The above code screws up uploads (which go into the /wp-content directory). I replaced that with the following and it Worked As Intended.


RewriteCond %{HTTPS} !=on
RewriteRule ^wp-login(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
RewriteCond %{HTTPS} !=on
RewriteRule ^wp-admin(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

That’s pretty much it. If your request starts with “wp-” it’ll redirect you to the same URL, but starting with https://. Problem solved. You do need to make sure you have an SSL VirtualHost pointing to your WordPress DocumentRoot so that https://yoursite.com goes to the same place as http://yoursite.com.

Blocking comment spammers by IP

I use Akismet to block comment spam, but it still annoys me that it even exists. Last night I put a simple IP ban into my httpd config. But who to block?

I used a grep & Perl to get a rough guess of which IPs were submitting the most comments (working on the assumption that one IP address submits many spam comments) It took me about 20 minutes to write this mess but it does what I wanted to do:

[root@lunix ~]# zgrep POST /var/log/httpd/evanhoffman-access_log-201008??.gz | grep comment | perl -ne 'chomp; $_ =~ m/(?:\d{1,3}\.){3}\d{1,3}/; print "$&\n";' | perl -e '%a = (); while (<>) { chomp; $a{$_} += 1; } while (my ($key, $value) = each (%a)) { if ($value > 1) { print "$value\t=>\t$key\n";}}'
2 => 218.6.9.140
180 => 91.201.66.34
2 => 213.5.67.41
2 => 188.187.102.74
[root@lunix ~]#

That’s pretty hard to read. Here’s a quick explanation of each piece:

zgrep POST /var/log/httpd/evanhoffman-access_log-201008??.gz

Use zgrep to search for the string “POST” in all of the gzipped Apache logs for August. Pipe the results (the matching lines) to the next part:

grep comment

grep for the string “comment”. This isn’t really scientific, but I feel safe in assuming that if “POST” and “comment” both appear in the HTTP request, it’s probably someone posting a comment. Pipe the matches to…

perl -ne ‘chomp; $_ =~ m/(?:\d{1,3}\.){3}\d{1,3}/; print “$&\n”;’

This is a perl one-liner that uses a regular expression to match an IP address in a given line and print it out. The original regex I used was \d+\.\d+\.\d+\.\d+, this one was slightly fancier but did the same work in this case. It’s worth noting that this will only print out the first match in the given line, but since the requester’s IP (REMOTE_ADDR) is the first field in Combined Log Format, that’s fine this case.

The output (the IPs from which comment posts have been made) is piped to…

perl -e ‘%a = (); while (<>) { chomp; $a{$_} += 1; } while (my ($key, $value) = each (%a)) { if ($value > 1) { print “$value\t=>\t$key\n”;}}’

This is another perl one-liner. Basically, it maintains a hash of String=>count pairs, so each time it sees a string it increments a “counter” for that line. Then when it’s done receiving input (i.e. all the data has been processed) it prints out the contents of the hash for keys that have a value > 1 (i.e. IPs that have POSTed more than 1 comment).

The output shows pretty clearly where the spam is coming from:

2 => 218.6.9.140
180 => 91.201.66.34
2 => 213.5.67.41
2 => 188.187.102.74

180 submits from 91.201.66.34. Out of curiosity I looked up that IP in whois:

[root@lunix ~]# whois 91.201.66.34
[Querying whois.ripe.net]
[whois.ripe.net]
% This is the RIPE Database query service.
% The objects are in RPSL format.
%
% The RIPE Database is subject to Terms and Conditions.
% See http://www.ripe.net/db/support/db-terms-conditions.pdf

% Note: This output has been filtered.
%       To receive output for a database update, use the "-B" flag.

% Information related to '91.201.64.0 - 91.201.67.255'

inetnum:        91.201.64.0 - 91.201.67.255
netname:        Donekoserv
descr:          DonEkoService Ltd
country:        RU
org:            ORG-DS41-RIPE
admin-c:        MNV32-RIPE
tech-c:         MNV32-RIPE
status:         ASSIGNED PI
mnt-by:         RIPE-NCC-END-MNT
mnt-by:         MNT-DONECO
mnt-by:         MNT-DONECO
mnt-lower:      RIPE-NCC-END-MNT
mnt-routes:     MHOST-MNT
mnt-routes:     MNT-PIN
mnt-domains:    MHOST-MNT
source:         RIPE # Filtered

organisation:   ORG-DS41-RIPE
org-name:       DonEko Service
org-type:       OTHER
address:        novocherkassk, ul stremyannaya d.6
e-mail:         admin@pinspb.ru
mnt-ref:        MNT-PIN
mnt-by:         MNT-PIN
source:         RIPE # Filtered

person:         Metluk Nikolay Valeryevich
address:        korp. 1a 40 Slavy ave.,
address:        St.-Petersburg, Russia
e-mail:         nm@internet-spb.ru
phone:          +7 812 4483863
fax-no:         +7 901 3149449
nic-hdl:        MNV32-RIPE
mnt-by:         MNT-PIN
source:         RIPE # Filtered

% Information related to '91.201.66.0/23AS21098'

route:          91.201.66.0/23
descr:          Route MHOST IDC
origin:         AS21098
mnt-by:         MHOST-MNT
source:         RIPE # Filtered

[root@lunix ~]#

Not much info other than the IP is based in Russia. Well, anyway, I IP blocked 91.0.0.0/8 (sorry, Russia), so if you’re in that subnet you’re probably seeing a 403 now.

Edit: It occurred to me that I can accomplish the same thing while being less draconian if I wrap the Deny in a <Limit></Limit> clause. This way everyone can still see the site but certain IP ranges won’t be able to POST anything:

<Limit POST PUT DELETE>
Order Allow,Deny
Allow from all
Deny from 218.6.9.
Deny from 173.203.101.
Deny from 122.162.28.
Deny from 91.
Deny from 213.5
</Limit>

LDAP-Active Directory authentication, Part 3

So I got everything working with .htaccess and AD/LDAP authentication. Just add LDAPVerifyServerCert Off to the httpd config to let Apache authenticate against an AD server with a self-signed certificate (without dealing with the annoyance of putting the cert on each Apache server).

With that piece of the puzzle largely solved, I moved on to another: how will users change their passwords (which are all stored in Active Directory)? For users running Windows this is pretty trivial — they can do it right in Windows when they’re logged into the domain. But what about Linux users? I figured the easiest thing to do would be to make a web form to do this. The user would login (with the http/LDAP auth I previously setup) and the form would ask for their password (twice) and update it in Active Directory. Sounds pretty simple to me. I think if this were OpenLDAP it probably would be, but being AD, it’s not.

Continue reading “LDAP-Active Directory authentication, Part 3”