Skip to content

Instantly share code, notes, and snippets.

@mmrwoods
Created February 7, 2012 11:39
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save mmrwoods/1759284 to your computer and use it in GitHub Desktop.
Save mmrwoods/1759284 to your computer and use it in GitHub Desktop.
Make me a new CentOS linode

Make me a new CentOS linode

Notes:

  • Instructions written for CentOS 6.3
  • Change, me, myhost, myip etc. to your username, hostname, ip address and so on.
  • Run all commands as root unless otherwise directed.
  • You might want to look at mounting /var and /home on separate partitions.
  • I've just allowed all members of the wheel group to operate as root. This is the height of laziness and highlights the fact that I'm just a developer that's stolen a sysadmin's woolly hat. It works fine, but the phrase "can do better" springs to mind.

Set the hostname

hostname myhost
mv /etc/sysconfig/network /etc/sysconfig/network.original
sed '/HOSTNAME=/d' /etc/sysconfig/network.original > /etc/sysconfig/network
echo "HOSTNAME=`hostname`" >> /etc/sysconfig/network
echo "`ifconfig eth0 | grep "inet addr" | cut -f2 -d: | cut -f1 -d' '` `hostname`" >> /etc/hosts

Note: add a reverse dns entry too if you plan to run a mail server.

Set the time zone

ln -sf /usr/share/zoneinfo/UTC /etc/localtime
vim /etc/sysconfig/clock

You may want to choose GB to enable daylight saving time.

Set the time

Install ntpd and ntpdate, set the clock using ntpdate and enable the ntpd service:

yum install ntp ntpdate
ntpdate pool.ntp.org
service ntpd start
chkconfig ntpd on

Disable SELinux

Update the configuration to ensure SELinux is disabled on boot:

vim /etc/selinux/config

...

SELINUX=disabled

If it wasn't already disabled, reboot or tell SELinux to stop enforcing now:

shutdown -r now
- OR -
setenforce 0

Add a new admin user

Add the user

useradd -m -G wheel me
passwd me

Edit sudoers

visudo

Uncomment the line that allow users in the wheel group to run all commands:

## Allows people in group wheel to run all commands
%wheel	ALL=(ALL)	ALL

To be on the safe side, because it's too easy to mistakenly remove yourself from a group and lock yourself out from sudo access, uncomment the line that creates an ADMINS user alias, and add yourself to it:

User_Alias ADMINS = me

And add a line below it to allow admins run all commands.

# Allow listed admins to run all commands
ADMINS ALL=(ALL) ALL

Note: you could alternatively just add your username directly to sudoers.

Add your pubkey to authorized keys

Logout of the server, and the use ssh-copy-id to copy your public key to the authorised keys file on the server...

logout
ssh-copy-id me@myip

Note: install the ssh-copy-id utility if necessary

Test ssh and sudo access

You should now be able to log on to the server without a password, and run commands as root via sudo (for which you will be prompted for a password):

ssh me@myip
sudo echo foo
logout

Add a new host to your local ssh config

Run on your workstation:

vim ~/.ssh/config

...

Host myhost
HostName myip
User me

Update sshd config to disable root login and add some other restrictions

vim /etc/ssh/sshd_config

...

PermitRootLogin no
LoginGraceTime 30    
MaxAuthTries 3
MaxStartups 3:50:10

Restart the ssh daemon after making changes:

service sshd restart

Note RE running sshd on a non-standard port - you should do this as a precautionary measure, even though we'll be using either fail2ban or denyhosts to prevent brute-force attacks. Remember to modify the iptables config below to allow your new SSH port though.

Basic firewall configuration

Run the following commands as root to create a basic set of firewall rules that will allow traffic on standard ports for http, https, smtp ftp etc., block anything else, and drop portscans. Note: I think I originally just took these from the following linode wiki page, which might be worth a look as it has some nice comments explaining why the rules are added: http://www.linode.com/wiki/index.php/CentOS_IPTables_sh.

