Git initial commit

This commit is contained in:
Lars Hahn 2023-08-20 11:13:01 +02:00
commit e319adc501
24 changed files with 790 additions and 0 deletions

9
LICENSE Executable file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

4
README.md Executable file
View File

@ -0,0 +1,4 @@
# cloud-basis
Ansible role to setup basic cloud configuration for a cloud node.
E.g.: mount volumes, setup user, install basic software and so on.

113
defaults/main.yml Executable file
View File

@ -0,0 +1,113 @@
---
## BASIC CONFIG
cloud_update: false
cloud_name: cloud
cloud_home: "/opt/{{ cloud_name }}"
cloud_type: "cloud"
cloud_env: production
cloud_env_path: "{{ cloud_home }}/{{ cloud_env }}"
cloud_host_group: server
cloud_control_version: 1.0.0
cloud_control_name: cloud-control
cloud_git_branch_main: main
cloud_stage: prod
cloud_tzdata: Europe/Berlin
cloud_apps: /opt
cloud_storage: /srv
cloud_python_envs: "{{ cloud_apps }}/pyenv"
cloud_internal_dns: 1.1.1.1
basis_apps:
- passwd
- vim
- unzip
- resolvconf
## HARDWARE
mount_points: []
# - path: /some/path
# dev: /dev/sdb
# fstype: ext4
# opts: noatime
# state: mounted
swap_on: true
swap_file: /swapfile
#block size * block count = swap size (Bytes)
swap_block_size: 1024
swap_block_count: 1048576
## USER + GROUPS
shared_group: "{{ cloud_name }}"
default_groups:
- "ssh"
- "users"
- "cdrom"
- "{{ cloud_shared_group }}"
users:
- name: username
displayname: User Name
shell: /bin/bash
groups:
- sudo
- username
state: present
ssh_key: "ssh-rsa ABCDEF"
## SSH
ssh_port: 22
ssh_configs:
- Protocol 2
- "Port {{ cloud_ssh_port }}"
- PermitRootLogin no
- PubkeyAuthentication yes
- PasswordAuthentication no
- PermitEmptyPasswords no
## FAIL2BAN
fail2ban_bantime: 1h
fail2ban_maxretry: 3
fail2ban_nginx_selfmade_filter:
- nginx-noscript
- nginx-nohome
- nginx-noproxy
fail2ban_nginx_default_filter:
- nginx-limit-req
- nginx-botsearch
fail2ban_activate_modules:
- sshd
- nginx
## WIREGUARD
wireguard_enabled: True
wireguard_is_gateway: False
wireguard_allow_adjacent_client_traffic: False
wireguard_keepalive: 25
wireguard_gateway_interface: eth0
wireguard_gateway_host: my-wireguard-server.tld
wireguard_gateway_port: 51820
wireguard_gateway_net_prefix: 10.10.10
wireguard_gateway_net_cidr: 28
wireguard_gateway_public_key: your-public-key
wireguard_gateway_private_key: your-privat-key
wireguard_gateway_forward: []
# - server_port: 22
# client_port: "{{ ssh_port }}"
# client_index: 0
wireguard_client_host: my-wireguard-client
wireguard_clients:
# my-wireguard-client:
# index: 0
# public_key: my-public-key
# private_key: my-private-key

19
handlers/main.yml Executable file
View File

@ -0,0 +1,19 @@
---
- name: restart sshd service
service:
name: sshd
state: restarted
enabled: yes
- name: restart fail2ban service
service:
name: fail2ban
state: restarted
enabled: yes
- name: restart wireguard service
service:
name: wg-quick@{{ cloud_name }}
state: restarted
enabled: yes
when: wireguard_installed is defined and not wireguard_installed.changed

16
meta/main.yml Executable file
View File

@ -0,0 +1,16 @@
---
galaxy_info:
role_name: basis
namespace: hahn-cloud
author: Lars Hahn
company: OpenDevChain
license: MIT
description: Basis role to setup a basic system with ansible; e.g. Users, Fail2Ban, SSHD
min_ansible_version: 2.7
platforms:
- name: Debian
versions:
- 10
galaxy_tags:
- basis
dependencies: []

8
tasks/cloud_control.yml Executable file
View File

@ -0,0 +1,8 @@
---
- name: setup Cloud Control script
template:
src: "templates{{ cloud_control_path }}/cloud-control.j2"
dest: "{{ cloud_control_path }}/{{ cloud_control_name }}"
owner: root
group: root
mode: 0754

30
tasks/fail2ban.yml Executable file
View File

@ -0,0 +1,30 @@
---
- name: install fail2ban service
apt:
update_cache: yes
state: "{% if cloud_update | bool %}latest{% else %}present{% endif %}"
install_recommends: yes
pkg: fail2ban
- name: setup fail2ban config jail.local
template:
backup: yes
src: "templates{{ fail2ban_jail_conf }}.j2"
dest: "{{ fail2ban_jail_conf }}"
owner: root
group: root
mode: 0644
notify: restart fail2ban service
- name: Install fail2ban filter plugins nginx
template:
src: "templates{{ fail2ban_path }}/filter.d/{{ filter }}.local.j2"
dest: "{{ fail2ban_path }}/filter.d/{{ filter }}.local"
owner: root
group: root
mode: 0644
loop: "{{ fail2ban_nginx_filter }}"
loop_control:
label: "{{ filter }}"
loop_var: filter
notify: restart fail2ban service

64
tasks/main.yml Executable file
View File

@ -0,0 +1,64 @@
---
- name: Limit access to cloude home only for root
file:
state: directory
path: "{{ cloud_home }}"
owner: root
group: root
mode: 0700
recurse: yes
- name: Install basic apps via apt
apt:
update_cache: yes
autoclean: yes
autoremove: yes
state: latest
install_recommends: yes
pkg: "{{ basis_apps }}"
- name: set timezone
timezone:
name: "{{ cloud_tzdata }}"
- name: Setup hahn-cloud users
import_tasks: users.yml
- name: Setup and configure sshd service
import_tasks: sshd.yml
- name: Setup and configure fail2ban service
import_tasks: fail2ban.yml
- name: Setup and configure cloud control script
import_tasks: cloud_control.yml
- name: Setup mount points
import_tasks: mount.yml
- name: Setup swap
import_tasks: swap.yml
when: swap_on
- name: Setup basic cloud folders for apps and storage
file:
state: directory
path: "{{ item }}"
mode: 0755
owner: root
group: "{{ shared_group }}"
loop:
- "{{ cloud_apps }}"
- "{{ cloud_storage }}"
- name: Setup shared python environment for all cloud users
file:
state: directory
path: "{{ cloud_python_envs }}"
mode: 0755
owner: root
group: "{{ shared_group }}"
- name: Setup wireguard vpn
import_tasks: wireguard.yml
when: wireguard_enabled

20
tasks/mount.yml Executable file
View File

@ -0,0 +1,20 @@
- name: generate filesystem on mounts if not defined
filesystem:
fstype: "{{ mnt.fstype | default('ext4') }}"
dev: "{{ mnt.dev }}"
loop: "{{ mount_points }}"
loop_control:
loop_var: mnt
label: "{{ mnt.dev }}: {{ mnt.fstype | default('ext4') }}"
- name: mount required points
mount:
path: "{{ mnt.path }}"
src: "{{ mnt.dev }}"
state: "{{ mnt.state | default('mounted') }}"
fstype: "{{ mnt.fstype | default('ext4') }}"
opts: "{{ mnt.opts | default('defaults') }}"
loop: "{{ mount_points }}"
loop_control:
loop_var: mnt
label: "{{ mnt.dev }}: {{ mnt.path }}"

21
tasks/sshd.yml Executable file
View File

@ -0,0 +1,21 @@
---
- name: install OpenSSH server
apt:
update_cache: yes
state: "{% if cloud_update | bool %}latest{% else %}present{% endif %}"
install_recommends: yes
pkg: openssh-server
- name: setup sshd_config
lineinfile:
path: "{{ sshd_conf }}"
regexp: '^#?{{ configline.split(" ")[0] }}{% if configline.split(" ") | length > 1 %} {% endif %}'
line: "{{ configline }}"
owner: root
group: root
mode: 0644
loop: "{{ ssh_configs }}"
loop_control:
loop_var: configline
label: "{{ configline }}"
notify: restart sshd service

35
tasks/swap.yml Executable file
View File

@ -0,0 +1,35 @@
---
- name: generate file for swapping
command:
cmd: "dd if=/dev/zero of={{ swap_file }} bs={{ swap_block_size }} count={{ swap_block_count }}"
chdir: "/"
creates: "{{ swap_file }}"
register: swapfile_setup
- name: prepare swap file
command:
cmd: "mkswap {{ swap_file }}"
chdir: "/"
when: swapfile_setup.changed
register: swapfile_creation
- name: set swap permission
file:
path: "{{ swap_file }}"
mode: 0600
- name: activate swap
command:
cmd: "swapon {{ swap_file }}"
chdir: "/"
when: swapfile_creation.changed
- name: mount swap on boot
mount:
path: none
src: "{{ swap_file }}"
fstype: swap
opts: sw
passno: 0
dump: 0
state: present

47
tasks/users.yml Executable file
View File

@ -0,0 +1,47 @@
---
- name: list active users
shell: cat /etc/passwd | grep -v "nologin" | cut -f 1 -d ":"
changed_when: false
register: active_users
- name: Setup required groups of users
group:
name: "{{ group }}"
state: present
loop: "{{ ((users | json_query('[*].groups') | flatten) + default_groups) | unique }}"
loop_control:
loop_var: group
label: "{{ group }}"
- name: Setup users
user:
name: "{{ user.name }}"
state: "{{ user.state }}"
comment: "{{ user.displayname }}"
group: "{{ user.name }}"
groups: "{{ default_groups | default([]) + user.groups }}"
append: yes
shell: "{{ user.shell }}"
loop: "{{ users }}"
loop_control:
loop_var: user
label: "{{ user.name }}"
register: new_user_setup
- name: Setup User ssh_key
authorized_key:
user: "{{ user.name }}"
state: "{{ user.state | default('present') }}"
key: "{{ user.ssh_key }}"
loop: "{{ users | selectattr('ssh_key', 'defined') | list }}"
loop_control:
label: "{{ user.name }}"
loop_var: user
- name: Activate password setup for newly created users
shell: " set -e pipefail; passwd -d {{ user }}; chage -d0 {{ user }}"
loop: "{{ new_user_setup | json_query('results[?changed==`true`].name') | difference(active_users.stdout_lines) }}"
loop_control:
label: "{{ user }}"
loop_var: user
when: new_user_setup.changed

34
tasks/wireguard.yml Normal file
View File

@ -0,0 +1,34 @@
---
- name: install fail2ban service
apt:
update_cache: yes
state: "{% if cloud_update | bool %}latest{% else %}present{% endif %}"
install_recommends: yes
pkg: wireguard
register: wireguard_installed
- name: setup key files
template:
src: "etc/wireguard/{{ item }}.j2"
dest: "/etc/wireguard/{{ item }}"
owner: root
mode: 0600
loop:
- private.key
- public.key
notify: restart wireguard service
- name: setup wireguard config
template:
src: "etc/wireguard/wireguard-{% if wireguard_is_gateway %}server{% else %}client{% endif %}.conf.j2"
dest: "/etc/wireguard/{{ cloud_name }}.conf"
owner: root
mode: 0600
notify: restart wireguard service
- name: enable wireguard systemd unit
systemd:
name: wg-quick@{{ cloud_name }}
enabled: yes
daemon_reload: yes
state: started

View File

@ -0,0 +1,5 @@
[Definition]
failregex = ^<HOST> -.*GET .*/~.*
ignoreregex =

View File

@ -0,0 +1,5 @@
[Definition]
failregex = ^<HOST> -.*GET http.*
ignoreregex =

View File

@ -0,0 +1,5 @@
[Definition]
failregex = ^<HOST> -.*GET.*(\.asp|\.exe|\.pl|\.cgi|\.scgi)
ignoreregex =

View File

@ -0,0 +1,34 @@
[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = {{ fail2ban_bantime }}
maxretry = {{ fail2ban_maxretry }}
[sshd]
enabled = true
port = 22,{{ ssh_port }},ssh
[nginx-http-auth]
enabled = {{ 'nginx' in fail2ban_activate_modules }}
port = http,https
filter = nginx-noscript
logpath = %(nginx_error_log)s
maxretry = 6
{% for filter in fail2ban_nginx_default_filter %}
[{{ filter }}]
enabled = {{ 'nginx' in fail2ban_activate_modules }}
port = http,https
filter = {{ filter }}
logpath = %(nginx_access_log)s
maxretry = 2
{% endfor %}
{% for filter in fail2ban_nginx_selfmade_filter %}
[{{ filter }}]
enabled = {{ 'nginx' in fail2ban_activate_modules }}
port = http,https
filter = {{ filter }}
logpath = %(nginx_access_log)s
maxretry = 2
{% endfor %}

121
templates/etc/ssh/sshd_config.j2 Executable file
View File

@ -0,0 +1,121 @@
# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
Protocol {{ ssh_protocol_version }}
Port {{ ssh_port }}
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 10m
PermitRootLogin {{ ssh_login_root }}
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
PubkeyAuthentication {{ ssh_use_key }}
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication {{ ssh_use_password }}
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server

View File

@ -0,0 +1 @@
{% if not wireguard_is_gateway %}{{ wireguard_clients[wireguard_client_host].private_key }}{% else %}{{ wireguard_gateway_private_key }}{% endif %}

View File

@ -0,0 +1 @@
{% if not wireguard_is_gateway %}{{ wireguard_clients[wireguard_client_host].public_key }}{% else %}{{ wireguard_gateway_public_key }}{% endif %}

View File

@ -0,0 +1,11 @@
[Interface]
Address = {{ wireguard_gateway_net_prefix }}.{{ wireguard_clients[wireguard_client_host].index }}/32
PrivateKey = {{ wireguard_clients[wireguard_client_host].private_key }}
DNS = {{ cloud_internal_dns }}
[Peer]
PublicKey = {{ wireguard_gateway_public_key }}
Endpoint = {{ wireguard_gateway_host }}:{{ wireguard_gateway_port }}
AllowedIPs = {{ wireguard_gateway_net_prefix }}.1/{% if wireguard_allow_adjacent_client_traffic %}{{ wireguard_gateway_net_cidr }}{% else %}32{% endif %}
PersistentKeepalive = {{ wireguard_keepalive }}

View File

@ -0,0 +1,25 @@
[Interface]
Address = {{ wireguard_gateway_net_prefix }}.1/{{ wireguard_gateway_net_cidr }}
ListenPort = {{ wireguard_gateway_port }}
PrivateKey = {{ wireguard_gateway_private_key }}
{% if wireguard_gateway_forward is defined and wireguard_gateway_forward | length > 0 %}
PreUp = sysctl -w net.ipv4.ip_forward=1
PreUp = sysctl -w net.ipv6.conf.all.forwarding=1
{% for config in wireguard_gateway_forward %}
PreUp = iptables -t nat -A PREROUTING -i {{ wireguard_gateway_interface }} -p tcp --dport {{ config.server_port }} -j DNAT --to-destination {{ wireguard_gateway_net_prefix }}.{{ config.client_index }}:{{ config.client_port }}
PostDown = iptables -t nat -D PREROUTING -i {{ wireguard_gateway_interface }} -p tcp --dport {{ config.server_port }} -j DNAT --to-destination {{ wireguard_gateway_net_prefix }}.{{ config.client_index }}:{{ config.client_port }}
{% endfor %}
PreUp = iptables -t nat -A POSTROUTING -o {{ cloud_name }} -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o {{ cloud_name }} -j MASQUERADE
{% endif %}
{% for client in wireguard_clients %}
## Wireguard {{ cloud_name }} - {{ client }} ##
[Peer]
PublicKey = {{ wireguard_clients[client].public_key }}
AllowedIPs = {{ wireguard_gateway_net_prefix }}.{{ wireguard_clients[client].index }}/32
{% endfor %}

View File

@ -0,0 +1,155 @@
#!/bin/bash
set -e
### VARIABLE ###################################################################
environment_folder={{ cloud_env_path }}
environment="{{ cloud_env }}"
host_type="{{ cloud_host_group }}"
script_name=$(basename $0)
cloud_name="{{ cloud_name }}"
cloud_type="{{ cloud_type }}"
version="{{ cloud_control_version }}"
branch_main="{{ cloud_git_branch_main }}"
################################################################################
### FUNCTION ###################################################################
to_working_directory() {
if [ ! -d $environment_folder/.git ] || [ ! -d $environment_folder ]; then
echo "Environment '$environment' in '$environment_folder' not available or folder not a git repository! Abort."
exit 1
fi
cd $environment_folder/$environment
}
help() {
echo "$script_name, version $version by L.Hahn"
echo ""
echo " $cloud_name script for cloud control"
echo " You can checkout environment (branches), rollout configurations,"
echo " run ansible and restore entire configurations."
echo ""
echo "Usage: $script_name [command] [options]"
echo ""
echo "commands:"
echo " - help print this help"
echo " - maintenance setup local server into maintenance mode; no automatic ansible call"
echo " - environment"
echo " download <branch> checkout <branch> from remote repository"
echo " update load latest remote changes for current branch"
echo " reset stash changes and reset current branch from remote repository with latest changes"
echo " restore checkout latest $branch_main branch from remote repository"
echo " - update get latest roles according to environment requirements.yml"
echo " - play play current loaded ansible playbooks"
echo " - reset perform 1. environment restore, 2. update, 3. execute"
echo ""
echo ""
echo "example:"
echo "~# $script_name environment update"
echo " this will download changes from the currently active remote branch"
}
environment() {
to_working_directory
current_branch=$(git branch | grep "^\*" | cut -d " " -f 2)
current_upstream=$(git rev-parse $current_branch@{upstream})
env_option=$1
case $env_option in
"update")
echo "### Updating branch '$current_branch' in $environment_folder ###"
git pull
;;
"reset")
echo "### Resetting branch '$current_branch' in $environment_folder ###"
git reset --hard $current_branch
git pull
;;
"restore")
echo "### Restoring branch '$branch_main' in $environment_folder ###"
git reset --hard HEAD
git clean -f
git checkout $branch_main
git pull
;;
"download")
if [ $# -lt 2 ]; then
echo "Missing branch name for environment downloading"
exit 1
fi
echo "### Stashing branch '$current_branch' & downloading branch '$2' in $environment_folder ###"
git stash
git checkout -b $2 origin/$2
git pull
;;
*)
echo "Unknown environments option '$env_option', abort!"
exit 1
;;
esac
}
maintenance() {
to_working_directory
echo "maint"
}
update() {
to_working_directory
ansible-galaxy install -f -p roles/ -r requirements.yml
}
play() {
to_working_directory
ansible-playbook $cloud_type"-"$host_type".yml"
}
################################################################################
### MAIN #######################################################################
if [ $# -eq 0 ]; then
help
exit 1
fi
script_command=$1
case $script_command in
"help")
help
;;
"maintenance")
maintenance
;;
"environment")
if [ $# -lt 2 ]; then
echo "ERROR! environment command needs options! None provided."
echo "Call '~# $script_name help' for more information."
exit 1
fi
environment $2 $3
;;
"update")
update
;;
"play")
play
;;
"reset")
echo "#=== restore environment ===#"
environment restore
echo ""
echo "#=== update roles ===#"
update
echo ""
echo "#=== play playbook ===#"
play
echo ""
;;
*)
echo "Unknown command '$script_command', abort!"
echo "Call '~# $script_name help' for more information."
;;
esac
################################################################################

7
vars/main.yml Executable file
View File

@ -0,0 +1,7 @@
---
sshd_path: "/etc/ssh"
sshd_conf: "{{ sshd_path }}/sshd_config"
fail2ban_path: "/etc/fail2ban"
fail2ban_jail_conf: "{{ fail2ban_path }}/jail.local"
cloud_control_path: "/usr/local/bin"