Nowadays deployments have moved from bare-metal servers to virtual machines that are quicker to start, e.g. the one provided by Amazon, Digital Ocean, and OpenStack-based providers. Thus, developers are no longer required to go through manual administration steps when configuring an Ubuntu box.
One of the ways you can get started is to use a ready-to-use pre-configured box images.
Another approach is to do an initial system restart and provision it according to your project needs with some provisioner tool like Ansible, Chef or Puppet.
If you decide to proceed with custom provisioning, your first step would be to perform basic box securing, as in some cases you are given with if you have a freshly-installed box with a root password.
Let me share with you quick recipe on initial box securing, which should be good for most of web deployments.
At the end of the article, we should be able secure an Ubuntu 14.04 LTS virtual server
Ansible comes with a nice concept of reusing deployment snippets, called roles. So let’s take a look, what sa-box-bootstrap role does:
Following variables might be overwritten: - root_dir - required, Ansible developer recipes repository - option_enforce_ssh_keys_login (true|false) - whenever to enforce ssh security - ufw_rules_default - default firewall policy. In most cases is not touched - ufw_rules_allow - set of inbound rules to be configured - sshd_config_lines - needed changes in SSHD config to secure the box. - option_file2ban - when true, file2ban package will additionally introduced - whitelistedips - set of ips that are considered safe - your office gateway, build server etc; To prevent you being accidentaly blocked
1-st step install and configure ufw firewall:
- include: "{{root_dir}}/tasks_ufw.yml"
by default, following firewall rules apply (outgoing any, http https & ssh are allowed inside):
ufw_rules_default:
- {
policy: deny,
direction: incoming
}
- {
policy: allow,
direction: outgoing
}
ufw_rules_allow:
- {
port: 80,
proto: tcp
}
- {
port: 443,
proto: tcp
}
- {
port: 22,
proto: tcp
}
You can override these variables to match your needs.
If you intend to work & provision this box, you most likely won’t want
to do it under the root. Thus, your second step would be to create a
deploy_user
that is authorized by a set of provided SSH keys and
allowed to become sudoer without a password (this is the base
requirement for automated provisioning)
- include: "{{root_dir}}/use/__create_deploy_user.yml user={{deploy_user}} group={{deploy_user}} home=/home/{{deploy_user}}"
when: deploy_user is defined
- name: SSH | Authorize keys
authorized_key: user={{deploy_user}} key="{{ lookup('file', item) }}"
when: deploy_user_keys is defined
with_items: "{{deploy_user_keys}}"
sudo: yes
You might define the user in your playbook, for example, in this way:
jenkins_user: jenkins
jenkins_authorized_keys:
- "{{playbook_dir}}/components/files/ssh/vyacheslav.pub"
and later pass this as a parameter to a role:
roles:
- {
role: "sa-box-bootstrap",
root_dir: "{{playbook_dir}}/public/ansible_developer_recipes",
deploy_user: "{{jenkins_user}}",
deploy_user_keys: "{{jenkins_authorized_keys}}"
}
- name: SSH | Enforce SSH keys security
lineinfile: dest=/etc/ssh/sshd_config regexp="{{item.regexp}}" line="{{item.line}}"
with_items: sshd_config_lines
when: option_enforce_ssh_keys_login
sudo: yes
tags: ssh
If var option_enforce_ssh_keys_login
is set to true, sshd config
will be modified according to sshd_config_lines
rules. By default,
it will use the v2 protocol, prohibit root login, and prohibit password
authenticaton.
If var option_fail2ban
is set to true and the special tool fail2ban
is installed, it will watch out for failure SSH logging attempts and ban
intruders. To prevent yourself from being accidentally blocked, it will
be a good idea to whitelist your IPs. Both single IPs and network masks
are supported, for example:
whitelistedips:
- 127.0.0.1
- 127.0.0.1/8
Let’s prepare a basic bootstrap project that can be used in the future. It includes following files:
.gitmodules
git syntax and
specifies a list of the dependencies that will be used by the
playbook. In particular, it includes ansible–by default
developer_recipes (repository with set of handy deployment
recipes)– and an ansible role called sa-box-bootstrap responsible
for box securing steps.[submodule "public/ansible_developer_recipes"]
path = public/ansible_developer_recipes
url = git@github.com:Voronenko/ansible-developer_recipes.git
[submodule "roles/sa-box-bootstrap"]
path = roles/sa-box-bootstrap
url = git@github.com:softasap/sa-box-bootstrap.git
hosts - here’s a list of the initial box credentials that were provided to you for the server
[bootstrap]
box_bootstrap ansible_ssh_host=192.168.0.17 ansible_ssh_user=your_user ansible_ssh_pass=your_password
box_vars.yml - set here specific environment overrides, like your preffered deploy user name and keys.
box_bootstrap.yml - here you put your box provisioning steps. Box securing is only the first step. In order, to override params for sa-box-bootstrap - pass the parameters like in example below.
- hosts: all
vars_files:
- ./box_vars.yml
roles:
- {
role: "sa-box-bootstrap",
root_dir: "{{playbook_dir}}/public/ansible_developer_recipes",
deploy_user: "{{my_deploy_user}}",
deploy_user_keys: "{{my_deploy_authorized_keys}}"
}
The code I’m using can be downloaded from the repository
https://github.com/Voronenko/devops-bootstrap-box-template Fork it,
adjust parameters to your needs, and use. Adjustments include: creation
of a box_vars.yml
file. You can override any of the above-mentioned
variables. The minimal required set is deploy_user
and your public
keys.
box_deploy_user: jenkins
box_deploy_authorized_keys:
- "{{playbook_dir}}/components/files/ssh/vyacheslav.pub"
Ensure you have ansible (bootstrap.sh
to install) and cloned roles
directories (init.sh), then run setup.sh. If everything is configured
correctly, you will see something like:
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [box_bootstrap]
TASK: [sa-box-bootstrap | Sets correctly hostname] ****************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | debug var="ufw_rules_allow"] ************************
ok: [box_bootstrap] => {
"var": {
"ufw_rules_allow": [
{
"port": 80,
"proto": "tcp"
},
{
"port": 443,
"proto": "tcp"
},
{
"port": 22,
"proto": "tcp"
}
]
}
}
TASK: [sa-box-bootstrap | UFW | Reset it] *************************************
ok: [box_bootstrap]
TASK: [sa-box-bootstrap | UFW | Configure incoming/outgoing defaults] *********
ok: [box_bootstrap] => (item={'policy': 'deny', 'direction': 'incoming'})
ok: [box_bootstrap] => (item={'policy': 'allow', 'direction': 'outgoing'})
TASK: [sa-box-bootstrap | UFW | Configure rules to allow incoming traffic] ****
ok: [box_bootstrap] => (item={'port': 80, 'proto': 'tcp'})
ok: [box_bootstrap] => (item={'port': 443, 'proto': 'tcp'})
ok: [box_bootstrap] => (item={'port': 22, 'proto': 'tcp'})
TASK: [sa-box-bootstrap | UFW | Configure rules to allow incoming traffic from specific hosts] ***
skipping: [box_bootstrap] => (item=ufw_rules_allow_from_hosts)
TASK: [sa-box-bootstrap | UFW | Enable it] ************************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Monit | Check if is installed] **********************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Monit | libssl-dev dependency] **********************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Monit | Download] ***********************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Monit | Install] ************************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | debug msg="Creating deploy user {{my_deploy_user}}:{{my_deploy_user}} with home directory /home/{{my_deploy_user}}"] ***
ok: [box_bootstrap] => {
"msg": "Creating deploy user jenkins:jenkins with home directory /home/jenkins"
}
TASK: [sa-box-bootstrap | Deploy User | Creating group] ***********************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Deploy User | Creating user] ************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Deploy User | Check key presence] *******************
ok: [box_bootstrap]
TASK: [sa-box-bootstrap | Deploy User | Copy authorized_keys from {{ansible_user_id}}] ***
skipping: [box_bootstrap]
TASK: [sa-box-bootstrap | Deploy User | Set permission on authorized_keys] ****
skipping: [box_bootstrap]
TASK: [sa-box-bootstrap | Deploy User | Ensuring sudoers no pwd prompting] ****
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | SSH | Authorize keys] *******************************
changed: [box_bootstrap] => (item=/home/slavko/labs/devops-bootstrap-box-template/components/files/ssh/vyacheslav.pub)
TASK: [sa-box-bootstrap | SSH | Enforce SSH keys security] ********************
ok: [box_bootstrap] => (item={'regexp': '^Protocol.*', 'line': 'Protocol 2'})
changed: [box_bootstrap] => (item={'regexp': '^PermitRootLogin.*', 'line': 'PermitRootLogin no'})
ok: [box_bootstrap] => (item={'regexp': '^RSAAuthentication.*', 'line': 'RSAAuthentication yes'})
ok: [box_bootstrap] => (item={'regexp': '^PubkeyAuthentication.*', 'line': 'PubkeyAuthentication yes'})
ok: [box_bootstrap] => (item={'regexp': '^ChallengeResponseAuthentication.*', 'line': 'ChallengeResponseAuthentication no'})
changed: [box_bootstrap] => (item={'regexp': '^PasswordAuthentication.*', 'line': 'PasswordAuthentication no'})
changed: [box_bootstrap] => (item={'regexp': '^MaxAuthTries.*', 'line': 'MaxAuthTries 3'})
TASK: [sa-box-bootstrap | SSH | Restart SSHD] *********************************
changed: [box_bootstrap]
TASK: [sa-box-bootstrap | Install base Ubuntu packages] ***********************
changed: [box_bootstrap] => (item=unzip,mc)
PLAY RECAP ********************************************************************
box_bootstrap : ok=21 changed=13 unreachable=0 failed=0
Finally, you’ll have a secured box with the sudoer (deployed user) you specified, which is allowed to authorize only with keys you set. Root is not allowed to login. Only some inbound ports are allowed according to your rules.
Check with NMap and try to login:
ssh 192.168.0.17
Permission denied (publickey).
ssh -ldeploy_user 192.168.0.17
Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.13.0-32-generic x86_64)
deploy_user@LABBOX17:~$
You can reuse this playbook to create your own box bootstrapping projects, and reuse the role to configure your environments in a quicker and more secure way with ansible