First, flush any existing rules (shouldn't be any, but just in case):

iptables -F

Then create the rules:

iptables -A INPUT -i lo -j ACCEPT 
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 
iptables -A INPUT -s 10.0.0.0/255.0.0.0 -j DROP 
iptables -A INPUT -s 169.254.0.0/255.255.0.0 -j DROP 
iptables -A INPUT -s 172.16.0.0/255.240.0.0 -j DROP 
iptables -A INPUT -s 127.0.0.0/255.0.0.0 -j DROP 
iptables -A INPUT -s 224.0.0.0/240.0.0.0 -j DROP 
iptables -A INPUT -d 224.0.0.0/240.0.0.0 -j DROP 
iptables -A INPUT -s 240.0.0.0/248.0.0.0 -j DROP 
iptables -A INPUT -d 240.0.0.0/248.0.0.0 -j DROP 
iptables -A INPUT -s 0.0.0.0/255.0.0.0 -j DROP 
iptables -A INPUT -d 0.0.0.0/255.0.0.0 -j DROP 
iptables -A INPUT -d 239.255.255.0/255.255.255.0 -j DROP 
iptables -A INPUT -d 255.255.255.255 -j DROP 
iptables -A INPUT -p icmp -m icmp --icmp-type 17 -j DROP 
iptables -A INPUT -p icmp -m icmp --icmp-type 13 -j DROP 
iptables -A INPUT -p icmp -m icmp --icmp-type any -m limit --limit 1/sec -j ACCEPT 
iptables -A INPUT -m state --state INVALID -j DROP 
iptables -A INPUT -p tcp -m tcp --tcp-flags RST RST -m limit --limit 2/sec --limit-burst 2 -j ACCEPT 
iptables -A INPUT -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP 
iptables -A INPUT -m recent --remove --name portscan --rsource 
iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix "Portscan:" 
iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT 
iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable 
iptables -A FORWARD -m state --state INVALID -j DROP 
iptables -A FORWARD -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP 
iptables -A FORWARD -m recent --remove --name portscan --rsource 
iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix "Portscan:" 
iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP 
iptables -A FORWARD -j REJECT --reject-with icmp-port-unreachable 
iptables -A OUTPUT -m state --state INVALID -j DROP
iptables -A OUTPUT -o lo -j ACCEPT 
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 21 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT 
iptables -A OUTPUT -p udp -m udp --dport 43 -j ACCEPT 
iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT 
iptables -A OUTPUT -p udp -m udp --dport 67 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 110 -j ACCEPT
iptables -A OUTPUT -p udp -m udp --dport 123 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 143 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 587 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 993 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 995 -j ACCEPT 
iptables -A OUTPUT -p tcp -m tcp --dport 9418 -j ACCEPT
iptables -A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT 
iptables -A OUTPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT 
iptables -A OUTPUT -j REJECT --reject-with icmp-port-unreachable

Now, check it worked:

iptables -L

And open another terminal and try to ssh to the server.

Save the configuration, so it works after a restart of iptables:

service iptables save

And sanity check the saved file (can't be too cautious):

cat /etc/sysconfig/iptables

Restart iptables and again check that the rules have been applied:

service iptables restart
iptables -L

Note: you may see a failure message when you restart iptables, referring to a "security" table which doesn't exist. This is fine for our purposes, you can ignore it. It's a known issue caused by a security table being defined in the latest linux kernel, which iptables doesn't know about. Google to read more if you're interested.

Finally, make sure iptables starts on boot:

chkconfig iptables on

Apply all security updates

yum update

Install Cron, Ruby, Git, Vim etc.

yum install cronie ruby ruby-devel rubygems git vim-enhanced nc bind-utils

Add Extra Packages for Enterprise Linux (EPEL) repo

A number of required packages are not available from the base CentOS yum repositories. Add the EPEL repo to yum by installing the rpm package provided:

rpm -Uvh http://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

More info about the EPEL project is available at:

http://fedoraproject.org/wiki/EPEL

Some basic improvements to apache security

Note: this is far from exhaustive and just addresses a handful of well-documented issues. Don't assume this means your apache web server is "secure", even if you follow the optional steps to install and configure mod_security. You want "secure", hire an expert with indemnity insurance.

Disable TRACE and OPTIONS HTTP methods by adding the following to the end of the httpd.conf file:

vim /etc/httpd/conf/httpd.conf

...

TraceEnable off
<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteCond %{REQUEST_METHOD} ^OPTIONS
  RewriteRule .* - [F]
</IfModule>

Disable mod_status, mod_info, all proxy modules, all dav modules, all ldap modules and anything else you know you don't need by simply commenting them out.

Restrict the information apache provides with http responses by setting the ServerTokens directive to Product.

ServerTokens Prod

Deny access to files outside /var/www by default. Update the existing Directory directive for the root path, and add a new one for /var/www:

<Directory />
  Order deny,allow
  Deny from all
  Options None
  AllowOverride None
</Directory>

<Directory /var/www>
  Order allow,deny
  Allow from all
  Options FollowSymLinks
  AllowOverride None
</Directory>

Finally, disable SSLv2 and weak encryption ciphers:

vim /etc/httpd/conf.d/ssl.conf

...

SSLProtocol -ALL +SSLv3 +TLSv1

SSLCipherSuite ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM

Check the configuration, then start apache and configure it to start on boot:

apachectl configtest
service httpd start
chkconfig httpd on

Install and configure mod_security (optional, but a good idea)

Ok, so mod_security can cause problems when it rejects things that look suspicious but are perfectly normal within the realm of your web app. However, it really isn't that difficult to tweak the configuration and get things working if you do run into trouble, so you should at least give it a go. Install it via yum:

yum install mod_security

Reload the apache configuration and it should be working:

service httpd reload

Check if you need to fix a bug with the default data directory for mod_security. The default is the the apache log directory, which mod_security may not have permisson to write to (check /var/log/httpd/error_log for confirmation).

vim /etc/httpd/conf.d/mod_security.conf

...

SecDataDir /tmp

See https://bugzilla.redhat.com/show_bug.cgi?id=732450 and https://bugzilla.redhat.com/show_bug.cgi?id=569360 for more info.

For rails applications, I found myself removing a few rules that were causing problems, but which I also knew weren't necessary because of the built in XSS and CSRF protection in rails, and because I set PassengerFriendlyErrorPages off. The simplest solution was just to use the DirectoryMatch directive:

vim /etc/httpd/conf/httpd.conf

...

<DirectoryMatch /var/www/[^/]+/current/public>
  # Reomve some mod_security rules that are causing problems for 
  # rails apps, and are known to be unncessary for rails apps.
  SecRuleRemoveById 90008 # self-executing JavaScript functions
  SecRuleRemoveById 900030 # common XSS concatenation patterns
  SecRuleRemoveById 970901 # response status 5xx
</DirectoryMatch>

If you find yourself running into seemingly unsurmountable problems, you can use the SecRuleEngine directive to disable the rule engine per virtual host. Just update your vhost config and add "SecRuleEngine Off". You could alternatively disable the rule engine by default by editing /etc/httpd/conf.d/mod_security.conf

Install and configure either fail2ban or denyhosts to prevent brute-force attacks

fail2ban

yum install fail2ban

Whitelist at least one remote IP address which will never be banned:

vim /etc/fail2ban/jail.conf

...

ignoreip = 127.0.0.1 officeip homeip etc

Start the daemon

service fail2ban start

And configure it to start on boot

chkconfig fail2ban on

denyhosts

yum install denyhosts

Whitelist at least one remote IP address which will never be banned:

vim /var/lib/denyhosts/allowed-hosts

...

# We mustn't block localhost
127.0.0.1

# my office
officeip

# my house
homeip

Start the daemon

service denyhosts start

And configure it to start on boot

chkconfig denyhosts on

Test the configuration

Test that it works by then creating a number of failed ssh logon attempts. After 5 or so logon attempts (depends on syslog buffering), the server should start to reject tcp connections and the ssh client should appear to hang while it waits to report a connection timeout.

ssh foo@myip <- just keep repeating this until the connection is rejected

Tune some kernel parameters

Edit /etc/sysctl.conf and make sure the following parameters are set. If the entries don't exist, add them:

vim /etc/sysctl.conf

...

# Reboot after a kernel panic
kernel.panic = 10

# Enable TCP SYN Cookie Protection
net.ipv4.tcp_syncookies = 1

# Disable IP Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Disable ICMP Redirect Acceptance
net.ipv4.conf.all.accept_redirects = 0

# Enable IP Spoofing Protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Disable TCP timestamps (information leakage)
net.ipv4.tcp_timestamps = 0

# Don't allow outsiders to alter the routing tables
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# Don't pass traffic between networks or act as a router
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Allow ICMP echo for basic monitoring
net.ipv4.icmp_echo_ignore_all = 0
net.ipv4.icmp_echo_ignore_broadcasts = 0

# Enable Bad Error Message Protection
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

And reload with the new kernel parameters:

sysctl -p /etc/sysctl.conf

Disable IPv6 networking

Most guides to disabling IPv6 tell you to disable the module. Don't do this. Disabling the module can cause problems for other things that depend on it. You've already disabled IPv6 in the kernel, that's sufficient. You can test this for yourself by trying ping6 localhost.

With IPv6 disabled in the kernel, you should update your network configuration to ignore it:

vim /etc/sysconfig/network

...

NETWORKING_IPV6=no

and confirm that the ip6tables daemon is not running and won't run on boot:

service ip6tables stop
chkconfig ip6tables off

If you plan to run postfix to send mail, you'll need to update the config file and tell it not to bother with IPv6:

vim /etc/postfix/main.cf

...

# Enable IPv4, and IPv6 if supported
inet_protocols = ipv4

References

@rentalcustard
Copy link

Needs moar Chef/puppet.

@mmrwoods
Copy link
Author

mmrwoods commented Feb 8, 2012

It's coming, as soon as I have time :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment