├── .README.MD_images └── why_ed25519.png └── README.MD /.README.MD_images/why_ed25519.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasatome/first-steps-and-hardening-in-ubuntu-server-and-docker/HEAD/.README.MD_images/why_ed25519.png -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # First Steps in Ubuntu Server / Hardening and Config With Docker 2 | 3 | Tested on Digital Ocean; Ubuntu versions 16.04 and 18.04 4 | (So all instructions are executed as ROOT) 5 | 6 | Example In Local Computer: 7 | 8 | [![Commands on local computer](https://asciinema.org/a/jQTQG2bfsRZtN1zwHvgbnP7aL.svg)](https://asciinema.org/a/jQTQG2bfsRZtN1zwHvgbnP7aL) 9 | 10 | 11 | Example In Remote Server: 12 | **SOON** 13 | 14 | 15 | ###### Start with this 16 | First of all configure your timezone and your encodings, this command will also install some utilities 17 | 18 | dpkg-reconfigure tzdata && \ 19 | dpkg-reconfigure locales && \ 20 | apt-get update && \ 21 | apt-get upgrade -y && \ 22 | apt-get install -y git vim wget curl 23 | 24 | 25 | ###### Install BashIt (Only if you do not use zsh or some similar console) 26 | 27 | git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it && ~/.bash_it/install.sh \ 28 | && ln -s ~/.bash_it/plugins/available/history.plugin.bash ~/.bash_it/enabled/250---history.plugin.bash \ 29 | && ln -s ~/.bash_it/plugins/available/history-search.plugin.bash ~/.bash_it/enabled/250---history-search.plugin.bash \ 30 | && ln -s ~/.bash_it/plugins/available/history-substring-search.plugin.bash ~/.bash_it/enabled/250---history-substring-search.plugin.bash \ 31 | && sed -i 's/bobby/sexy/' ~/.bashrc 32 | 33 | > You can change the last line "sexy" by any of the available themes, exit the console and re-enter to apply https://github.com/Bash-it/bash-it/tree/master/themes 34 | 35 | ###### Environments 36 | 37 | It is important that you change these environment variables, with your personal data in Server 38 | 39 | export MY_USERNAME="USERNAMETOCHANGE"; \ 40 | export MY_EMAIL="YOUREMAIL@DOMAINTOCHANGE.COM"; \ 41 | export MY_SERVER_DOMAIN="DOMAINTOCHANGE.COM"; \ 42 | export MY_SERVER_EMAIL_FROM="root@DOMAINTOCHANGE.COM"; \ 43 | export MY_SERVER_SSH_PORT="4444" 44 | For example: 45 | > export MY_USERNAME="nasatome"; 46 | 47 | > export MY_EMAIL="nasatome@mydomain.com"; 48 | 49 | > export MY_SERVER_DOMAIN="mydomain.com"; 50 | 51 | > export MY_SERVER_EMAIL_FROM="root@mydomain.com"; 52 | 53 | > export MY_SERVER_SSH_PORT="4444" 54 | 55 | And in your **local machine** in the file `~/.bashrc`: 56 | copy and paste the following (change for your data): 57 | 58 | export GIT_COMMITTER_NAME='John Connor' 59 | export GIT_COMMITTER_EMAIL='john@gmail.com' 60 | export GIT_AUTHOR_NAME='John Connor' 61 | export GIT_AUTHOR_EMAIL='john@gmail.com' 62 | 63 | You can open it with `vim ~/.bashrc` or with your favorite graphical text editor, 64 | 65 | If you installed BashIt on your local machine, you can install it after the line. `#export GIT_HOSTING='git@git.domain.com'` 66 | 67 | 68 | #### Unattended Upgrades 69 | Security updates are very important on any server. 70 | 71 | > the second command it's an interactive dialog (respond YES and OK) which will create /etc/apt/apt.conf.d/20auto-upgrades 72 | 73 | apt-get install -y unattended-upgrades update-notifier-common && \ 74 | dpkg-reconfigure --priority=low unattended-upgrades && \ 75 | sed -i 's#//Unattended-Upgrade::Mail "root";#Unattended-Upgrade::Mail "'$MY_EMAIL'";#' /etc/apt/apt.conf.d/50unattended-upgrades &&\ 76 | sed -i 's#//Unattended-Upgrade::MailOnlyOnError#Unattended-Upgrade::MailOnlyOnError#' /etc/apt/apt.conf.d/50unattended-upgrades &&\ 77 | sed -i 's#//Unattended-Upgrade::Automatic-Reboot "false";#Unattended-Upgrade::Automatic-Reboot "true";#' /etc/apt/apt.conf.d/50unattended-upgrades &&\ 78 | sed -i 's#//Unattended-Upgrade::Automatic-Reboot-Time "02:00";#Unattended-Upgrade::Automatic-Reboot-Time "04:00";#' /etc/apt/apt.conf.d/50unattended-upgrades 79 | 80 | If you only want to install packages from security sources, run this command: 81 | 82 | sed -i 's# "${distro_id}:${distro_codename}";#// "${distro_id}:${distro_codename}";#' /etc/apt/apt.conf.d/50unattended-upgrades 83 | > this will depend on the level of stability you want in your packages 84 | 85 | 86 | #### Hardening SSH 87 | 88 | Now, we will generate new host keys, as well as a new user and add it to the group of administration privileges. 89 | 90 | > In your **remote machine** 91 | 92 | ssh-keygen -A && \ 93 | adduser $MY_USERNAME && \ 94 | usermod -aG sudo $MY_USERNAME 95 | 96 | > In your **Local Machine** (Laptop o PC With Linux): 97 | 98 | [Why ed25519](https://medium.com/risan/upgrade-your-ssh-key-to-ed25519-c6e8d60d3c54) 99 | 100 | ![](.README.MD_images/why_ed25519.png) 101 | 102 | Now we will create the new key 103 | ``` 104 | ssh-keygen -t ed25519 105 | ``` 106 | we proceed to add it to the key ring 107 | ``` 108 | ssh-add ~/.ssh/id_ed25519 109 | ``` 110 | and we get a public key 111 | ``` 112 | cat ~/.ssh/id_ed25519.pub 113 | ``` 114 | 115 | > Now In **Remote Machine** 116 | 117 | Paste Output in `~/.ssh/authorized_keys` with the following commands 118 | (Remember to copy and paste only the part of the command and not the part of the username): 119 | 120 | from your terminal in root change user 121 | 122 | ROOT #: su $MY_USERNAME 123 | Now create a new folder in case it doesn't exist 124 | 125 | MY_USERNAME $: mkdir ~/.ssh 126 | Now let's change the permissions of the folder, only the owner can modify it 127 | 128 | MY_USERNAME $: chmod 700 ~/.ssh 129 | Now we will create the file where the public key that we obtained previously will be copied. 130 | 131 | MY_USERNAME $: vim ~/.ssh/authorized_keys 132 | 133 | >To use VIM, once inside vim, press the "Ins or Insert" key on your keyboard, until at the bottom right you see a legend that says "-- INSERT --" then paste the contents of the public key you got from your local machine, which you could see when you executed the command "cat ~/.ssh/id_ed25519.pub" above, once the string is pasted, press the ESC key then press the ":" key and then press the "wq" keys visually you should see something like ":wq" which means you can save the file and then exit vim 134 | 135 | Now we will change to the correct permissions to the file that has just been created 136 | 137 | MY_USERNAME $: chmod 600 ~/.ssh/authorized_keys 138 | 139 | >Example to paste: ssh-ed25519 AAAAC3Nzavrg545t64t34rferfdrferodwEv1MMI2+Nh9QxpgrNxb2Is1Cc6 YOURUSERNAME 140 | 141 | Now... Open New Console in your **local machine** and reconect... 142 | 143 | ssh YOUR_USER@YOUR_IP_OR_DOMAIN_DNS 144 | 145 | And... once connected with your user test that you can upgrade your privileges to root 146 | 147 | YOURUSERNAME $: sudo -E su root 148 | 149 | > Now exec this command in root mode (after executing -> sudo -E su root): 150 | 151 | cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak \ 152 | && sed -i 's/Port 22/Port '$MY_SERVER_SSH_PORT'/' /etc/ssh/sshd_config \ 153 | && sed -i 's/#Port '$MY_SERVER_SSH_PORT'/Port '$MY_SERVER_SSH_PORT'/' /etc/ssh/sshd_config \ 154 | && sed -i 's/X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config \ 155 | && sed -i 's/AcceptEnv LANG LC_*/AcceptEnv LANG LC_* GIT_/' /etc/ssh/sshd_config \ 156 | && sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config \ 157 | && sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config \ 158 | && sed -zi 's/DebianBanner yes\|$/DebianBanner no\n/' /etc/ssh/sshd_config \ 159 | && sed -zi 's/ClientAliveInterval 60\|$/ClientAliveInterval 60\n/' /etc/ssh/sshd_config \ 160 | && sed -zi 's/ClientAliveCountMax 600\|$/ClientAliveCountMax 600\n/' /etc/ssh/sshd_config \ 161 | && sed -zi 's/AllowAgentForwarding no\|$/AllowAgentForwarding yes/' /etc/ssh/sshd_config 162 | 163 | 164 | 165 | > And now reload ssh service 166 | 167 | systemctl reload sshd 168 | 169 | > **In local machine** To avoid disconnections exec 170 | 171 | echo "Host *" >> ~/.ssh/config 172 | echo "SendEnv LANG LC_* GIT_*" >> ~/.ssh/config 173 | echo "ServerAliveInterval 60" >> ~/.ssh/config 174 | echo "ServerAliveCountMax 600" >> ~/.ssh/config 175 | echo "ForwardAgent yes" >> ~/.ssh/config 176 | 177 | > And now test: 178 | 179 | cat ~/.ssh/config 180 | 181 | > You should see this exit 182 | 183 | <<---------~/.ssh/config-------------- 184 | Host * 185 | SendEnv LANG LC_* GIT_* 186 | ServerAliveInterval 60 187 | ServerAliveCountMax 600 188 | ForwardAgent yes 189 | <<------------------------------------ 190 | 191 | 192 | > Apart from the global and repository configuration settings, Git reads the environment variables GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL. The idea now is to set them on your machine and take them via SSH on every single machine. 193 | Local setup 194 | 195 | > At first you need to set the variables in your .bashrc file: 196 | 197 | > And now test connection in other **local machine** terminal 198 | 199 | > (Don't forget to change the 4444 to the port you have assigned in your SSH environment variable in remote 7server) 200 | 201 | ssh -p 4444 USERNAMETOCHANGE@YOUR_IP_OR_DOMAIN_DNS 202 | 203 | Now In **remote server**): exec this commands 204 | 205 | sudo -E su root 206 | 207 | and one of these commands 208 | 209 | echo "$SSH_AUTH_SOCK" 210 | ssh-add -L 211 | ssh -T git@github.com 212 | 213 | > Everyone should give you positive outcomes; for example github command response: 214 | 215 | ``` 216 | Hi YOURGITUSERNAME! You've successfully authenticated, but GitHub does not provide shell access. 217 | ``` 218 | 219 | > Remember that you must add your SSH key to your GITHUB account 220 | 221 | `https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/` 222 | 223 | ###### Over Firewalld 224 | firewalld.org 225 | For Ubuntu 18.04 in other VPS Providers, it seems firewalld has become the default where ufw is installed but inactive. 226 | if you execute the following commands you can check if ufw is active 227 | 228 | ``` 229 | root@localhost:~# firewall-cmd --state 230 | running 231 | root@localhost:~# ufw status 232 | Status: inactive 233 | ``` 234 | If this is your case, then these commands can be useful. (otherwise, skip this section) 235 | 236 | firewall-cmd --permanent --zone=public --add-service={http,https} 237 | firewall-cmd --permanent --zone=public --add-port=$MY_SERVER_SSH_PORT/tcp 238 | firewall-cmd --permanent --zone=public --remove-service=ssh --permanent 239 | 240 | firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -j LOG && \ 241 | firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -j LOG && \ 242 | firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -j LOG && \ 243 | firewall-cmd --permanent --direct --add-rule ipv6 filter FORWARD 0 -j LOG 244 | 245 | The `firewall-cmd –reload` command is necessary to activate the change. Contrary to the `–complete-reload` option, current connections are not stopped. 246 | More Info: https://www.certdepot.net/rhel7-get-started-firewalld/ 247 | 248 | 249 | #### Firewall UFW And IPTABLES Persistent 250 | 251 | Install ufw 252 | 253 | apt-get install -y ufw 254 | 255 | Disable incoming ports 256 | 257 | ufw allow $MY_SERVER_SSH_PORT && ufw allow http && ufw allow https && ufw default deny incoming && ufw enable && ufw logging on 258 | 259 | Install iptables-persistent 260 | 261 | apt-get install -y iptables-persistent 262 | 263 | Enable Logs of iptables 264 | 265 | iptables -A INPUT -j LOG \ 266 | && iptables -A FORWARD -j LOG \ 267 | && ip6tables -A INPUT -j LOG \ 268 | && ip6tables -A FORWARD -j LOG 269 | 270 | ###### Network Hardening 271 | 272 | sed -i 's/#net.ipv4.conf.default.rp_filter=1/net.ipv4.conf.default.rp_filter=1/' /etc/sysctl.conf \ 273 | && sed -i 's/#net.ipv4.conf.all.rp_filter=1/net.ipv4.conf.all.rp_filter=1/' /etc/sysctl.conf \ 274 | && sed -i 's/#net.ipv4.conf.all.accept_source_route = 0/net.ipv4.conf.all.accept_source_route = 0/' /etc/sysctl.conf \ 275 | && sed -i 's/#net.ipv6.conf.all.accept_source_route = 0/net.ipv6.conf.all.accept_source_route = 0/' /etc/sysctl.conf \ 276 | && sed -i 's/#net.ipv4.conf.all.send_redirects = 0/net.ipv4.conf.all.send_redirects = 0/' /etc/sysctl.conf \ 277 | && sed -i 's/#net.ipv4.tcp_syncookies=1/net.ipv4.tcp_syncookies=1/' /etc/sysctl.conf \ 278 | && sed -i 's/#net.ipv4.conf.all.log_martians = 1/net.ipv4.conf.all.log_martians = 1/' /etc/sysctl.conf \ 279 | && sed -i 's/#net.ipv4.conf.all.accept_redirects = 0/net.ipv4.conf.all.accept_redirects = 0/' /etc/sysctl.conf \ 280 | && sed -i 's/#net.ipv6.conf.all.accept_redirects = 0/net.ipv6.conf.all.accept_redirects = 0/' /etc/sysctl.conf 281 | 282 | Now Reload With 283 | 284 | sysctl -p 285 | 286 | And Now 287 | 288 | sed -i 's/order hosts,bind/order bind,hosts/' /etc/host.conf 289 | 290 | #### Fail2Ban and PSAD 291 | 292 | Now Install 293 | 294 | apt-get install -y fail2ban psad 295 | 296 | And exec 297 | 298 | sed -i 's/destemail = root@localhost/destemail = '$MY_EMAIL'/' /etc/fail2ban/jail.conf \ 299 | && sed -i 's/sender = root@localhost/sender = '$MY_SERVER_EMAIL_FROM'/' /etc/fail2ban/jail.conf \ 300 | && sed -i 's/action = %(action_)s/action = %(action_mwl)s/' /etc/fail2ban/jail.conf \ 301 | && echo "[nginx-http-auth]" > /etc/fail2ban/jail.local \ 302 | && echo "enabled = false" >> /etc/fail2ban/jail.local \ 303 | && echo "filter = nginx-http-auth" >> /etc/fail2ban/jail.local \ 304 | && echo "port = http,https" >> /etc/fail2ban/jail.local \ 305 | && echo "logpath = %(nginx_error_log)s" >> /etc/fail2ban/jail.local \ 306 | && echo "#logpath = /opt/prj/dockavel/data/log/nginx/error.log" >> /etc/fail2ban/jail.local \ 307 | && echo "" >> /etc/fail2ban/jail.local \ 308 | && echo "[nginx-noscript]" >> /etc/fail2ban/jail.local \ 309 | && echo "enabled = false" >> /etc/fail2ban/jail.local \ 310 | && echo "port = http,https" >> /etc/fail2ban/jail.local \ 311 | && echo "filter = nginx-noscript" >> /etc/fail2ban/jail.local \ 312 | && echo "logpath = %(nginx_error_log)s" >> /etc/fail2ban/jail.local \ 313 | && echo "#logpath = /opt/prj/dockavel/data/log/nginx/error.log" >> /etc/fail2ban/jail.local \ 314 | && echo "maxretry = 6" >> /etc/fail2ban/jail.local \ 315 | && echo "" >> /etc/fail2ban/jail.local \ 316 | && echo "[ssh]" >> /etc/fail2ban/jail.local \ 317 | && echo "enabled = true" >> /etc/fail2ban/jail.local \ 318 | && echo "port = $MY_SERVER_SSH_PORT" >> /etc/fail2ban/jail.local \ 319 | && echo "filter = sshd" >> /etc/fail2ban/jail.local \ 320 | && echo "logpath = /var/log/auth.log" >> /etc/fail2ban/jail.local \ 321 | && echo "maxretry = 4" >> /etc/fail2ban/jail.local \ 322 | && echo "" >> /etc/fail2ban/jail.local \ 323 | && echo "[ssh-ddos]" >> /etc/fail2ban/jail.local \ 324 | && echo "enabled = true" >> /etc/fail2ban/jail.local \ 325 | && echo "port = $MY_SERVER_SSH_PORT" >> /etc/fail2ban/jail.local \ 326 | && echo "filter = sshd-ddos" >> /etc/fail2ban/jail.local \ 327 | && echo "logpath = /var/log/auth.log" >> /etc/fail2ban/jail.local \ 328 | && echo "" >> /etc/fail2ban/jail.local \ 329 | && echo "[Definition]" > /etc/fail2ban/filter.d/nginx-http-auth.conf \ 330 | && echo 'failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: , server: \S+, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"\s*$' >> /etc/fail2ban/filter.d/nginx-http-auth.conf \ 331 | && echo "ignoreregex =" >> /etc/fail2ban/filter.d/nginx-http-auth.conf \ 332 | && echo "[Definition]" > /etc/fail2ban/filter.d/nginx-noscript.conf \ 333 | && echo "failregex = ^ -.*GET.*(\.asp|\.exe|\.pl|\.cgi|\.scgi)" >> /etc/fail2ban/filter.d/nginx-noscript.conf \ 334 | && echo "ignoreregex =" >> /etc/fail2ban/filter.d/nginx-noscript.conf 335 | 336 | > Now restart 337 | 338 | systemctl restart fail2ban 339 | 340 | > For psad config 341 | 342 | sed -i 's/EMAIL_ADDRESSES root@localhost;/EMAIL_ADDRESSES '$MY_EMAIL';/' /etc/psad/psad.conf \ 343 | && sed -i 's/HOSTNAME _CHANGEME_;/HOSTNAME '$MY_SERVER_DOMAIN';/' /etc/psad/psad.conf \ 344 | && sed -i 's/ENABLE_AUTO_IDS N;/ENABLE_AUTO_IDS Y;/' /etc/psad/psad.conf \ 345 | && sed -i 's/DANGER_LEVEL2 15;/DANGER_LEVEL2 15;/' /etc/psad/psad.conf \ 346 | && sed -i 's/DANGER_LEVEL3 150;/DANGER_LEVEL3 30;/' /etc/psad/psad.conf \ 347 | && sed -i 's/DANGER_LEVEL4 1500;/DANGER_LEVEL4 100;/' /etc/psad/psad.conf \ 348 | && sed -i 's/DANGER_LEVEL5 10000;/DANGER_LEVEL5 300;/' /etc/psad/psad.conf \ 349 | && sed -i 's/EMAIL_ALERT_DANGER_LEVEL 1;/EMAIL_ALERT_DANGER_LEVEL 5;/' /etc/psad/psad.conf \ 350 | && sed -i 's/EMAIL_LIMIT 0;/EMAIL_LIMIT 1;/' /etc/psad/psad.conf \ 351 | && sed -i 's/EMAIL_LIMIT_STATUS_MSG Y;/EMAIL_LIMIT_STATUS_MSG N;/' /etc/psad/psad.conf \ 352 | && sed -i 's/ENABLE_AUTO_IDS_EMAILS Y;/ENABLE_AUTO_IDS_EMAILS N;/' /etc/psad/psad.conf \ 353 | && sed -i 's/AUTO_IDS_DANGER_LEVEL 5;/AUTO_IDS_DANGER_LEVEL 1;/' /etc/psad/psad.conf \ 354 | && sed -i 's#IPT_SYSLOG_FILE /var/log/messages;#IPT_SYSLOG_FILE /var/log/syslog;#' /etc/psad/psad.conf 355 | 356 | 357 | Now Update 358 | 359 | psad --sig-update 360 | 361 | And Check 362 | 363 | service rsyslog restart 364 | 365 | In the following command, all outputs with a + sign must appear 366 | 367 | psad --fw-analyze 368 | 369 | Now you can restart the service with any of these commands 370 | 371 | psad -H 372 | psad -R 373 | 374 | ###### Protect/Block TOR IPs 375 | > This only works if you don't open the ports you are going to protect from docker, since docker gives more privilege to the rules from iptables than the ones generated by this script. 376 | 377 | [Block TOR IPs](https://github.com/nasatome/block_tor_net_by_iptables) 378 | 379 | ###### Save IpTables 380 | Now save firewall rules 381 | 382 | netfilter-persistent save 383 | 384 | ##### Install Docker 385 | 386 | Review Script Docker: 387 | 388 | ´https://get.docker.com/´ 389 | 390 | And Exec 391 | 392 | curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh 393 | 394 | (for not production mode) 395 | 396 | curl -fsSL test.docker.com -o get-docker.sh && sh get-docker.sh 397 | 398 | > Can verify with 399 | 400 | docker info 401 | docker version 402 | 403 | > Now test Docker 404 | 405 | docker run hello-world 406 | 407 | #### Install Docker Compose 408 | 409 | > Review last stable release: https://github.com/docker/compose/releases 410 | 411 | Get docker-compose version in ENV variable: 412 | 413 | COMPOSE_VERSION=`git ls-remote https://github.com/docker/compose | grep refs/tags | grep -oP "[0-9]+\.[0-9][0-9]+\.[0-9]+$" | tail -n 1` 414 | 415 | Install docker-compose 416 | 417 | curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && docker-compose --version 418 | 419 | 420 | ###### Check for rootkits 421 | install 422 | 423 | apt-get install rkhunter chkrootkit 424 | 425 | now test 426 | 427 | chkrootkit 428 | 429 | now test 2 430 | 431 | rkhunter --update 432 | rkhunter --propupd 433 | rkhunter --check 434 | 435 | 436 | 437 | 438 | >todo: 439 | 440 | * add to cron: check rootkits with mail 441 | * Tiger and Tripwire 442 | * https://www.digitalocean.com/community/questions/best-practices-for-hardening-new-sever-in-2017 443 | --------------------------------------------------------------------------------