Skip to content

Commit

Permalink
Added better ssh defaults, added fail2ban, ferm for added security.
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanielks committed Nov 19, 2014
1 parent 5be55f5 commit 313853c
Show file tree
Hide file tree
Showing 31 changed files with 896 additions and 3 deletions.
52 changes: 52 additions & 0 deletions README.md
Expand Up @@ -83,6 +83,58 @@ The example `Vagrantfile` in this project can be kept in this folder, or moved a

Whenever you move or copy the `Vagrantfile` somewhere else, you need to make sure to adjust the relative paths in it including `config.vm.synced_folder` and `ansible.playbook = './site.yml'`.

## Security

### Locking down `root`

By default, ssh access to the `root` user is allowed on fresh server installs. The `secure-root.yml` playbook does a few things to lock the server down:

* Creates users with `sudo` privileges.
* Disable ssh root login.
* Disable password authentication for ssh access.
* Set better ssh defaults.

There are a few variables to be aware of:

* `sudoers` located in `group_vars/all`
This variable contains a list of dictionaries of users to create.
* `sudoer_passwords` located in `vars/sudoer_passwords`. This is a dictionary of user => password pairs used when creating users with sudo privileges.

Here's an example list of `sudoers`:
```
sudoers:
- user: "admin"
groups: ["sudo"]
- user: "another_user"
groups: ["sudo"]
```

`user` is the key to be used when creating the user and `groups` is an array used to determine the groups the user belongs to. The first item in the array will be used when setting the user's primary group.


Here's an example list of `sudoer_passwords`:
```
sudoers:
admin: $6$rounds=100000$JUkj1d3hCa6uFp6R$3rZ8jImyCpTP40e4I5APx7SbBvDCM8fB6GP/IGOrsk/GEUTUhl1i/Q2JNOpj9ashLpkgaCxqMqbFKdZdmAh26/
another_user: $6$rounds=100000$r3ZZsk/uc31cAxQT$YHMkmKrwgXr3u1YgrSvg0wHZg5IM6MLEzqOraIXqh5o7aWshxD.QaNeCcUX3KInqzTqaqN3qzo9nvc/QI0M1C.
```

The passwords were generated using the python command [found here](http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module). The passwords generated here are `example_password` and `another_password`, respectively. The ansible user module doesn't handle any encryption and passwords must be encrypted beforehand. It's also recommended `vars/sudoer_passwords.yml` be encrypted using one of the encryption methods described in the [passwords](#passwords) section of this document. Passwords are stored separately in order to ease the separation of encrypted var files and are looked up based on the user name.

This playbook should be run on remote hosts and is designed to be run once whenever the server is initially created (as the root user will be inaccessible after running and the ansible default user is the [current user](http://docs.ansible.com/intro_configuration.html#remote-user)). To invoke, run `ansible-playbook -i hosts/ENVIRONMENT secure-root.yml`. Because the root user is locked down, remote hosts also need to use a different ssh user. You can set the default remote user in `site.yml` by setting the `remote_user` variable.

### `fail2ban` and `ferm`

> Fail2ban scans log files (e.g. /var/log/apache/error_log) and bans IPs that show the malicious signs -- too many password failures, seeking for exploits, etc.
From http://www.fail2ban.org/wiki/index.php/Main_Page

> ferm is a tool to maintain complex firewalls, without having the trouble to rewrite the complex rules over and over again. ferm allows the entire firewall rule set to be stored in a separate file, and to be loaded with one command.
From http://ferm.foo-projects.org/

You can read the role documentation for `fail2ban` [here](roles/fail2ban/README.md) and `ferm` [here](roles/ferm/README.md).

## Vagrant Box

By default, the example `Vagrantfile` now uses the `roots/bedrock` box. It's publicly available on the Vagrant Cloud site [here](https://vagrantcloud.com/roots/bedrock).
Expand Down
1 change: 0 additions & 1 deletion Vagrantfile
Expand Up @@ -50,7 +50,6 @@ Vagrant.configure('2') do |config|
ansible_ssh_user: 'vagrant',
user: 'vagrant'
}
ansible.sudo = true
end


Expand Down
15 changes: 15 additions & 0 deletions group_vars/all
@@ -1,3 +1,18 @@
---
www_root: /srv/www
mariadb_dist: trusty
apt_cache_valid_time: 86400

sudoers:
- user: "admin"
groups: ["sudo"]

ferm_input_list:
- type: "dport_accept"
dport: ["http", "https"]
filename: "nginx_accept"
- type: "dport_limit"
dport: ["ssh"]
seconds: "300"
hits: "5"
disabled: false
22 changes: 22 additions & 0 deletions roles/fail2ban/LICENSE
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2014 Nick Janetakis nick.janetakis@gmail.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
119 changes: 119 additions & 0 deletions roles/fail2ban/README.md
@@ -0,0 +1,119 @@
## What is ansible-fail2ban?

It is an [ansible](http://www.ansible.com/home) role to install and configure fail2ban.

### What problem does it solve and why is it useful?

Security is important and fail2ban is an excellent tool to harden your server with minimal or even no configuration.

## Role variables

Below is a list of default values along with a description of what they do.

```
# Which log level should it be output as?
# 1 = ERROR, 2 = WARN, 3 = INFO, 4 = DEBUG
fail2ban_loglevel: 3
# Where should log outputs be sent to?
# SYSLOG, STDERR, STDOUT, file
fail2ban_logtarget: /var/log/fail2ban.log
# Where should the socket be created?
fail2ban_socket: /var/run/fail2ban/fail2ban.sock
# Which IP address, CIDR mark or DNS host should be ignored?
fail2ban_ignoreip: 127.0.0.1/8
# How long in seconds should the ban last for?
fail2ban_bantime: 600
# How many times can they try before getting banned?
fail2ban_maxretry: 6
# How should the file changes be detected?
# gamin, polling, auto
fail2ban_backend: polling
# Where should e-mail reports be sent to?
fail2ban_destemail: root@localhost
# How should the ban be applied?
# iptables, iptables-new, iptables-multiport, shorewall, etc.
fail2ban_banaction: iptables-multiport
# What e-mail action should be used?
# sendmail or mail
fail2ban_mta: sendmail
# What should the default protocol be?
fail2ban_protocol: tcp
# Which chain would the JUMPs be added into iptables-*?
fail2ban_chain: INPUT
# What should the default ban action be?
# action_, action_mw, action_mwl
fail2ban_action: action_
# What services should fail2ban monitor?
# You can define fail2ban_services as an empty string to not monitor anything.
# You can define multiple services as a standard yaml list.
fail2ban_services:
# The name of the service
# REQUIRED.
- name: ssh
# Is it enabled?
# OPTIONAL: Defaults to "true" (must be a string).
enabled: "true"
# What port does the service use?
# Separate multiple ports with a comma, no spaces.
# REQUIRED.
port: ssh
# What protocol does the service use?
# OPTIONAL: Defaults to the protocol listed above.
protocol: tcp
# What filter should it use?
# REQUIRED.
filter: sshd
# Which log path should it monitor?
# REQUIRED.
logpath: /var/log/auth.log
# How many times can they try before getting banned?
# OPTIONAL: Defaults to the maxretry listed above.
maxretry: 6
# What should the default ban action be?
# OPTIONAL: Defaults to the action listed above.
action: action_
# How should the ban be applied?
# OPTIONAL: Defaults to the banaction listed above.
banaction: iptables-multiport
```

## Example playbook

Let's say you want to edit a few values, you can do this by opening `group_vars/all` and then add the following:

```
fail2ban_services:
- name: ssh
port: ssh
filter: sshd
logpath: /var/log/auth.log
- name: postfix
port: smtp,ssmtp
filter: postfix
logpath: /var/log/mail.log
```

## Attribution

Many thanks to [nickjj](https://github.com/nickjj/) for creating the [original version](https://github.com/nickjj/ansible-fail2ban/) of this role.
24 changes: 24 additions & 0 deletions roles/fail2ban/defaults/main.yml
@@ -0,0 +1,24 @@
---
fail2ban_loglevel: 3
fail2ban_logtarget: /var/log/fail2ban.log
fail2ban_socket: /var/run/fail2ban/fail2ban.sock

fail2ban_ignoreip: 127.0.0.1/8
fail2ban_bantime: 600
fail2ban_maxretry: 6

fail2ban_backend: polling

fail2ban_destemail: root@localhost
fail2ban_banaction: iptables-multiport
fail2ban_mta: sendmail
fail2ban_protocol: tcp
fail2ban_chain: INPUT

fail2ban_action: action_

fail2ban_services:
- name: ssh
port: ssh
filter: sshd
logpath: /var/log/auth.log
3 changes: 3 additions & 0 deletions roles/fail2ban/handlers/main.yml
@@ -0,0 +1,3 @@
---
- name: restart fail2ban
service: name=fail2ban state=restarted
16 changes: 16 additions & 0 deletions roles/fail2ban/tasks/main.yml
@@ -0,0 +1,16 @@
---
- name: ensure fail2ban is installed
apt: pkg=fail2ban state=latest update_cache=true cache_valid_time={{ apt_cache_valid_time }}
notify:
- restart fail2ban

- name: ensure fail2ban is configured
template: src={{ item }}.j2 dest=/etc/fail2ban/{{ item }}
with_items:
- jail.local
- fail2ban.local
notify:
- restart fail2ban

- name: ensure fail2ban starts on a fresh reboot
service: name=fail2ban state=started enabled=yes
5 changes: 5 additions & 0 deletions roles/fail2ban/templates/fail2ban.local.j2
@@ -0,0 +1,5 @@
[Definition]

loglevel = {{ fail2ban_loglevel }}
logtarget = {{ fail2ban_logtarget }}
socket = {{ fail2ban_socket }}
47 changes: 47 additions & 0 deletions roles/fail2ban/templates/jail.local.j2
@@ -0,0 +1,47 @@
[DEFAULT]

ignoreip = {{ fail2ban_ignoreip }}
bantime = {{ fail2ban_bantime }}
maxretry = {{ fail2ban_maxretry }}

backend = {{fail2ban_backend}}

destemail = {{ fail2ban_destemail }}
banaction = {{ fail2ban_banaction }}
mta = {{ fail2ban_mta }}
protocol = {{ fail2ban_protocol }}
chain = {{ fail2ban_chain }}

action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]

action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]

action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

action = %({{ fail2ban_action }})s

{% if fail2ban_services is iterable %}
{% for service in fail2ban_services %}
[{{ service.name }}]

enabled = {{ service.enabled|default("true") }}
port = {{ service.port }}
filter = {{ service.filter }}
logpath = {{ service.logpath }}
{% if service.maxretry is defined %}
maxretry = {{ service.maxretry }}
{% endif %}
{% if service.protocol is defined %}
protocol = {{ service.protocol }}
{% endif %}
{% if service.action is defined %}
action = %({{ service.action }})s
{% endif %}
{% if service.banaction is defined %}
banaction = {{ service.banaction }}
{% endif %}

{% endfor %}
{% endif %}
22 changes: 22 additions & 0 deletions roles/ferm/LICENSE
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2014 Nick Janetakis nick.janetakis@gmail.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

12 comments on commit 313853c

@30suns
Copy link

@30suns 30suns commented on 313853c Dec 1, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems cool but I don't really understand why we need yet another user. It's confusing to have the built-in ubuntu (with sudo powers), new admin (with sudo powers), and deploy. What specifically are the roles of each user in the default configuration? Also, is it really necessary to manually generate a password during setup and paste it into a configuration file? Seems like the goal should be making this more portable with less files to edit, rather than expanding. Surely there's an automated way to auto-generate a good random password when the play is run (BTW: since passwords aren't ever used shouldn't they be disabled with passwd -l)

@30suns
Copy link

@30suns 30suns commented on 313853c Dec 1, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also ufw is still installed in dev-tools role, does this conflict with ferm?

@mAAdhaTTah
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ubuntu user isn't "built-in", at least not on DigitalOcean's Ubuntu droplets. The admin user is basically the ubuntu user but with a more general name (in case you're using Debian, for example).

The deploy user owns all the web files for security reasons.

You should have a password; logging in should require a public/private key pair but running sudo commands on the box should require the password.

@nathanielks will have to speak to ufw/ferm.

@nathanielks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@30suns ufw is definitely an oversight, we can remove that. It doesn't conflict because it isn't configured.

Everthing @mAAdhaTTah said :)

In addition to that, yes, there are automated ways of generating passwords, like so. In the research I was doing though, it's generally recommended not to do a plain text lookup though because of security reasons and lack of portability as the file would exist outside the repo (most likely). At least with this implementation, the file can be encrypted and edited using ansible-vault and safely stored in the repo when encrypted. I'd love to hear alternate solutions! All for more portability and security.

@30suns
Copy link

@30suns 30suns commented on 313853c Dec 1, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanielks fail2ban is also in dev-tools :)

As far as a sudo password goes, I'm down with that, but I get an error now when I run vagrant up Missing sudo password

Also, stock ubuntu amis on aws come with with full passwordless sudo and root ssh already disabled. So is bedrock-ansible DigitalOcean-specific (fine if it is, but if not, should be made more portable). I guess on aws we'd need to remove the ubuntu user, but that's challenging to do since you can't log in as root :)

@nathanielks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@30suns that's probably a separate issue, unless you ran secure-root.yml on your vagrant box? ( This isn't recommended ).

It's not necessarily DO specific, but I do believe the team prefers it. I know we're waiting on a few other tools so we can build in DO integration. As far as AWS is concerned, I believe @austinpray likes it. I haven't/don't use it, so tbh I'm building with DO in mind.

I don't see why we'd need to remove the ubuntu user on AWS. @swalkinshaw any ideas on techniques to make sure we can play well with other hosts?

@austinpray
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanielks @30suns This playbook should definitely be portable enough to work on AWS. I would say that is a good litmus test for portability.

I don't see why we'd need to remove the ubuntu user on AWS.

To work with AWS: couldn't you just set the "remote_user" to "ubuntu" and then just not run the secure-root.yml playbook? AWS already does this for you, so you can just skip it right?

@nathanielks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@austinpray this sounds correct to me as well. I'm not sure secure-root.yml would add anymore security than what's already built in to AWS's. @30suns would you mind comparing AWS's /etc/ssh/sshd_config to ours and see if there are any differences?

@30suns
Copy link

@30suns 30suns commented on 313853c Dec 3, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the followup everyone.

@mAAdhaTTah on debian-based systems installed with d-i there's normally a default-user and a root user 'built in', and ubuntu images tend to use ubuntu. I'm not sure why DO ubuntu droplets don't have a default user other than root, but that certainly explains why secure-root.yml assumes a root user has SSH access, and some of the other choices made. Some of those choices might need to be tweaked for portability.

