docker-lab/defguardDocker/deployment/docker-compose/setup.sh

891 lines
25 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck shell=bash
# This is a script that sets up an entire defguard instance (including core,
# gateway, enrollment proxy and reverse proxy). It's goal is to prepare
# a working instance by running a single command.
set -o errexit # abort on nonzero exitstatus
set -o pipefail # don't hide errors within pipes
# Global variables
VERSION="1.0.2"
SECRET_LENGTH=64
PASSWORD_LENGTH=16
VOLUME_DIR=".volumes"
SSL_DIR="${VOLUME_DIR}/ssl"
RSA_DIR="${VOLUME_DIR}/core"
COMPOSE_FILE="docker-compose.yaml"
ENV_FILE=".env"
LOG_FILE=$(mktemp setup.log.XXXXXX)
BASE_COMPOSE_FILE_URL="https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/docker-compose.yaml"
BASE_ENV_FILE_URL="https://raw.githubusercontent.com/DefGuard/deployment/main/docker-compose/.env.template"
CORE_IMAGE_TAG="${CORE_IMAGE_TAG:-latest}"
GATEWAY_IMAGE_TAG="${GATEWAY_IMAGE_TAG:-latest}"
PROXY_IMAGE_TAG="${PROXY_IMAGE_TAG:-latest}"
#####################
### MAIN FUNCTION ###
#####################
main() {
is_utf_term
is_term_color
tput reset
print_header
# display help `--help` argument is found
for i in $*; do
test "$i" == "--help" && print_usage && exit 0
# run non interactive
if [[ "$i" == "--non-interactive" ]]; then
CFG_NON_INTERACTIVE=1
# we need to remove this element from $* or getopt will return an error
set -- $(remove_element "$i" $*)
fi
# configure https
if [[ "$i" == "--use-https" ]]; then
CFG_USE_HTTPS=1
# we need to remove this element from $* or getopt will return an error
set -- $(remove_element "$i" $*)
fi
done
#
# First let's gather the ENV/command line variables
#
# load configuration from env variables
load_configuration_from_env
# load configuration from CLI options
load_configuration_from_cli "$@"
# load configuration from user inputs
if [ X$CFG_VOLUME_DIR != X ]; then
VOLUME_DIR=${CFG_VOLUME_DIR}
SSL_DIR="${VOLUME_DIR}/ssl"
RSA_DIR="${VOLUME_DIR}/core"
fi
export VOLUME_DIR
# We have enough to check the enviromnent
# so check if necessary tools are available
check_environment
# load configuration from user inputs
if ! [ $CFG_NON_INTERACTIVE ]; then
load_configuration_from_input
fi
# check that all required configuration options are set
validate_required_variables
# generate external service URLs based on config
generate_external_urls
# print out config
print_config
# set current working directory
WORK_DIR_PATH=$(pwd)
# setup RSA & SSL keys
setup_keys
# generate caddyfile
create_caddyfile
# generate `.env` file
generate_env_file
# enable insecure cookies if not using HTTPS
if ! [ "$CFG_USE_HTTPS" ]; then
uncomment_feature "HTTP" "${PROD_ENV_FILE}"
fi
# generate base docker-compose file
PROD_COMPOSE_FILE="${WORK_DIR_PATH}/${COMPOSE_FILE}"
if [ -f "$PROD_COMPOSE_FILE" ]; then
echo -n " ${TXT_BEGIN} Using existing docker-compose file at ${PROD_COMPOSE_FILE}... "
print_confirmation
else
fetch_base_compose_file
fi
# enable reverse proxy in compose file
uncomment_feature "PROXY" "${PROD_COMPOSE_FILE}"
# enable enrollment service in compose file
if [ "$CFG_ENABLE_ENROLLMENT" ]; then
enable_enrollment
fi
# fetch latest images
echo " ${TXT_BEGIN} Fetching latest Docker images: "
$COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull
# enable and setup VPN gateway
if [ "$CFG_ENABLE_VPN" ]; then
enable_vpn_gateway
fi
# start docker-compose stack
echo " ${TXT_BEGIN} Starting docker-compose stack"
$COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" up -d
if [ $? -ne 0 ]; then
echo >&2 "ERROR: failed to start docker-compose stack"
exit 1
fi
print_instance_summary
}
########################
### HELPER FUNCTIONS ###
########################
check_character_support() {
local char="$1"
echo -e "$char" | grep -q "$char"
}
is_utf_term() {
if check_character_support "√"; then
TXT_CHECK="✓"
TXT_BEGIN="▶"
TXT_SUB="▷"
TXT_STAR="★"
TXT_X="✗"
TXT_INPUT="✍"
else
TXT_CHECK="+"
TXT_BEGIN=">>"
TXT_SUB=">"
TXT_STAR="*"
TXT_X="x"
TXT_INPUT=" ::"
fi
}
is_term_color() {
if [[ $TERM == *"256"* ]]; then
C_RED="\033[31m"
C_GREEN="\033[32m"
C_YELLOW="\033[33m"
C_BLUE="\033[34m"
C_WHITE="\033[37m"
C_GREY="\033[90m"
C_LRED="\033[91m"
C_LGREEN="\033[92m"
C_LYELLOW="\033[93m"
C_LBLUE="\033[94m"
C_BOLD="\033[1m"
C_ITALICS="\033[3m"
C_BG_GREY="\033[100m"
C_END="\033[0m"
else
C_RED=""
C_GREEN=""
C_YELLOW=""
C_BLUE=""
C_WHITE=""
C_GREY=""
C_LRED=""
C_LGREEN=""
C_LYELLOW=""
C_LBLUE=""
C_BOLD=""
C_ITALICS=""
C_BG_GREY=""
C_END=""
fi
}
# remove array element
remove_element() {
local remove=$1
local result=()
for element in "$@"; do
if [[ "$element" != "$remove" ]]; then
result+=("$element")
fi
done
echo "${result[@]}"
}
# Function to convert relative path to absolute path
to_absolute_path() {
local path="$1"
if [[ "${path:0:1}" != "/" ]]; then
path="$(cd "$(dirname "$path")" && pwd)/$(basename "$path")"
fi
echo ${path}
}
print_header() {
echo -e "${C_LBLUE}"
cat << _EOF_
#
## #
## ## # # ## #
## ## # # # #
# ## # #### # #### ##### #### # # #### ### #### #
# ## ## # ## # ## # # # # # # # # # ##
## ## # # ######## # # # # # # # # #
# ## ## # # # ## # ##### # # ###### # # #
# ## # # ## # # # # # # # # # # ##
## ## #### # ##### # ####### #### # #### # # #### #
## ## # # #
## # #######
#
_EOF_
echo -e "${C_END}"
echo
echo "defguard docker-compose deployment setup script v${VERSION}"
echo -e "Copyright (C) 2023-2024 ${C_BOLD}teonite${C_END} <${C_BG_GREY}${C_YELLOW}https://teonite.com${C_END}>"
echo
}
print_confirmation() {
echo -e " ${C_LGREEN}${TXT_CHECK}${C_END} "
}
print_usage() {
echo "Usage: ${BASENAME} [options]"
echo
echo 'Available options:'
echo
echo -e "\t--help this help message"
echo -e "\t--non-interactive run in non-interactive mode - !REQUIRES SETTING all options/env vars"
echo -e "\t--domain <domain> domain where defguard web UI will be available"
echo -e "\t--enrollment-domain <domain> domain where enrollment service will be available"
echo -e "\t--use-https configure reverse proxy to use HTTPS"
echo -e "\t--volume <directory> Docker volumes directory - default: ${VOLUME_DIR}"
echo -e "\t--vpn-name <name> VPN location name"
echo -e "\t--vpn-ip <address> VPN server address & netmask (e.g. 10.0.50.1/24)"
echo -e "\t--vpn-gateway-ip <ip> VPN gateway external IP (! NOT DOMAIN - IP)"
echo -e "\t--vpn-gateway-port <port> VPN gateway external port (your clients connect here)"
echo
}
command_exists() {
local command="$1"
command -v "$command" >/dev/null 2>&1
}
command_exists_check() {
local command="$1"
if ! command_exists "$command"; then
echo >&2 "ERROR: $command command not found"
echo >&2 "ERROR: dependency failed, exiting..."
exit 2
fi
}
check_environment() {
echo -n " ${TXT_BEGIN} Checking if all required tools are available..."
# compose can be provided by newer docker versions or a separate docker-compose
docker compose version >/dev/null 2>&1
if [ $? = 0 ]; then
COMPOSE_CMD="docker compose"
else
if command_exists docker-compose; then
COMPOSE_CMD="docker-compose"
else
echo
echo >&2 "ERROR: docker-compose or docker compose command not found"
echo >&2 "ERROR: dependency failed, exiting..."
exit 3
fi
fi
command_exists_check openssl
command_exists_check curl
command_exists_check grep
# Check if the volume dir is an absolute path since docker requires it
VOLUME_DIR=$(to_absolute_path "${VOLUME_DIR}")
if [ -d ${VOLUME_DIR} ]; then
echo
echo >&2 "ERROR: volume directory: ${VOLUME_DIR} exists."
echo >&2 "ERROR: this means, I would overwrite the configuration, database and certificates."
echo >&2 "ERROR: please backup or remove the volume directory."
exit 3
fi
# create all necessary directories
for dir in ${VOLUME_DIR} ${SSL_DIR} ${RSA_DIR}; do
mkdir ${dir}
if [ $? -ne 0 ]; then
echo >&2 "ERROR: cloud not create volume directory: ${dir}"
exit 3
fi
done
print_confirmation
}
load_configuration_from_env() {
echo -n " ${TXT_BEGIN} Loading configuration from environment variables... "
# required variables
CFG_DOMAIN="$DEFGUARD_DOMAIN"
# optional variables
CFG_VOLUME_DIR="$DEFGUARD_VOLUME_DIR"
CFG_VPN_NAME="$DEFGUARD_VPN_NAME"
CFG_VPN_IP="$DEFGUARD_VPN_IP"
CFG_VPN_GATEWAY_IP="$DEFGUARD_VPN_GATEWAY_IP"
CFG_VPN_GATEWAY_PORT="$DEFGUARD_VPN_GATEWAY_PORT"
CFG_ENROLLMENT_DOMAIN="$DEFGUARD_ENROLLMENT_DOMAIN"
if ! [ $CFG_USE_HTTPS ]; then
CFG_USE_HTTPS="$DEFGUARD_USE_HTTPS"
fi
print_confirmation
}
load_configuration_from_cli() {
echo -n " ${TXT_BEGIN} Loading configuration from CLI arguments... "
ARGUMENT_LIST=(
"domain"
"enrollment-domain"
"volume"
"vpn-name"
"vpn-ip"
"vpn-gateway-ip"
"vpn-gateway-port"
)
# read arguments
opts=$(
getopt \
--longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$@"
)
eval set --$opts
while [[ $# -gt 0 ]]; do
case "$1" in
--domain)
CFG_DOMAIN=$2
shift 2
;;
--enrollment-domain)
CFG_ENROLLMENT_DOMAIN=$2
shift 2
;;
--volume)
CFG_VOLUME_DIR=$2
shift 2
;;
--vpn-name)
CFG_VPN_NAME=$2
shift 2
;;
--vpn-ip)
CFG_VPN_IP=$2
shift 2
;;
--vpn-gateway-ip)
CFG_VPN_GATEWAY_IP=$2
shift 2
;;
--vpn-gateway-port)
CFG_VPN_GATEWAY_PORT=$2
shift 2
;;
*)
break
;;
esac
done
print_confirmation
}
load_configuration_from_input() {
echo -ne "${C_ITALICS}${C_LBLUE}"
cat << _EOF_
Please provide the values to configure your defguard instance. If you've
already configured some options by setting environment variables or through
CLI options, those will be used as defaults.
If you prefer to disable this user input section, please restart the script
with --non-interactive CLI flag.
_EOF_
echo -ne "${C_GREY}"
cat << _EOF_
Choose domains that will be used to expose your instance through Caddy
reverse proxy. defguard uses a separate domain for the Web UI, and for
the optional enrollment/desktop client configuration/password reset
service.
If you don't provide any domain for the enrollment service, the service
itself will not be deployed.
You can also enable HTTPS here (highly recommended), which will configure
Caddy to automatically provision SSL certificates.
_EOF_
echo -ne "${C_BOLD}"
cat << _EOF_
Please note that this requires your server to have a public IP address
and public DNS records for your chosen domains to be configured
correctly (pointing to your server's IP address).
_EOF_
echo -ne "${C_END}"
echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} General config ${TXT_STAR}${C_END}\n"
while [ X${domain} = "X" ]; do
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter defguard domain [default: ${CFG_DOMAIN}]: " domain
if [ "$domain" ]; then
CFG_DOMAIN="$domain"
fi
done
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter enrollment domain [default: ${CFG_ENROLLMENT_DOMAIN}]: " enroll
if [ "$enroll" ]; then
CFG_ENROLLMENT_DOMAIN="$enroll"
fi
use_https_bool_value="false"
if [ $CFG_USE_HTTPS ]; then use_https_bool_value="true"; fi
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Use HTTPS [default: ${use_https_bool_value}]: " https
if [ "$https" ]; then
CFG_USE_HTTPS=1
fi
echo
echo -e " ${C_BOLD}${C_GREEN}${TXT_STAR} WireGuard VPN${TXT_STAR}${C_END}\n"
echo -ne "${C_ITALICS}${C_GREY}"
cat << _EOF_
If you wish to configure and deploy WireGuard VPN gateway, please
provide your VPN location name. To skip, just press enter and VPN will
not be configured.
_EOF_
echo -ne "${C_END}\n"
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter VPN location name [default: ${CFG_VPN_NAME}]: " vpn_name
if [ "$vpn_name" ]; then
CFG_VPN_NAME="$vpn_name"
fi
if [ "$CFG_VPN_NAME" ]; then
while [ X${vpn_ip} = "X" ]; do
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter VPN server address and subnet (e.g. 10.0.60.1/24) [default: ${CFG_VPN_IP}]: " vpn_ip
if [ "$vpn_ip" ]; then
CFG_VPN_IP="$vpn_ip"
fi
done
echo -ne "${C_ITALICS}${C_GREY}"
cat << _EOF_
Now we'll configure a public endpoint (IP + port) that your WireGuard
client devices will use to safely connect to your gateway from the
public internet.
Since we'll be starting the gateway on this server the IP address should
be the same as your server's public IP address.
_EOF_
echo -ne "${C_BOLD}"
cat << _EOF_
Please also remember that your firewall should be configured
to allow incoming UDP traffic on the chosen WireGuard port.
_EOF_
echo -ne "${C_END}"
while [ X${public_ip} = "X" ]; do
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter VPN gateway public IP (no domains!) [default: ${CFG_VPN_GATEWAY_IP}]: " public_ip
if [ "$public_ip" ]; then
CFG_VPN_GATEWAY_IP="$public_ip"
fi
done
while [ X${public_port} = "X" ]; do
echo -ne "${C_YELLOW}${TXT_INPUT}${C_END} "
read -p "Enter VPN gateway public port [default: ${CFG_VPN_GATEWAY_PORT}]: " public_port
if [ "$public_port" ]; then
CFG_VPN_GATEWAY_PORT="$public_port"
fi
done
else
echo -e " ${C_BOLD}${C_RED}${TXT_X} ${C_GREY} WireGuard VPN skipped${C_END}\n"
fi
echo
echo -e "${C_BOLD}${C_GREEN}Thank you. We'll now proceed with the deployment using provided values.${C_END}"
}
check_required_variable() {
local var_name="$1"
if [ -z "${!var_name}" ]; then
echo >&2 "ERROR: ${var_name} configuration option not set"
exit 4
fi
}
validate_required_variables() {
echo -n " ${TXT_BEGIN} Validating configuration options..."
check_required_variable "CFG_DOMAIN"
# if VPN name is given validate other VPN configurations are present
if [ "$CFG_VPN_NAME" ]; then
CFG_ENABLE_VPN=1
check_required_variable "CFG_VPN_IP"
check_required_variable "CFG_VPN_GATEWAY_IP"
check_required_variable "CFG_VPN_GATEWAY_PORT"
fi
print_confirmation
}
generate_external_urls() {
# prepare full defguard URL
if [ $CFG_USE_HTTPS ]; then
CFG_DEFGUARD_URL="https://${CFG_DOMAIN}"
else
CFG_DEFGUARD_URL="http://${CFG_DOMAIN}"
fi
# prepare full enrollment URL
if [ "$CFG_ENROLLMENT_DOMAIN" ]; then
CFG_ENABLE_ENROLLMENT=1
if [ "$CFG_USE_HTTPS" ]; then
CFG_ENROLLMENT_URL="https://${CFG_ENROLLMENT_DOMAIN}"
else
CFG_ENROLLMENT_URL="http://${CFG_ENROLLMENT_DOMAIN}"
fi
fi
}
print_config() {
echo
echo " ${TXT_BEGIN} Setting up your defguard instance with following config:"
echo
echo -e " ${TXT_SUB} data volume: ${C_BOLD}${VOLUME_DIR}${C_END}"
echo
echo -e " ${TXT_SUB} domain: ${C_BOLD}${CFG_DOMAIN}${C_END}"
echo -e " ${TXT_SUB} web UI URL: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}"
if [ "$CFG_VPN_NAME" ]; then
echo -e " ${TXT_SUB} VPN location name: ${C_BOLD}${CFG_VPN_NAME}${C_END}"
echo -e " ${TXT_SUB} VPN address: ${C_BOLD}${CFG_VPN_IP}${C_END}"
echo -e " ${TXT_SUB} VPN gateway IP: ${C_BOLD}${CFG_VPN_GATEWAY_IP}${C_END}"
echo -e " ${TXT_SUB} VPN gateway port: ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END}"
fi
if [ "$CFG_ENROLLMENT_DOMAIN" ]; then
echo -e " ${TXT_SUB} Enrollment service domain: ${C_BOLD}${CFG_ENROLLMENT_DOMAIN}${C_END}"
echo -e " ${TXT_SUB} Enrollment service URL: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}"
fi
echo
echo -e " ${TXT_BEGIN} All executed command's results are in log file: ${C_BOLD}${LOG_FILE}${C_END}"
echo
}
setup_keys() {
echo " ${TXT_BEGIN} Setting up SSL certificates and RSA keys..."
if [ -d ${SSL_DIR} -a "$(ls -A ${SSL_DIR})" ]; then
echo " ${TXT_SUB} Using existing SSL certificates from ${SSL_DIR}"
else
generate_certs
fi
if [ -d ${RSA_DIR} -a "$(ls -A ${RSA_DIR})" ]; then
echo " ${TXT_SUB} Using existing RSA keys from ${RSA_DIR}."
else
generate_rsa
fi
}
generate_certs() {
echo " ${TXT_BEGIN} Creating new SSL certificates in ${SSL_DIR}..."
mkdir -p ${SSL_DIR}
PASSPHRASE=$(generate_secret)
echo "PEM passphrase for SSL certificates set to '${PASSPHRASE}'."
# generate private key for CA
openssl genrsa -des3 -out ${SSL_DIR}/defguard-ca.key -passout pass:"${PASSPHRASE}" 2048 2>&1 >> ${LOG_FILE}
# generate Root Certificate
# TODO: allow configuring CA parameters
openssl req -x509 -new -nodes -key ${SSL_DIR}/defguard-ca.key -sha256 -days 1825 -out ${SSL_DIR}/defguard-ca.pem -passin pass:"${PASSPHRASE}" -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE}
# generate CA-signed certificate for defguard gRPC
openssl genrsa -out ${SSL_DIR}/defguard-grpc.key 2048 2>&1 >> ${LOG_FILE}
openssl req -new -key ${SSL_DIR}/defguard-grpc.key -out ${SSL_DIR}/defguard-grpc.csr -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE}
cat >${SSL_DIR}/defguard-grpc.ext <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${CFG_DOMAIN}
DNS.2 = core
DNS.3 = localhost
EOF
openssl x509 -req -in ${SSL_DIR}/defguard-grpc.csr -CA ${SSL_DIR}/defguard-ca.pem -CAkey ${SSL_DIR}/defguard-ca.key -passin pass:"${PASSPHRASE}" -CAcreateserial \
-out ${SSL_DIR}/defguard-grpc.crt -days 1000 -sha256 -extfile ${SSL_DIR}/defguard-grpc.ext 2>&1 >> ${LOG_FILE}
# generate CA-signed certificate for defguard proxy gRPC
openssl genrsa -out ${SSL_DIR}/defguard-proxy-grpc.key 2048 2>&1 >> ${LOG_FILE}
openssl req -new -key ${SSL_DIR}/defguard-proxy-grpc.key -out ${SSL_DIR}/defguard-proxy-grpc.csr -subj "/C=PL/ST=Zachodniopomorskie/L=Szczecin/O=Example/OU=IT Department/CN=${CFG_DOMAIN}" 2>&1 >> ${LOG_FILE}
cat >${SSL_DIR}/defguard-proxy-grpc.ext <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = proxy
DNS.2 = localhost
EOF
openssl x509 -req -in ${SSL_DIR}/defguard-proxy-grpc.csr -CA ${SSL_DIR}/defguard-ca.pem -CAkey ${SSL_DIR}/defguard-ca.key -passin pass:"${PASSPHRASE}" -CAcreateserial \
-out ${SSL_DIR}/defguard-proxy-grpc.crt -days 1000 -sha256 -extfile ${SSL_DIR}/defguard-proxy-grpc.ext 2>&1 >> ${LOG_FILE}
}
generate_rsa() {
echo "Generating RSA keys in ${RSA_DIR}..."
mkdir -p ${RSA_DIR}
openssl genpkey -out ${RSA_DIR}/rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>&1 >> ${LOG_FILE}
}
generate_secret() {
generate_secret_inner "${SECRET_LENGTH}"
}
generate_password() {
generate_secret_inner "${PASSWORD_LENGTH}"
}
generate_secret_inner() {
local length="$1"
openssl rand -base64 ${length} | tr -d "=+/" | tr -d '\n' | cut -c1-${length-1}
}
create_caddyfile() {
caddy_volume_path="${VOLUME_DIR}/caddy"
caddyfile_path="${caddy_volume_path}/Caddyfile"
mkdir -p ${caddy_volume_path}
cat >${caddyfile_path} <<EOF
${CFG_DEFGUARD_URL} {
reverse_proxy core:8000
}
EOF
if [ "$CFG_ENABLE_ENROLLMENT" ]; then
cat >>${caddyfile_path} <<EOF
${CFG_ENROLLMENT_URL} {
reverse_proxy proxy:8080
}
EOF
fi
cat >>${caddyfile_path} <<EOF
:80 {
respond 404
}
:443 {
respond 404
}
EOF
}
fetch_base_compose_file() {
echo -n " ${TXT_BEGIN} Fetching base compose file to ${PROD_COMPOSE_FILE}... "
curl --proto '=https' --tlsv1.2 -sSf "${BASE_COMPOSE_FILE_URL}" -o "${PROD_COMPOSE_FILE}" 2>&1 >> ${LOG_FILE}
print_confirmation
}
generate_env_file() {
PROD_ENV_FILE="${WORK_DIR_PATH}/${ENV_FILE}"
fetch_base_env_file
update_env_file
print_confirmation
}
fetch_base_env_file() {
echo -e " ${TXT_BEGIN} Fetching base ${ENV_FILE} file for compose stack..."
curl --proto '=https' --tlsv1.2 -sSf "${BASE_ENV_FILE_URL}" -o "${PROD_ENV_FILE}" 2>&1 >> ${LOG_FILE}
print_confirmation
}
update_env_file() {
echo -n " ${TXT_BEGIN} Setting environment variables in ${ENV_FILE} file for compose stack..."
# set image versions
set_env_file_value "CORE_IMAGE_TAG" "${CORE_IMAGE_TAG}"
set_env_file_value "PROXY_IMAGE_TAG" "${PROXY_IMAGE_TAG}"
set_env_file_value "GATEWAY_IMAGE_TAG" "${GATEWAY_IMAGE_TAG}"
# fill in values
set_env_file_secret "DEFGUARD_AUTH_SECRET"
set_env_file_secret "DEFGUARD_YUBIBRIDGE_SECRET"
set_env_file_secret "DEFGUARD_GATEWAY_SECRET"
set_env_file_secret "DEFGUARD_SECRET_KEY"
# use existing password if set in env variable
if [ "$DEFGUARD_DB_PASSWORD" ]; then
set_env_file_value "DEFGUARD_DB_PASSWORD" "${DEFGUARD_DB_PASSWORD}"
else
set_env_file_password "DEFGUARD_DB_PASSWORD"
fi
DEFGUARD_DEFAULT_ADMIN_PASSWORD="$(generate_password)"
set_env_file_value "DEFGUARD_DEFAULT_ADMIN_PASSWORD" "${DEFGUARD_DEFAULT_ADMIN_PASSWORD}"
set_env_file_value "DEFGUARD_URL" "${CFG_DEFGUARD_URL}"
set_env_file_value "DEFGUARD_WEBAUTHN_RP_ID" "${CFG_DOMAIN}"
print_confirmation
}
set_env_file_value() {
# make sure variable exists in file
grep -qF "${1}=" "${PROD_ENV_FILE}" || echo "${1}=" >>"${PROD_ENV_FILE}"
sed -i "s@\(${1}\)=.*@\1=${2}@" "${PROD_ENV_FILE}"
}
set_env_file_secret() {
set_env_file_value "${1}" "$(generate_secret)" "${PROD_ENV_FILE}"
}
set_env_file_password() {
set_env_file_value "${1}" "$(generate_password)" "${PROD_ENV_FILE}"
}
uncomment_feature() {
sed -i "s@# \(.*\) # \[${1}\]@\1@" "${2}"
}
enable_enrollment() {
echo -n " ${TXT_BEGIN} Enabling enrollment proxy service in compose file..."
# update .env file
uncomment_feature "ENROLLMENT" "${PROD_ENV_FILE}"
set_env_file_value "DEFGUARD_ENROLLMENT_URL" "${CFG_ENROLLMENT_URL}"
# update compose file
uncomment_feature "ENROLLMENT" "${PROD_COMPOSE_FILE}"
print_confirmation
}
enable_vpn_gateway() {
echo " ${TXT_BEGIN} Enabling VPN gateway service..."
uncomment_feature "VPN" "${PROD_COMPOSE_FILE}"
uncomment_feature "VPN" "${PROD_ENV_FILE}"
# fetch latest image
echo " ${TXT_SUB} Fetching latest gateway image..."
$COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" pull gateway
# create VPN location
echo " ${TXT_BEGIN} Adding VPN to core & generating gateway token..."
VPN_NETWORK=`echo ${CFG_VPN_IP} | awk -F'[./]' '{print $1"."$2"."$3".0/"$5}'`
token=$($COMPOSE_CMD -f "${PROD_COMPOSE_FILE}" --env-file "${PROD_ENV_FILE}" run core init-vpn-location --name "${CFG_VPN_NAME}" --address "${CFG_VPN_IP}" --endpoint "${CFG_VPN_GATEWAY_IP}" --port "${CFG_VPN_GATEWAY_PORT}" --allowed-ips "${VPN_NETWORK}" | tail -n 1)
if [ $? -ne 0 ]; then
echo >&2 "ERROR: failed to create VPN network"
exit 1
fi
# add gateway token to .env file
set_env_file_value "DEFGUARD_TOKEN" "${token}"
}
print_instance_summary() {
echo
echo -e "${C_LGREEN} ${TXT_CHECK} defguard setup finished successfully${C_END}"
echo
echo "If your DNS configuration is correct your defguard instance should be available at:"
echo
echo -e "\t${TXT_SUB} Web UI: ${C_BOLD}${CFG_DEFGUARD_URL}${C_END}"
if [ "$CFG_ENABLE_ENROLLMENT" ]; then
echo -e "\t${TXT_SUB} Enrollment service: ${C_BOLD}${CFG_ENROLLMENT_URL}${C_END}"
fi
echo
echo -e " ${TXT_BEGIN} You can log into the UI using the default admin user:"
echo
echo -e "\t${TXT_SUB} username: ${C_BOLD}admin${C_END}"
echo -e "\t${TXT_SUB} password: ${C_BOLD}${DEFGUARD_DEFAULT_ADMIN_PASSWORD}${C_END}"
echo
if [ "$CFG_ENABLE_VPN" ]; then
echo -e "\t\tVPN server public endpoint is ${C_BOLD}${CFG_VPN_GATEWAY_IP}:${CFG_VPN_GATEWAY_PORT}${C_END}"
echo -e "\t\tVPN network is ${C_BOLD}${VPN_NETWORK}${C_END}"
echo -e "\t\t! Make sure your firewall allows external UDP traffic to port ${C_BOLD}${CFG_VPN_GATEWAY_PORT}${C_END} !"
echo
echo -e "\t\tTo test if the VPN is working: ping ${CFG_VPN_IP} (after connecting to VPN)"
fi
echo
echo -e "Files used to deploy your instance are stored in:"
echo -e "\t docker compose file: ${C_BOLD}${PROD_COMPOSE_FILE}${C_END}"
echo -e "\t docker compose environment: ${C_BOLD}${PROD_ENV_FILE}${C_END}"
echo
echo -e "Persistent data (docker volumes) is stored in ${C_BOLD}${VOLUME_DIR}${C_END}"
echo
echo -e " ${C_YELLOW}${TXT_STAR} To support our work, please star us on GitHub! ${TXT_STAR}${C_END}"
echo -e " ${C_YELLOW}${TXT_STAR} https://github.com/defguard/defguard ${TXT_STAR}${C_END}"
echo
}
# run main function
main "$@" || exit 1