aboutsummaryrefslogtreecommitdiffstats
path: root/roles/archive_server
diff options
context:
space:
mode:
Diffstat (limited to 'roles/archive_server')
-rw-r--r--roles/archive_server/defaults/main.yml4
-rw-r--r--roles/archive_server/files/usr/local/libexec/archiver/archive_edgeswitch36
-rw-r--r--roles/archive_server/files/usr/local/libexec/archiver/archive_opnsense12
-rw-r--r--roles/archive_server/tasks/freeipa.yml51
-rw-r--r--roles/archive_server/tasks/main.yml79
-rw-r--r--roles/archive_server/templates/etc/archiver.conf.j216
-rw-r--r--roles/archive_server/templates/usr/local/bin/archiver.sh.j299
-rw-r--r--roles/archive_server/vars/main.yml22
8 files changed, 319 insertions, 0 deletions
diff --git a/roles/archive_server/defaults/main.yml b/roles/archive_server/defaults/main.yml
new file mode 100644
index 0000000..150a652
--- /dev/null
+++ b/roles/archive_server/defaults/main.yml
@@ -0,0 +1,4 @@
+archive_dest_path: /nfs/archive
+archive_user: s-archiver
+archive_on_calendar: '*-*-* 23:00:00'
+archive_retention_days: 365
diff --git a/roles/archive_server/files/usr/local/libexec/archiver/archive_edgeswitch b/roles/archive_server/files/usr/local/libexec/archiver/archive_edgeswitch
new file mode 100644
index 0000000..43979de
--- /dev/null
+++ b/roles/archive_server/files/usr/local/libexec/archiver/archive_edgeswitch
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+set -Eeu -o pipefail
+
+HOST=$1
+USERNAME=$2
+PASSWORD=$3
+
+TIMESTAMP=$(date +%Y%m%d%H%M%S)
+COOKIE_JAR=$(mktemp -t archiver-XXXXXX)
+
+trap 'rm -f "$COOKIE_JAR"' EXIT
+
+curl -sSfk \
+ -o /dev/null \
+ -c "$COOKIE_JAR" \
+ -H "Referer: https://${HOST}/htdocs/login/login.lsp" \
+ --data-urlencode "username=${USERNAME}" \
+ --data-urlencode "password=${PASSWORD}" \
+ -d 'accept_eula=0&require_eula=0' \
+ "https://${HOST}/htdocs/login/login.lua"
+
+curl -sSfk \
+ -o /dev/null \
+ -c "$COOKIE_JAR" \
+ -b "$COOKIE_JAR" \
+ -H "Referer: https://${HOST}/htdocs/pages/base/file_upload_modal.lsp?filetypes=6&protocol=6" \
+ --data-urlencode 'file_type_sel[]=config' \
+ "https://${HOST}/htdocs/lua/ajax/file_upload_ajax.lua?protocol=6"
+
+curl -sSfk \
+ -o "config-${TIMESTAMP}.scr" \
+ -c "$COOKIE_JAR" \
+ -b "$COOKIE_JAR" \
+ -H "Referer: https://${HOST}/htdocs/pages/base/file_upload_modal.lsp?filetypes=6&protocol=6" \
+ "https://${HOST}/htdocs/pages/base/http_download_file.lua?filepath=/mnt/download/TempConfigScript.scr"
diff --git a/roles/archive_server/files/usr/local/libexec/archiver/archive_opnsense b/roles/archive_server/files/usr/local/libexec/archiver/archive_opnsense
new file mode 100644
index 0000000..a51a068
--- /dev/null
+++ b/roles/archive_server/files/usr/local/libexec/archiver/archive_opnsense
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -Eeu -o pipefail
+
+HOST=$1
+KEY=$2
+SECRET=$3
+
+URL=https://${HOST}/api/backup/backup/download
+TIMESTAMP=$(date +%Y%m%d%H%M%S)
+
+curl -sSfk -u "${KEY}:${SECRET}" -o "opnsense-${TIMESTAMP}.xml" "$URL"
diff --git a/roles/archive_server/tasks/freeipa.yml b/roles/archive_server/tasks/freeipa.yml
new file mode 100644
index 0000000..f0920f3
--- /dev/null
+++ b/roles/archive_server/tasks/freeipa.yml
@@ -0,0 +1,51 @@
+- name: create freeipa user
+ ipauser:
+ ipaadmin_principal: '{{ ipa_user }}'
+ ipaadmin_password: '{{ ipa_pass }}'
+ name: '{{ archive_user }}'
+ loginshell: /bin/bash
+ homedir: '{{ archive_home }}'
+ givenname: archive
+ sn: Service Account
+ state: present
+ run_once: True
+
+- name: create archive-clients hostgroup
+ ipahostgroup:
+ ipaadmin_principal: '{{ ipa_user }}'
+ ipaadmin_password: '{{ ipa_pass }}'
+ name: '{{ archive_clients_hbac_hostgroup }}'
+ description: Archive Clients
+ state: present
+ run_once: True
+
+- name: create HBAC rule for ssh
+ ipahbacrule:
+ ipaadmin_principal: '{{ ipa_user }}'
+ ipaadmin_password: '{{ ipa_pass }}'
+ name: archive_ssh_to_archive_clients
+ description: Allow archive user to ssh to archive clients
+ user:
+ - '{{ archive_user }}'
+ hostgroup:
+ - '{{ archive_clients_hbac_hostgroup }}'
+ hbacsvc: sshd
+ run_once: True
+
+- name: retrieve user keytab
+ include_role:
+ name: freeipa_keytab
+ vars:
+ keytab_principal: '{{ archive_user }}'
+ keytab_path: '{{ archive_keytab }}'
+
+- name: configure gssproxy for kerberized nfs
+ include_role:
+ name: gssproxy_client
+ vars:
+ gssproxy_name: archiver
+ gssproxy_section: service/archiver
+ gssproxy_keytab: /etc/krb5.keytab
+ gssproxy_client_keytab: '{{ archive_keytab }}'
+ gssproxy_cred_usage: initiate
+ gssproxy_euid: '{{ archive_user }}'
diff --git a/roles/archive_server/tasks/main.yml b/roles/archive_server/tasks/main.yml
new file mode 100644
index 0000000..d1bed55
--- /dev/null
+++ b/roles/archive_server/tasks/main.yml
@@ -0,0 +1,79 @@
+- import_tasks: freeipa.yml
+
+- name: install rsync
+ dnf:
+ name: rsync
+ state: present
+
+- name: create home directory
+ file:
+ path: '{{ archive_home }}'
+ owner: '{{ archive_user }}'
+ group: '{{ archive_user }}'
+ mode: 0700
+ state: directory
+
+- name: create ssh directory
+ file:
+ path: '{{ archive_home }}/.ssh'
+ owner: '{{ archive_user }}'
+ group: '{{ archive_user }}'
+ mode: 0700
+ state: directory
+
+- name: copy ssh privkey
+ copy:
+ content: '{{ archive_ssh_privkey }}'
+ dest: "{{ archive_home }}/.ssh/id_{{ archive_ssh_pubkey | regex_replace('^ssh-(\\w+).*', '\\1') }}"
+ owner: '{{ archive_user }}'
+ group: '{{ archive_user }}'
+ mode: 0600
+
+- name: generate archiver script
+ template:
+ src: '{{ archive_script_path[1:] }}.j2'
+ dest: '{{ archive_script_path }}'
+ mode: 0555
+
+- name: create plugin directory
+ file:
+ path: '{{ archive_plugin_dir }}'
+ state: directory
+
+- name: copy plugins
+ copy:
+ src: '{{ item.src }}'
+ dest: '{{ archive_plugin_dir }}/{{ item.path }}'
+ mode: 0555
+ loop: "{{ lookup('filetree', archive_plugin_dir[1:], wantlist=True) }}"
+ when: item.state == 'file'
+
+- name: generate configuration
+ template:
+ src: '{{ archive_config_path[1:] }}.j2'
+ dest: '{{ archive_config_path }}'
+ owner: '{{ archive_user }}'
+ group: '{{ archive_user }}'
+ mode: 0440
+
+- name: create SELinux policy to avoid logspam
+ include_role:
+ name: selinux_policy
+ apply:
+ tags: selinux
+ vars:
+ selinux_policy_name: ssh_gssproxy
+ selinux_policy_te: '{{ archive_selinux_policy_te }}'
+ tags: selinux
+
+- name: create systemd timer
+ include_role:
+ name: systemd_timer
+ vars:
+ timer_name: archiver
+ timer_description: Remote file archiver
+ timer_after: nss-user-lookup.target network-online.target gssproxy.service
+ timer_on_calendar: '{{ archive_on_calendar }}'
+ timer_user: '{{ archive_user }}'
+ timer_exec: '{{ archive_script_path }}'
+ timer_persistent: no
diff --git a/roles/archive_server/templates/etc/archiver.conf.j2 b/roles/archive_server/templates/etc/archiver.conf.j2
new file mode 100644
index 0000000..d598a39
--- /dev/null
+++ b/roles/archive_server/templates/etc/archiver.conf.j2
@@ -0,0 +1,16 @@
+# The format of this file is:
+#
+# HOST PLUGIN_NAME [ARGS...]
+#
+# Beware, each line is naively split on whitespace to tokenize the arguments.
+# Quoting, escaping, shell characters, etc are NOT supported.
+
+# opnsense firewalls
+{% for host in groups.opnsense_firewalls %}
+{{ host }} archive_opnsense {{ hostvars[host].opnsense_backup_api_key }} {{ hostvars[host].opnsense_backup_api_secret }}
+{% endfor %}
+
+# edgeswitches
+{% for host in groups.switches %}
+{{ host }} archive_edgeswitch {{ hostvars[host].edgeswitch_backup_username }} {{ hostvars[host].edgeswitch_backup_password }}
+{% endfor %}
diff --git a/roles/archive_server/templates/usr/local/bin/archiver.sh.j2 b/roles/archive_server/templates/usr/local/bin/archiver.sh.j2
new file mode 100644
index 0000000..582b776
--- /dev/null
+++ b/roles/archive_server/templates/usr/local/bin/archiver.sh.j2
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+set -Eeu -o pipefail
+
+shopt -s dotglob
+
+CLIENT_HOSTGROUP={{ archive_clients_hbac_hostgroup | quote}}
+ARCHIVE_SRC={{ archive_source_path | quote }}
+ARCHIVE_DEST={{ archive_dest_path | quote }}
+ARCHIVE_PLUGIN_DIR={{ archive_plugin_dir | quote }}
+ARCHIVE_CONFIG={{ archive_config_path }}
+ARCHIVE_HOME={{ archive_home | quote }}
+ARCHIVE_RETENTION_DAYS={{ archive_retention_days | quote }}
+DOMAIN={{ ansible_domain }}
+{% raw %}
+export GSS_USE_PROXY=yes
+
+RSYNC_ARGS=(
+ --recursive
+ --ignore-existing
+ --links
+ --perms
+ --no-group
+ --chmod=D2770,F440
+ --times
+ --omit-dir-times
+ --prune-empty-dirs
+ --remove-source-files
+ --human-readable
+ --itemize-changes
+)
+
+FAILED_HOSTS=()
+
+trap 'rm -rf "$TMPDIR"' EXIT
+
+############
+# First, archive the /var/spool/archive directory for all hosts in the
+# archive clients host group via ssh.
+############
+readarray -t HOSTS < <(ipa hostgroup-show "$CLIENT_HOSTGROUP" --raw \
+ | awk '$1 == "member:" { match($2, /^fqdn=([^,]+),/, m); print m[1] }')
+
+for HOST in "${HOSTS[@]}"; do
+ echo "archiving ${HOST}..."
+ TMPDIR=$(mktemp -d "${ARCHIVE_HOME}/.archiver-XXXXXX")
+ rsync "${RSYNC_ARGS[@]}" "${HOST}:${ARCHIVE_SRC}/" "$TMPDIR" && RC=$? || RC=$?
+
+ if (( RC == 0 )); then
+ mkdir -p "${ARCHIVE_DEST}/${HOST}"
+ find "$TMPDIR" -mindepth 2 -maxdepth 2 -print0 | xargs -0 -I{} cp -rpn {} "${ARCHIVE_DEST}/${HOST}"
+ else
+ FAILED_HOSTS+=("$HOST")
+ fi
+
+ rm -rf "$TMPDIR"
+done
+
+
+############
+# Next, we archive hosts that don't support pull via ssh. For each line in
+# $ARCHIVE_CONFIG, we run the plugin command inside of a temporary directory and
+# then rsync any created files to the archive directory.
+############
+grep -v '^\s*$\|^\s*\#' "$ARCHIVE_CONFIG" | while read -r HOST CMD ARGS; do
+ echo "archiving ${HOST} via script..."
+
+ TMPDIR=$(mktemp -d "${ARCHIVE_HOME}/.archiver-XXXXXX")
+ pushd "$TMPDIR" > /dev/null
+ "${ARCHIVE_PLUGIN_DIR}/${CMD}" "$HOST" ${ARGS:-} && RC=$? || RC=$?
+ popd > /dev/null
+
+ if [[ $HOST = *.* ]]; then
+ FQDN=$HOST
+ else
+ FQDN="${HOST}.${DOMAIN}"
+ fi
+
+ if (( RC == 0 )); then
+ mkdir -p "${ARCHIVE_DEST}/${FQDN}"
+ rsync "${RSYNC_ARGS[@]}" "${TMPDIR}/" "${ARCHIVE_DEST}/${FQDN}"
+ else
+ FAILED_HOSTS+=("$HOST")
+ fi
+
+ rm -rf "$TMPDIR"
+done
+
+
+############
+# Prune old archive files.
+############
+find "$ARCHIVE_DEST" -type f -mtime "+${ARCHIVE_RETENTION_DAYS}" -delete
+
+if (( ${#FAILED_HOSTS[@]} )); then
+ echo "the following hosts had errors: ${FAILED_HOSTS[*]}" 1>&2
+ exit 1
+fi
+{% endraw %}
diff --git a/roles/archive_server/vars/main.yml b/roles/archive_server/vars/main.yml
new file mode 100644
index 0000000..c59fbce
--- /dev/null
+++ b/roles/archive_server/vars/main.yml
@@ -0,0 +1,22 @@
+archive_home: /var/spool/archive
+archive_source_path: /var/spool/archive
+
+archive_keytab: /var/lib/gssproxy/clients/{{ archive_user }}.keytab
+archive_clients_hbac_hostgroup: archive_clients
+archive_script_path: /usr/local/bin/archiver.sh
+archive_config_path: /etc/archiver.conf
+
+archive_plugin_dir: /usr/local/libexec/archiver
+
+archive_selinux_policy_te: |
+ require {
+ type gssd_t;
+ type ssh_exec_t;
+ type gssproxy_t;
+ class file getattr;
+ class key read;
+ }
+
+ #============= gssproxy_t ==============
+ allow gssproxy_t gssd_t:key read;
+ allow gssproxy_t ssh_exec_t:file getattr;