@mAAdhaTTah e.g., passwords for sudo may be really great (I'm not saying they aren't) but there are quite a few gymnastics one has to go through to enable them on most AWS AMIs I've used. e.g., have a look at http://www.whiteboardcoder.com/2013/04/amazon-aws-fix-cloud-init-in-ubuntu-1210.html Further gymnastics will need to be done to remove the /etc/sudoers.d/90-cloud-init-users and possibly other things (I gave up after a few hours trying to get Packer to build me a Ubuntu image using a user_data_file that would enable passwords for the default user).

I'm agnostic about what to call the users but I do think there should be clearer baseline roles defined in bedrock-ansible as well as a bootstrap process portable enough for AWS. AWS assumes the root user is locked out of SSH altogether, and the default user's password is locked, and has passwordless sudo. To change those defaults is going to require a separate play, mucking around with a user_data_file and possibly involve generating a new AMI; And I question whether the security will really be 'better'.

@mAAdhaTTah The deploy owns web files 'for security reasons', can you elaborate? It appears that it's capistrano can deploy to the web directory, but what about cap tasks involving restarting services, etc.? As currently implemented, wordpress_sites is an array, each with its own user defined and used in various plays. Said users are also in the same class as the 'deploy' user intended to be used with capistrano though there's presumably only one of those in use?

@austinpray As far as ignoring root-setup.yml, and just using the ubuntu user, certainly, but maybe the play should be renamed to secure-DO-root.yml Perhaps unsurprisingly, the security assumptions of that play are pretty different than those made by other teams. :)

@nathanielks I will take a look at AWS's /etc/ssh/sshd_config and post my results separately.

@30suns
Copy link

@30suns 30suns commented on 313853c Dec 3, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanielks Stock Ubuntu on AWS /etc/ssh/sshd_config: https://gist.github.com/30suns/25d7fb94c5e753f1fb63

@mAAdhaTTah
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@30suns Yeah, generally speaking, the development of bedrock-ansible has been DO-focused. I remember seeing issues suggesting testing using DO droplets, plus #40. I'm not sure how much extra work is required to make this portable to AWS, but if as you say it's a lot of extra work without being "safer," you can always skip running secure-root.yml for your AWS instances and secure it yourself. That's the reason it's a separate playbook. Maybe there's an argument for renaming it or including some documentation explaining it's for DO, but yeah, this was certainly written with DO in mind.
#61 addresses including some of the services under the deploy user so you can restart them when you deploy. The idea is you could give other developers access to deployment without giving them full access to the server, i.e. installing software, manipulating the database, etc. Though I guess this does raise the question about whether the deploy user should be given permissions to the .env file, though it's still better than full DB access. Similarly, if the deploy user is compromised, the damage is still more limited than full server access (i.e. they wouldn't be able to lock you out).

@swalkinshaw
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@30suns ideally this playbook should be portable and work on different providers (assuming Ubuntu 14.04 at least for now). Ansible has cloud modules so it shouldn't be too hard to at least make this playbook work with those easily.

Regarding users and deploy. I generally think there should be 3 "classes" of users/groups: root (obviously built-in and should be avoided as AWS does), admin/ubuntu (these are really the same thing, we should just avoid duplicating them), and then a web/deploy user.

I prefer to avoid using a user which has full sudo access and system wide permissions for ownership of web directories and access to a few services it may need to restart. This last one may be debatable but I don't think 3 users which clearly defined roles is too much.

I'm not sure even sure about some of the options for wordpress_sites anymore. I wanted to make it configurable but allowing for different users/groups for each one just seems confusing now.

I think at this point it would be useful to open up an issue about the conflict between the admin user and the built-in one some providers have. Do you want to do that? If there's any other issues for AWS, I'd welcome issues for those as well.

Please sign in to comment.