Self-Hosted

Self-hosting guides



Self-hosting is the practice of running and maintaining a website or service using a private server, instead of using a service outside of someones own control.



Andreas Bauer. All rights reserved.

Virtual Mail Server

How to establish a complete virtual user mail system




Postfix will be our MTA (mail transfer agent) to send and recieve encrypted mail. Virtual mail users will be managed with PostfixAdmin, a web interface for Postfix. An unlimited number of domains and domain (specific) user names may be managed and will be stored in a SQL database. Dovecot will be our MDA (mail delivery agent) to access email via secure IMAP from Roundcube, a web-based MUA (mail user agent) or an desktop MUA like Evolution. All encrypted mail communications will be secured with a TLS certificate. SPF (Sender Policy Framework) will ensure that the only verified servers/IP addresses may send mail from a given domain and DKIM (DomainKeys Identified Mail) will sign all outgoing messages with verification keys. This measures prevent our outgoing mail ending up in the junk box or our server being blacklisted for spam entirely. DMARC (Domain-based Message Authentication, Reporting and Conformance) ensures that both DKIM and SPF are properly enforced. Amavis, Spam-assassin will fiter messages for SPAM and ClamAV will be used for virus protection.

Postfix
│ └── PostfixAdmin
├── Dovecot
├──┬── SPF
│  ├── DKIM
│  └── DMARC
├── Amavis
│    ├── SpamAssassin
│    └── ClamAV
└── Roundcube

Postfix

Install Postfix and MariaDB packages.

pacman -Syu postfix mariadb postfix-mysql ca-certificates

Configure Postfix Uncomment and add or modify default Postfix settings.

/etc/postfix/main.cf
mail_owner = postfix
myhostname = mail.wildw1ng.com
mydomain = wildw1ng.com
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 10.0.0.0/22, 127.0.0.0/8
relayhost =
alias_maps = hash:/etc/postfix/aliases
alias_database = $alias_maps
home_mailbox = Maildir/
smtpd_banner = $myhostname ESMTP $mail_name (Arch Linux)
inet_protocols = ipv4
append_dot_mydomain = no
mailbox_size_limit = 0

relay_domains = $mydestination
virtual_alias_maps = proxy:mysql:/etc/postfix/virtual_alias_maps.cf,proxy:mysql:/etc/postfix/virtual_alias_domains_maps.cf
virtual_alias_domains = proxy:mysql:/etc/postfix/virtual_alias_domains.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/virtual_mailbox_domains.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/virtual_mailbox_maps.cf
virtual_mailbox_base = /home/vmail
virtual_mailbox_limit = 512000000
virtual_minimum_uid = 5000
virtual_transport = virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
local_transport = virtual
local_recipient_maps = $virtual_mailbox_maps
transport_maps = hash:/etc/postfix/transport

# Secure SMTP (receiving)
smtpd_tls_security_level = may
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
smtpd_tls_CApath = /etc/ssl/certs
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = /var/run/dovecot/auth-client
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy-spf
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_sasl_security_options = noanonymous
smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
smtpd_tls_auth_only = yes
smtpd_tls_received_header = yes
smtpd_sasl_local_domain = $mydomain
smtpd_tls_loglevel = 1

# Enable SASL authentication
smtp_sasl_auth_enable = yes
# Disallow any methods that do allow anonymous authentication
smtp_sasl_security_options = noanonymous
# Define the sasl_passwd file location
smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd

# Enable STARTTLS encryption
smtp_use_tls = yes

# Secure SMTP (sending)
smtp_tls_security_level = may
# smtp_tls_security_level = secure
# smtp_enforce_tls = yes

# Enable TLS logging
smtp_tls_loglevel = 1

# Discovering servers that support TLS
smtp_tls_note_starttls_offer = yes

non_smtpd_milters   = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters       = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock

policy-spf_time_limit = 3600s

# Disable VRFY (verify)
disable_vrfy_command = yes

# Block spam using DNS blacklists
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_rbl_client bl.spamcop.net
# reject_rbl_client zen.spamhaus.org

smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_sender_domain, reject_unknown_reverse_client_hostname, reject_unknown_client_hostname

# Require the client to provide a HELO/EHLO hostname
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
smtp_helo_name = $mydomain

# Unsubscibe header
header_checks = regexp:/etc/postfix/list_unsub_header

# Protecting against forged sender addresses
smtpd_sender_login_maps=mysql:/etc/postfix/virtual_alias_maps.cf

# Hide the sender's IP and user agent in the Received header
smtp_header_checks = regexp:/etc/postfix/smtp_header_checks

/etc/postfix/master.cf
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       n       -       -       smtpd
  -o content_filter=amavisfeed:[127.0.0.1]:10024

submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
  -o syslog_name=postfix/submission
  -o smtpd_tls_wrappermode=no
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o content_filter=amavisfeed:[127.0.0.1]:10024
  -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit_sasl_authenticated,reject

policy-spf  unix  -       n       n       -       0       spawn
        user=nobody argv=/usr/bin/policyd-spf

amavisfeed      unix  -    -       n       -       2       smtp
 -o smtp_data_done_timeout=1200
 -o smtp_send_xforward_command=yes
 -o disable_dns_lookups=yes
 -o max_use=20

127.0.0.1:10025 inet n  -       y       -       -       smtpd
 -o content_filter=
 -o smtpd_delay_reject=no
 -o smtpd_client_restrictions=permit_mynetworks,reject
 -o smtpd_helo_restrictions=
 -o smtpd_sender_restrictions=
 -o smtpd_recipient_restrictions=permit_mynetworks,reject
 -o smtpd_data_restrictions=reject_unauth_pipelining
 -o smtpd_end_of_data_restrictions=
 -o mynetworks=127.0.0.0/8
 -o smtpd_error_sleep_time=0
 -o smtpd_soft_error_limit=1001
 -o smtpd_hard_error_limit=1000
 -o smtpd_client_connection_count_limit=0
 -o smtpd_client_connection_rate_limit=0
 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
 -o local_header_rewrite_clients=

Create unprivileged user

For security reasons, we create a new user vmail to store the mails.

groupadd -g 5000 vmail
useradd -u 5000 -g vmail -s /usr/bin/nologin -d /home/vmail -m vmail

We use a gid and uid of 5000 in both cases so that we do not run into conflicts with regular users.
All our mail will be stored in /home/vmail.


MariaDB

We have to initialize the MariaDB data directory and create the system tables in the mysql database before starting the mariadb.service.

mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

Enable and start mariadb.service.

systemctl enable mariadb
systemctl start mariadb

Improve the initial security of our MariaDB installation with recommended security measures,
such as removing anonymous accounts and removing the test database.
When prompted to “Switch to unix_socket authentication” enter n for No.

mysql_secure_installation

By default, MySQL will listen on the 0.0.0.0 address, which includes all network interfaces.
We have to restrict MySQL to listen only to the loopback address.

/etc/my.cnf.d/server.cnf
[mysqld]
#bind-address = localhost
bind-address = 127.0.0.1

Restart mariadb.service.

systemctl restart mariadb

Postfix database initialization

We have to create an empty database and give the corresponding user permission to use the database.
postfix_user will have read/write access to the database postfix_db using POSTFIXDBPASSWORD as password.

mysql -u root -p
CREATE DATABASE postfix_db;
GRANT ALL ON postfix_db.* TO 'postfix_user'@'localhost' IDENTIFIED BY 'POSTFIXDBPASSWORD';
FLUSH PRIVILEGES;
QUIT;

We have to set up the necessary configurations for postfix to interact with the database for all its other transport needs.

/etc/postfix/virtual_alias_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = alias
select_field = goto
where_field = address

/etc/postfix/virtual_mailbox_domains.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = domain
select_field = domain
where_field = domain

/etc/postfix/virtual_mailbox_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
table = mailbox
select_field = maildir
where_field = username

/etc/postfix/virtual_alias_domains_maps.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = '1' AND alias_domain.active='1'

/etc/postfix/virtual_alias_domains.cf
user = postfix_user
password = POSTFIXDBPASSWORD
hosts = localhost
dbname = postfix_db
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'

Only postfix should have access rights to these files, as they contain passwords.

chown root:postfix -R /etc/postfix/
chmod 640 /etc/postfix/virtual_*

We have to run postmap on transport to generate its database.

postmap /etc/postfix/transport

Dovecot

Install Dovecot package.

pacman -Syu dovecot

Create the dovecot configuration directory and configuration files.

mkdir /etc/dovecot

/etc/dovecot/dovecot.conf
protocols = imap
listen = *
auth_mechanisms = plain login
passdb {
    driver = sql
    args = /etc/dovecot/dovecot-sql.conf
}
userdb {
    driver = sql
    args = /etc/dovecot/dovecot-sql.conf
}

service auth {
    unix_listener auth-client {
        group = postfix
        mode = 0660
        user = postfix
    }
    user = root
}


mail_home = /home/vmail/%d/%n
mail_location = maildir:~

ssl_dh = </etc/dovecot/dh.pem

ssl_cert = </etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem

/etc/dovecot/dovecot-sql.conf
driver = mysql
connect = host=localhost dbname=postfix_db user=postfix_user password=POSTFIXDBPASSWORD
# It is highly recommended to not use deprecated MD5-CRYPT. Read more at http://wiki2.dovecot.org/Authentication/PasswordSchemes
default_pass_scheme = SHA512-CRYPT
# Get the mailbox
user_query = SELECT '/home/vmail/%d/%n' as home, 'maildir:/home/vmail/%d/%n' as mail, 5000 AS uid, 5000 AS gid, concat('dirsize:storage=',  quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
# Get the password
password_query = SELECT username as user, password, '/home/vmail/%d/%n' as userdb_home, 'maildir:/home/vmail/%d/%n' as userdb_mail, 5000 as  userdb_uid, 5000 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# If using client certificates for authentication, comment the above and uncomment the following
#password_query = SELECT null AS password, ‘%u’ AS user

Set permissions.

chown dovecot:dovecot /etc/dovecot/*

Remove the old temporary SSL parameters file.

rm /var/lib/dovecot/ssl-parameters.dat

We are required to provide DH parameters. Generate a new DH parameters file (this might take a long time).

openssl dhparam -out /etc/dovecot/dh.pem 4096

Enable Dovecot debug logging.

/etc/dovecot/dovecot.conf
auth_verbose = yes
auth_verbose_passwords = no
auth_debug = yes
auth_debug_passwords = yes
mail_debug = yes
verbose_ssl = yes

Testing IMAP.

openssl s_client -connect 127.0.0.1:993
a login admin@wildw1ng.com PASSWORD
a examine inbox
a logout

PostfixAdmin

Web interface for Postfix used to manage mailboxes, virtual domains and aliases. postfixadmin.png


Install PostfixAdmin, Apache and PHP packages.

pacman -Syu postfixadmin apache php-fpm php-imap

Apache HTTP Server configuration

/etc/httpd/conf/httpd.conf
ServerName localhost
Listen 0.0.0.0:80

# php-fpm, an alternative PHP FastCGI implementation with some additional features (mostly) useful for heavy-loaded sites
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

# SSL
LoadModule ssl_module modules/mod_ssl.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule rewrite_module modules/mod_rewrite.so

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

# PostfixAdmin
Include /etc/httpd/conf/postfixadmin.conf

# php-fpm
Include conf/extra/php-fpm.conf

# Secure (SSL/TLS) connections
Include conf/extra/httpd-ssl.conf

<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>

<IfModule mod_ssl.c>
Listen 0.0.0.0:443
</IfModule>
Include /etc/httpd/conf/extra/httpd-vhosts-le-ssl.conf

php-fpm proxy configuration

/etc/httpd/conf/extra/php-fpm.conf
DirectoryIndex index.php index.html
<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
chmod 644 /etc/httpd/conf/extra/php-fpm.conf

Configure Apache HTTP Server with php-fpm

/etc/httpd/conf/postfixadmin.conf
Alias /postfixadmin "/usr/share/webapps/postfixadmin/public"
<Directory "/usr/share/webapps/postfixadmin/public">
    DirectoryIndex index.html index.php
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/postfixadmin/postfixadmin.sock|fcgi://localhost/"
    </FilesMatch>
    AllowOverride All
    Options FollowSymlinks
    Require all granted
    SetEnv PHP_ADMIN_VALUE "open_basedir = /tmp/:/usr/share/webapps/postfixadmin:/etc/webapps/postfixadmin/:/var/cache/postfixadmin/templates_c"
</Directory>

/etc/php/php-fpm.d/postfixadmin.conf
[postfixadmin]
user = postfixadmin
group = postfixadmin
listen = /run/postfixadmin/postfixadmin.sock
listen.owner = root
listen.group = http
listen.mode = 0660
pm = ondemand
pm.max_children = 4
php_admin_value['date.timezone'] = UTC
php_admin_value['session.save_path'] = /tmp
php_admin_value['open_basedir'] = /tmp/:/usr/share/webapps/postfixadmin/:/etc/webapps/postfixadmin/:/usr/bin/doveadm:/var/cache/postfixadmin

PHP configuration

/etc/php/php.ini
open_basedir = /var/cache/postfixadmin/:/etc/webapps/:/usr/share/webapps/:/tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail
date.timezone = "UTC"

extension=imap
extension=mysqli
extension=pdo_mysql
extension=iconv
extension=gd
extension=intl
extension=exif
extension=imagick

PostfixAdmin configuration

/etc/webapps/postfixadmin/config.local.php
<?php
$CONF['configured'] = true;
// correspond to dovecot maildir path /home/vmail/%d/%u 
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix_user';
$CONF['database_password'] = 'POSTFIXDBPASSWORD';
$CONF['database_name'] = 'postfix_db';

$CONF['default_aliases'] = array (
    'abuse' => 'abuse@wildw1ng.com',
    'hostmaster' => 'hostmaster@wildw1ng.com',
    'postmaster' => 'postmaster@wildw1ng.com',
    'webmaster' => 'webmaster@wildw1ng.com'
);

$CONF['vacation_domain'] = 'autoreply.wildw1ng.com';

$CONF['footer_text'] = 'Return to wildw1ng.com';
$CONF['footer_link'] = 'https://wildw1ng.com';
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
$CONF['setup_password'] = 'HASHEDSETUPPASSWORD';

Enable and start Services.

systemctl enable httpd
systemctl enable php-fpm
systemctl enable postfix
systemctl enable dovecot

Generate hashes with non-default hash functions.

doveadm pw -s SHA512-CRYPT -p "DOVEADMPASSWORD"

Write the HASHEDSETUPPASSWORD to the configuration file.

Navigate to http://10.0.1.18/postfixadmin/setup.php.
Now we can create a superadmin account.


Restrict access to setup.php after installation is finished.

chmod 600 /usr/share/webapps/postfixadmin/public/setup.php

Check the apache log for errors.

less /var/log/httpd/error_log

PostfixAdmin pacman hook

The database needs to be upgraded after a version bump.
We will see a message saying ‘The PostfixAdmin database layout is outdated’ on the login page.
Therefore we may set up a hook that runs the needed upgrade.php script automatically via a pacman hook.

/etc/pacman.d/hooks/postfixadmin.hook
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = postfixadmin

[Action]
Description = Run Postfixadmin upgrade.php to make sure database is up to date
When = PostTransaction
Exec = /usr/bin/runuser -u postfixadmin -- /usr/bin/php /usr/share/webapps/postfixadmin/public/upgrade.php


Roundcube

Full-featured, PHP web-based mail client. roundcube.png


Install Roundcube and PHP Plugin packages.

pacman -Syu roundcubemail php-gd php-intl php-imagick librsvg

Warning

Roundcube needs a separate database to work. You should not use the same database for Roundcube and PostfixAdmin.
Create a second database roundcube_db and a new user named roundcube_user.


Create an empty database and give the corresponding user permission to use the database.

mysql -u root -p
CREATE DATABASE `roundcube_db` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
CREATE USER `roundcube_user`@'localhost' IDENTIFIED BY 'ROUNDCUBEDBPASSWORD';
GRANT ALL PRIVILEGES ON `roundcube_db`.* TO `roundcube_user`@`localhost`;
FLUSH PRIVILEGES;
QUIT;

We need to initialize the roundcubemail database tables.

mysql -u root -p roundcube_db < /usr/share/webapps/roundcubemail/SQL/mysql.initial.sql

Copy the default configuration file and set permisions.

cd /etc/webapps/roundcubemail/config
cp config.inc.php.sample config.inc.php
chown http:http config.inc.php
chmod 640 config.inc.php

Set our mail server settings.

/etc/webapps/roundcubemail/config/config.inc.php
?php

$config = [];

// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
//       or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
$config['db_dsnw'] = 'mysql://roundcube_user:ROUNDCUBEDBPASSWORD@localhost/roundcube_db';
$config['imap_host'] = 'tls://mail.wildw1ng.com';
$config['smtp_host'] = 'tls://mail.wildw1ng.com';
$config['smtp_port'] = 587;
$config['imap_port'] = 993;
$config['mime_types'] = '/etc/webapps/roundcubemail/config/mime.types';

// IMAP host chosen to perform the log-in.
// See defaults.inc.php for the option description.
// $config['imap_host'] = 'localhost:143';

// SMTP server host (for sending mails).
// See defaults.inc.php for the option description.
// $config['smtp_host'] = 'localhost:587';

// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '%u';

// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '%p';

// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = 'https://wildw1ng.com';

// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';

// This key is used to encrypt the users imap password which is stored
// in the session record. For the default cipher method it must be
// exactly 24 characters long.
// YOUR KEY MUST BE DIFFERENT THAN THE SAMPLE VALUE FOR SECURITY REASONS
$config['des_key'] = 'LONGRANDOMSTRING';

// List of active plugins (in plugins/ directory)
$config['plugins'] = [
    'archive',
    'zipdownload',
    'password',
];

// skin name: folder from skins/
$config['skin'] = 'elastic';    

Set enable_installer to enable the setup wizard

$config['enable_installer'] = true;

For Roundcube to be able to detect mime-types from filename extensions you need to point it to a mime.types file.
Apache usually comes with one.

cp /etc/httpd/conf/mime.types /etc/webapps/roundcubemail/config/mime.types
chown http:http /etc/webapps/roundcubemail/config/mime.types
chmod 640 /etc/webapps/roundcubemail/config/mime.types
/etc/webapps/roundcubemail/config/config.inc.php
$config['mime_types'] = '/etc/webapps/roundcubemail/config/mime.types';

Info

If you have configured open_basedir in php.ini, make sure it includes /etc/webapps and /usr/share/webapps,
so PHP can open the required Roundcube files.


Enable the password plugin to let users change their passwords from within Roundcube.

/etc/webapps/roundcubemail/config/config.inc.php
$config['plugins'] = password;

Configure the password plugin and make sure you alter the settings accordingly.

/usr/share/webapps/roundcubemail/plugins/password/config.inc.php
<?php

$config['password_driver'] = 'sql';
$config['password_db_dsn'] = 'mysql://postfix_user:POSTFIXDBPASSWORD@localhost/postfix_db';
// If you are not using dovecot specify another algorithm explicitly e.g 'sha256-crypt'
$config['password_algorithm'] = 'dovecot';
// For dovecot salted passwords only (above must be set to 'dovecot')
// $config['password_algorithm_prefix'] = 'true';
// $config['password_dovecotpw'] = 'doveadm pw';
// $config['password_dovecotpw_method'] = 'SHA512-CRYPT';
// $config['password_dovecotpw_with_method'] = true;
$config['password_query'] = 'UPDATE mailbox SET password=%P WHERE username=%u';

Now we finish the Roundcube installation with the wizard in our browser http://10.0.1.18/roundcube/installer.


For security reasons, we have to disable the installer after finishing the wizard and remove the installer directory.

rm /usr/share/webapps/roundcubemail/installer
/etc/webapps/roundcubemail/config/config.inc.php

delete $config['enable_installer'] = true;


DNS Record

We need to set A and MX DNS records pointing our mail server.

dns-records.png

A record pointing our system’s FQDN (hostname) to our mail server IPv4 address.

mail.wildw1ng.com 60 IN A 37.201.217.90

MX record specifies which mail server is responsible for accepting emails on behalf of a recipient’s domain.
All messages sent to @wildw1ng.com email addresses will be accepted by the mail.wildw1ng.com mail server.

wildw1ng.com 3600 IN MX 0 mail.wildw1ng.com


Open ports on mail server

Port Service Description
25 SMTP Transmission of email from email server to email server
993 IMAP Secure session

Check open ports on our machine.

ss -tapn
netstat -tlpn

Get SSL certificates with Certbot via Let’s Encrypt for Apache

/etc/httpd/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
        ServerAdmin admin@wildw1ng.com
        DocumentRoot "/usr/share/webapps/roundcubemail"
        ServerName mail.wildw1ng.com
        ServerAlias mail.wildw1ng.com
        ErrorLog "/var/log/httpd/mail.wildw1ng.com-error.log"
        CustomLog "/var/log/httpd/mail.wildw1ng.com-access.log" common
        <Directory "/usr/share/webapps/roundcubemail">
                AllowOverride All
                Options FollowSymlinks
                Require all granted
                SetEnv PHP_ADMIN_VALUE "open_basedir /tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail"
        </Directory>
RewriteEngine on
RewriteCond %{SERVER_NAME} =mail.wildw1ng.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

/etc/httpd/conf/extra/httpd-vhosts-le-ssl.conf
<IfModule mod_ssl.c>
SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
<VirtualHost *:443>
        ServerAdmin admin@wildw1ng.com
        DocumentRoot "/usr/share/webapps/roundcubemail"
        ServerName mail.wildw1ng.com
        ServerAlias mail.wildw1ng.com
        ErrorLog "/var/log/httpd/mail.wildw1ng.com-error.log"
        CustomLog "/var/log/httpd/mail.wildw1ng.com-access.log" common
        <Directory "/usr/share/webapps/roundcubemail">
                AllowOverride All
                Options FollowSymlinks
                Require all granted
                SetEnv PHP_ADMIN_VALUE "open_basedir /tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail"
        </Directory>

Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem
SSLUseStapling on
</VirtualHost>
</IfModule>


Run Certbot to obtain a certificate.

certbot --apache

Certificate is saved at: /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem



If we get errors, we have to ensure that SSL is not multiple defined.

grep -r "Listen 443" /etc/httpd

Sender Policy Framework

SPF is an email authentication protocol used to stop phishing attacks.
We can specify who is allowed to send email on behalf of our domain.


Install SPF package.

yay -Syu python-spf-engine

Modify Postfix configuration files to enable SPF.

/etc/postfix/main.cf
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy-spf
policy-spf_time_limit = 3600s

/etc/postfix/master.cf
policy-spf  unix  -       n       n       -       0       spawn
    user=nobody argv=/usr/bin/policyd-spf

SPF DNS Record

To allow other mail exchangers to validate mails apparently sent from our domain, we need to set a DNS TXT record with v=spf1 mx ~all. We are approving the domain mail servers (mx) and if the SPF check fails, the result will be a soft failure (~all).


DomainKeys Identified Mail

DKIM is a sender authentication protocol that allows signing messages so mailbox providers can verify them.
This method is designed to detect email spoofing by identifying forged sender addresses in email.


Install OpenDKIM package.

pacman -Syu opendkim

Create a directory for dkim.

mkdir /var/db/dkim/

Generate a secret signing key.

opendkim-genkey -r -s default -d wildw1ng.com
chmod 400 /var/db/dkim/default.*

Copy the default configuration file.

cp /usr/share/doc/opendkim/opendkim.conf.sample /etc/opendkim/opendkim.conf
chmod 644 /etc/opendkim/opendkim.conf

Modify OpenDKIM configuration and create a Socket for DKIM.

/etc/opendkim/opendkim.conf
Domain                  wildw1ng.com
KeyFile                 /var/db/dkim/default.private
Selector                default
Socket                  unix:/run/opendkim/opendkim.sock
TemporaryDirectory      /run/opendkim
UMask                   002
UserID                  opendkim
Canonicalization        relaxed/simple

mkdir /run/opendkim
chown opendkim:postfix /run/opendkim
chmod 750 /run/opendkim
mkdir -p /etc/systemd/system/opendkim.service.d/
chmod 755 /etc/systemd/system/opendkim.service.d/

/etc/tmpfiles.d/opendkim.conf
D /run/opendkim 0750 opendkim postfix

chmod 644 /etc/tmpfiles.d/opendkim.conf

/etc/systemd/system/opendkim.service.d/override.conf
[Service]
User=
User=opendkim
Group=
Group=postfix

chmod 644 /etc/systemd/system/opendkim.service.d/override.conf
chown opendkim:postfix /var/db/dkim/
chown opendkim:postfix /var/db/dkim/default.private

Enable and start the opendkim.service.

systemctl enable opendkim

DKIM DNS Record

Add a DNS TXT record with the selector and public key.

less /var/db/dkim/default.txt 

Copy everything in between (" “) without the brackets and quotes into a default._domainkey TXT DNS Record.

v=DKIM1; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNXDCBiQKBgQDjk96JyEAU2QLfDjZYyHTHVWYP/effPipH3hpgfa+Nk Wg/WmfZXjI3CmDY+N3m+eRmZdIzYO9oPGi+r0h3ceSZe4Cj858/k/0D7aYdG18QQDLIY+x+dmp7MjRK1+/B1xWjWy/Sn4n F1zVmROVxuBraX2eL32deu+qrnZlsu2H9MwIDAQAB

Check the record.

host -t TXT default._domainkey.wildw1ng.com

Domain-based Message Authentication, Reporting and Conformance

DMARC is an email authentication protocol that provides domain-level protection, detecting and preventing email spoofing techniques used in phishing.


Install OpenDMARC package.

pacman -Syu opendmarc

Modify OpenDMARC configuration and create a Socket for DMARC.

/etc/opendmarc/opendmarc.conf
Socket  unix:/run/opendmarc/opendmarc.sock
UMask 002

mkdir /run/opendmarc
chown opendmarc:postfix /run/opendmarc
chmod 750 /run/opendmarc

/etc/tmpfiles.d/opendmarc.conf
D /run/opendmarc 0750 opendmarc postfix

chmod 644 /etc/tmpfiles.d/opendmarc.conf
mkdir -p /etc/systemd/system/opendmarc.service.d/
chmod 755 /etc/systemd/system/opendmarc.service.d/

/etc/systemd/system/opendmarc.service.d/override.conf
[Service]
Group=
Group=postfix

chmod 644 /etc/systemd/system/opendmarc.service.d/override.conf

Add Mail Filter Sockets to our Postfix configuration and make sure that the DMARC milter is declared after the DKIM milter.

/etc/postfix/main.cf
non_smtpd_milters   = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters       = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock

Enable and start the opendmarc.service.

systemctl enable opendmarc

DMARC DNS Record

To enable DMARC for a domain, add a new TXT record to its DNS zone.


First testing, no harm as (sub)policy is “none”, but start to receive aggregated reports and failing reports (SPF and DKIM).

_dmarc.wildw1ng.com TXT v=DMARC1; rua=mailto:admin@wildw1ng.com; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1

After a certain time, after analyzing these reports enable the policy, for wildw1ng, for 10% of e-mail traffic.

_dmarc.wildw1ng.com TXT v=DMARC1; p=quarantine; rua=mailto:admin@wildw1ng.com; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1; pct=10

Then slowly raise the percentage and finalize with policy 100% enabled and only failing reports.

_dmarc.wildw1ng.com TXT v=DMARC1; p=quarantine; ruf=mailto:admin@wildw1ng.com; adkim=s; fo=1

Use DNS blacklists

/etc/postfix/main.cf
# Block spam using DNS blacklists
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net

List-Unsubscribe header

Set header checks.

/etc/postfix/main.cf
header_checks = regexp:/etc/postfix/list_unsub_header

Create a list_unsub_header file.

/etc/postfix/list_unsub_header
/Content-Transfer-Encoding:/i PREPEND List-Unsubscribe: mailto:admin@wildw1ng.com?subject=unsubscribe
chmod 644 /etc/postfix/list_unsub_header

Amavis and ClamAV

Amavis is an interface between the MTA and content checkers, ClamAV virus scanner and SpamAssassin.


Install packages.

pacman -Syu amavisd-new clamav p7zip unrar arj lrzip lz4 lzo rpmextract

Disable anti-spam, enable logging.

/etc/amavisd/amavisd.conf
@bypass_virus_checks_maps = (1);  # controls running of anti-virus code
@bypass_spam_checks_maps  = (1);  # controls running of anti-spam code
# $bypass_decode_parts = 1;         # controls running of decoders&dearchivers
 
$mydomain = 'wildw1ng.com';
$myhostname = 'mail.wildw1ng.com';
 
$log_level = 5;              # verbosity 0..5, -d

Enable ClamAV support and list the same clamd.sock as in /etc/clamav/clamd.conf.

# http://www.clamav.net/
['ClamAV-clamd',
   \&ask_daemon, ["CONTSCAN {}\n", "/run/clamav/clamd.ctl"],
   qr/\bOK$/m, qr/\bFOUND$/m,
   qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],
# # NOTE: run clamd under the same user as amavisd - or run it under its own
# #   uid such as clamav, add user clamav to the amavis group, and then add
# # NOTE: match socket name (LocalSocket) in clamav.conf to the socket name in
# #   this entry; when running chrooted one may prefer a socket under $MYHOME.

Add a comment to this line to enable anti-virus scan.

# @bypass_virus_check_maps = (1);  # controls running of anti-virus code

After that, add clamav user to amavis group to avoid permission problems.

usermod -a -G amavis clamav

Updating ClamAV virus definition database

We need to run freshclam before starting the service for the first time or you will run into trouble/errors which will prevent ClamAV from starting correctly.


Start and enable clamav-freshclam.service so that the virus definitions are kept up to date.

systemctl enable clamav-freshclam.service

Start and enable Amavis and ClamAV services.

systemctl enable clamav-daemon.service
systemctl enable amavisd.service

Integration with Postfix

/etc/postfix/master.cf
#
# anti spam & anti virus section
#
amavisfeed      unix  -    -       n       -       2       smtp
 -o smtp_data_done_timeout=1200
 -o smtp_send_xforward_command=yes
 -o disable_dns_lookups=yes
 -o max_use=20
127.0.0.1:10025 inet n  -       y       -       -       smtpd
 -o content_filter=
 -o smtpd_delay_reject=no
 -o smtpd_client_restrictions=permit_mynetworks,reject
 -o smtpd_helo_restrictions=
 -o smtpd_sender_restrictions=
 -o smtpd_recipient_restrictions=permit_mynetworks,reject
 -o smtpd_data_restrictions=reject_unauth_pipelining
 -o smtpd_end_of_data_restrictions=
 -o smtpd_restrictions_classes=
 -o mynetworks=127.0.0.0/8
 -o smtpd_error_sleep_time=0
 -o smtpd_soft_error_limit=1001 
 -o smtpd_hard_error_limit=1000
 -o smtpd_client_connection_count_limit=0
 -o smtpd_client_connection_rate_limit=0
 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
 -o local_header_rewrite_clients=

In this configuration we assume that postfix and Amavis are running on the same machine (i.e. 127.0.0.1).
If that is not the case edit /etc/amavisd/amavisd.conf and the prevous Postfix entry accordingly.
Postfix will listen to port 10025 so that Amavis can send back checked emails to that port.
We also have to add a configuration in our smtp or submission sections.

-o content_filter=amavisfeed:[127.0.0.1]:10024

Using this options implies that Postfix will send emails to Amavis on port 10024, so that these can be checked.
If mail passes the control then these are sent to port 10025.

We can now restart postfix.service and amavisd.service.


SpamAssasin

Install package.

pacman -Syu spamassassin

Note

Spamassassin is integrated in Amavis so we do not have to start spamassassin.service.


To enable support for Spamassassin comment the following line.

/etc/amavis/amavis.conf
# @bypass_spam_checks_maps = (1);  # controls running of anti-spam code

Edit the SpamAssassin configuration.

$sa_tag_level_deflt  = 1.0;  # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 1.0;  # add 'spam detected' headers at that level
$sa_kill_level_deflt = 5.0;  # triggers spam evasive actions (e.g. blocks mail)
$sa_dsn_cutoff_level = 8;   # spam level beyond which a DSN is not sent
# $sa_quarantine_cutoff_level = 25; # spam level beyond which quarantine is off
$penpals_threshold_high = $sa_kill_level_deflt;  # do not waste time on hi spam
$bounce_killer_score = 100;  # spam score points to add for joe-jobbed bounces

Before we restart the amavisd service we have to run sa-update.

mkdir /etc/mail/spamassassin/sa-update-keys
chown spamd:spamd /etc/mail/spamassassin/sa-update-keys
chmod 700 /etc/mail/spamassassin/sa-update-keys
cd /etc/mail/spamassassin
sudo -u spamd wget "http://spamassassin.apache.org/updates/GPG.KEY"
sudo -u spamd sa-update --import GPG.KEY
rm GPG.KEY
sudo -u spamd sa-update -D
sudo -u spamd sa-compile

Keep SpamAssassin up to date

Manual update.

sudo -u spamd sa-update --channel updates.spamassassin.org
sudo -u spamd sa-compile

Create service to automate the process.

/usr/lib/systemd/system/spamassassin-update.service
[Unit]
Description=SpamAssassin Update
After=network.target

[Service]
User=spamd
Group=spamd
Type=oneshot
# UMask=0022

ExecStart=/usr/bin/vendor_perl/sa-update --channel updates.spamassassin.org
SuccessExitStatus=1
ExecStart=/usr/bin/vendor_perl/sa-compile
# ExecStart=!/usr/bin/systemctl -q --no-block try-restart spamassassin.service

# uncomment the following ExecStart line to train SA's bayes filter
# and specify the path to the mailbox that contains spam email(s)
# ExecStart=/usr/bin/vendor_perl/sa-learn --spam <path_to_your_spam_mailbox>

/usr/lib/systemd/system/spamassassin-update.timer
[Unit]
Description=SpamAssassin Update Timer

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Start and enable spamassassin-update.timer.

systemctl enable spamassassin-update.timer

Check permissions in /var/lib/spamassassin/ if you get errors.


Andreas Bauer. All rights reserved.

Guacamole

How to access remote desktops and command line interfaces from any browser with Guacamole remote desktop gateway



guacamole.gif


Installation

pacman -Syu adobe-source-code-pro-fonts pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber pipewire-docs helvum freerdp libwebsockets mariadb tomcat9 tomcat-native && yay -Syu guacamole-server guacamole-client

Manual guacamole client installation

wget https://apache.org/dyn/closer.lua/guacamole/1.4.0/binary/guacamole-1.4.0.war?action=download
mv guacamole-1.4.0.war /usr/share/guacamole/guacamole.war

Apache Tomcat Servlet

ln -s /usr/share/guacamole/guacamole.war /var/lib/tomcat9/webapps

/etc/tomcat9/tomcat-users.xml
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="manager-gui"/>
  <role rolename="manager-script"/>
  <role rolename="manager-jmx"/>
  <role rolename="manager-status"/>
  <role rolename="admin-gui"/>
  <role rolename="admin-script"/>
  <user username="tomcat" password="PASSWORD1" roles="tomcat"/>
  <user username="manager" password="PASSWORD2" roles="manager-gui,manager-script,manager-jmx,manager-status"/>
  <user username="admin" password="PASSWORD3" roles="admin-gui"/>
</tomcat-users>

systemctl enable tomcat9

Database authentication

Installing MariaDB/MySQL system tables.

mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
systemctl enable mariadb
systemctl start mariadb

Improve initial security with recommended security measures, such as removing anonymous accounts and removing the test database.

mysql_secure_installation

When prompted to “Switch to unix_socket authentication” enter n for No.


Listen only on the loopback address

/etc/my.cnf.d/server.cnf
[mysqld]
bind-address = localhost

systemctl restart mariadb

Create Guacamole database

mysql -u root -p

CREATE DATABASE guacamole_db;
CREATE USER 'guacamole_user'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'localhost';
FLUSH PRIVILEGES;
quit;


Install MySQL extensions for Guacamole

mkdir /etc/guacamole/{extensions,lib}
chmod 755 /etc/guacamole/extensions
chmod 755 /etc/guacamole/lib
echo 'GUACAMOLE_HOME=/etc/guacamole' >> /etc/default/tomcat9

Download the MySQL extension https://guacamole.apache.org/releases/

cd /etc/guacamole/extensions/
wget https://dlcdn.apache.org/guacamole/1.4.0/binary/guacamole-auth-jdbc-1.4.0.tar.gz
tar -vxf guacamole-auth-jdbc-1.4.0.tar.gz

Write SQL schema files into the MySQL database

cat /etc/guacamole/extensions/guacamole-auth-jdbc-1.4.0/mysql/schema/*.sql | mysql guacamole_db

Copy the extension

cp /etc/guacamole/extensions/guacamole-auth-jdbc-1.4.0/mysql/guacamole-auth-jdbc-mysql-1.4.0.jar /etc/guacamole/extensions/
chmod 644 /etc/guacamole/extensions/guacamole-auth-jdbc-mysql-1.4.0.jar

Download the JDBC driver https://dev.mysql.com/downloads/connector/j/

wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.29.tar.gz
tar -vxf mysql-connector-java-8.0.29.tar.gz
cp mysql-connector-java-8.0.29/mysql-connector-java-8.0.29.jar /etc/guacamole/lib/
chmod 644 /etc/guacamole/lib/mysql-connector-java-8.0.29.jar

Configuring the client to use the database

/etc/guacamole/guacamole.properties
# Hostname and Guacamole server port
guacd-hostname: localhost
guacd-port: 4822

# MySQL properties
mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole_db
mysql-username: guacamole_user
mysql-password: PASSWORD

chmod 644 /etc/guacamole/guacamole.properties
chmod 644 /etc/guacamole/guacd.conf
systemctl enable guacd

Logging in

http://localhost:8080/guacamole

The default Guacamole user created by the provided SQL scripts is guacadmin, with a default password of guacadmin.

Warning

Before continuing with configuring Guacamole, it’s recommended that you create a new admin account and delete the original.


Create a new SSH connection using public key authentication

ssh-public-key.png

Generate key pair in PEM format on Guacamole machine

ssh-keygen -t rsa -b 4096 -m PEM

Tip

Debug sshd

journalctl -t sshd -b0

Find out Public host key (Base64) on the machine you want to connect to

ssh-keyscan -t ecdsa 192.168.0.204 2>&1 | grep ecdsa

Setup SSH server on the machine you want to connect to

/etc/ssh/sshd_config
AuthenticationMethods publickey
PubkeyAuthentication yes
PubkeyAcceptedKeyTypes=+ssh-rsa
PasswordAuthentication no

Fix RDP connection issues

Note

Guacamole server (guacd) service runs as user daemon by default.

ps aux | grep -v grep | grep guacd

Create a guacd system user account which can be used to run guacd instead of running as daemon user.

useradd -M -d /var/lib/guacd/ -r -s /sbin/nologin -c "Guacd" guacd
mkdir /var/lib/guacd
chown -R guacd: /var/lib/guacd

Change the Guacd service user

/usr/lib/systemd/system/guacd.service
[Unit]
Description=Guacamole Server
Documentation=man:guacd(8)
After=network.target

[Service]
User=guacd
ExecStart=/usr/bin/guacd -f
Restart=on-abnormal

[Install]
WantedBy=multi-user.target

Write protect Guacamole service

chattr +i /usr/lib/systemd/system/guacd.service

Andreas Bauer. All rights reserved.

Cozy

How to self host Cozy, a personal cloud and password manager




Installation

pacman -Syu opensmtpd erlang-nox freeglut cairo chafa ghostscript libheif libjxl libraw librsvg libwebp libwmf libxml2 libzip ocl-icd openexr openjpeg2 djvulibre pango imagemagick-doc nodejs nsjail

Apache CouchDB NoSQL database

/etc/couchdb/local.ini
[admins]
admin = plain-password

[couchdb]
single_node = true

[chttpd]
port = 5984
bind_address = 0.0.0.0

After starting CouchDB for the first time, plain-password will be replaced with the hashed version.

Tip

Set bind_address to 0.0.0.0 to access CouchDB from other nodes.

systemctl enable couchdb
systemctl start couchdb

Test to see if the service is running by running

curl http://127.0.0.1:5984/

You can now access the Fauxton admin interface by going to http://127.0.0.1:5984/_utils


Increase security single node setup

mkdir -pv /etc/systemd/system/couchdb.service.d
/etc/systemd/system/couchdb.service.d/10-bind-locally.conf
[Service]
Environment=ERL_EPMD_ADDRESS=127.0.0.1
/etc/couchdb/vm.args
-kernel inet_dist_use_interface {127,0,0,1}
/etc/cozy/cozy.yml
couchdb:
  url: http://admin:MYSECUREPASSWORD@127.0.0.1:5984/

Register credentials

Retrieve the correct node name

curl -X GET http://admin:MYSECUREPASSWORD@127.0.0.1:5984/_membership
curl -X PUT http://admin:MYSECUREPASSWORD@127.0.0.1:5984/_node/"couchdb@127.0.0.1"/_config/admins/admin -d "\"MYSECUREPASSWORD\""

Configuring Cozy

cp /usr/share/cozy/cozy.example.yaml /etc/cozy/cozy.yml
/etc/cozy/cozy.yml
# server host - flags: --host
#host: 0.0.0.0
host: 192.168.0.207

# server port - flags: --port -p
port: 8080

# how to structure the subdomains for apps - flags: --subdomains
# values:
#  - nested, like https://<app>.<user>.<domain>/ (well suited for self-hosted with Let's Encrypt)
#  - flat, like https://<user>-<app>.<domain>/ (easier when using wildcard TLS certificate)
subdomains: nested

# administration endpoint parameters. this endpoint should be protected
admin:
  # server host - flags: --admin-host
  host: localhost
  # server port - flags: --admin-port
  port: 6060
  # secret file name containing the derived passphrase to access to the
  # administration endpoint. this secret file can be generated using the `cozy-
  # stack config passwd` command. this file should be located in the same path
  # as the configuration file.
  secret_filename: cozy-admin-passphrase

# file system parameters
  # file system url - flags: --fs-url
  # default url is the directory relative to the binary: ./storage

  # url: file://localhost/var/lib/cozy
  # url: swift://openstack/?UserName={{ .Env.OS_USERNAME }}&Password={{ .Env.OS_PASSWORD }}&ProjectName={>

  # Swift FS can be used with advanced parameters to activate TLS properties.
  # For using swift with https, you must use the "swift+https" scheme.
  #
  # root_ca: /ca-certificates.pem
  # client_cert: /client_cert.pem
  # client_key: /client_key
  # pinned_key: 57c8ff33c9c0cfc3ef00e650a1cc910d7ee479a8bc509f6c9209a7c2a11399d6
  # insecure_skip_validation: true
  # can_query_info: true
  # default_layout: 2 # 1 for layout v2 and 2 for layout v3

  # auto_clean_trashed_after:
  #   context_a: 30D
  #   context_b: 3M

  # versioning:
  #   max_number_of_versions_to_keep: 20
  #   min_delay_between_two_versions: 15m
fs:
  url: file:///var/lib/cozy

# vault contains keyfiles informations
# See https://docs.cozy.io/en/cozy-stack/cli/cozy-stack_config_gen-keys/
# to generate the keys
vault:
# the path to the key used to encrypt credentials
  credentials_encryptor_key: /etc/cozy/vault.enc
# the path to the key used to decrypt credentials
  credentials_decryptor_key: /etc/cozy/vault.dec

# couchdb parameters
couchdb:
  # CouchDB URL - flags: --couchdb-url
  # url: http://localhost:5984/
  url: http://admin:MYSECUREPASSWORD@127.0.0.1:5984

# konnectors execution parameters for executing external processes.
konnectors:
# run connectors with node
# cmd: /usr/share/cozy/konnector-node-run.sh
# run connectors with nsjail
  cmd: /usr/share/cozy/konnector-nsjail-run.sh

log:
  # logger level (debug, info, warning, panic, fatal) - flags: --log-level
  level: info
  # send logs to the local syslog - flags: --log-syslog
  syslog: false

# Registries used for applications and konnectors
registries:
  default:
  - https://apps-registry.cozycloud.cc/selfhosted
  - https://apps-registry.cozycloud.cc/banks
  - https://apps-registry.cozycloud.cc/

Configuring Cozy admin password

cozy-stack config passwd /etc/cozy/cozy-admin-passphrase
chown cozy:cozy /etc/cozy/cozy-admin-passphrase
chmod 600 /etc/cozy/cozy-admin-passphrase

Creating vault keys

cozy-stack config gen-keys /etc/cozy/vault
chmod 700 /etc/cozy
chown cozy:cozy /etc/cozy/vault.dec
chmod 600 /etc/cozy/vault.dec
chown cozy:cozy /etc/cozy/vault.enc
chmod 600 /etc/cozy/vault.enc

Enable service

systemctl enable cozy-stack
systemctl start cozy-stack

Creating an instance

Add an instance. You will be prompted for your Cozy admin password,
you might also pass it using COZY_ADMIN_PASSWORD env var

cozy-stack instances add cozy.wildw1ng.com --apps home,settings,store

You will then need to visit https://<instance>.example.tld/?registerToken=<token>
which requires you to have setup a reverse proxy.


Andreas Bauer. All rights reserved.

Zabbix

How to self host Zabbix, an Enterprise-class open source network monitoring solution



zabbix.gif


Install packages

pacman -Syu zabbix-server zabbix-frontend-php mariadb apache php php-fpm php-apache php-gd fping traceroute

Install MariaDB/MySQL system tables

mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
systemctl enable mariadb
systemctl start mariadb

Improve initial security with recommended security measures, such as removing anonymous accounts and removing the test database.

mysql_secure_installation

When prompted to “Switch to unix_socket authentication” enter n for No.

Listen only on the loopback address

/etc/my.cnf.d/server.cnf
[mysqld]
bind-address = localhost
systemctl restart mariadb

Database Initialization

mysql -v -u root -p -e "create database zabbix character set utf8 collate utf8_bin"
mysql -v -u root -p -e "grant all on zabbix.* to zabbix@localhost identified by 'MYPASSWORD'"
mysql -v -u zabbix -p -D zabbix < /usr/share/zabbix-server/mysql/schema.sql
mysql -v -u zabbix -p -D zabbix < /usr/share/zabbix-server/mysql/images.sql
mysql -v -u zabbix -p -D zabbix < /usr/share/zabbix-server/mysql/data.sql

Database Configuration

/etc/zabbix/zabbix_server.conf
DBName=zabbix
DBUser=zabbix
DBPassword=MYPASSWORD
LogType=system

Setup Apache HTTP Server

Enable proxy modules

/etc/httpd/conf/httpd.conf

uncomment LoadModule proxy_module modules/mod_proxy.so
uncomment LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
comment # LoadModule mpm_event_module modules/mod_mpm_event.so
uncomment LoadModule mpm_prefork_module modules/mod_mpm_prefork.so

At the end of the LoadModule list
add LoadModule php_module modules/libphp.so
add AddHandler php-script .php

At the end of the Include list
add Include conf/extra/php_module.conf
add Include conf/extra/php-fpm.conf

/etc/httpd/conf/extra/php-fpm.conf
DirectoryIndex index.php index.html
<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/php-fpm.sock|fcgi://localhost/"
</FilesMatch>

Symlink the Zabbix web application directory to your http document root

ln -s /usr/share/webapps/zabbix /srv/http/zabbix

Setup PHP

List available php modules

php -m
/etc/php/php.ini
date.timezone = Europe/Berlin
display_errors = On
open_basedir = /srv/http/:/var/www/:/home/:/tmp/:/var/tmp/:/var/cache/:/usr/share/pear/:/usr/share/webapps/:/etc/webapps/

post_max_size = 16M
max_execution_time = 300
max_input_time = 300

extension=bcmath
extension=curl
extension=gd
extension=gettext
extension=mysqli
extension=sockets
extension=zip

Enable and start services

systemctl enable php-fpm
systemctl enable httpd
systemctl enable zabbix-server-mysql

Access Zabbix via your local web server, http://localhost/zabbix/,
finish the installation wizard and access the frontend the first time.
The default username is Admin and password zabbix.


Fix “[ERROR] Incorrect definition of table mysql.column_stats: expected column ‘histogram’”

mysql_upgrade --user=root

Setup client machines

Install client

pacman -Syu zabbix-agent2

Configuration

/etc/zabbix/zabbix_agent2.conf

Replace the server variable with the IP of your monitoring server. Only servers from this/these IP will be allowed to access the agent.

Server=archlinux-zabbix
ServerActive=archlinux-zabbix
Hostname=HOSTNAME

Make sure the port 10050 on your device being monitored is not blocked and is properly forwarded.

comment out # Include=./zabbix_agent2.d/plugins.d/*.conf


Monitor Arch Linux clients for available system updates using a custom UserParameter

# Monitor Arch Linux system updates
Include=/etc/zabbix/zabbix_agent2.conf.d/*.conf
mkdir /etc/zabbix/zabbix_agent2.conf.d
/etc/zabbix/zabbix_agent2.conf.d/archlinuxupdates.conf
UserParameter=archlinuxupdates,checkupdates | wc -l
chown -R zabbix-agent:zabbix-agent /etc/zabbix/zabbix_agent2.conf.d
chmod 755 /etc/zabbix/zabbix_agent2.conf.d
chmod 644 /etc/zabbix/zabbix_agent2.conf.d/archlinuxupdates.conf

Monitor nVidia GPU

/etc/zabbix/zabbix_agent2.conf.d/nvidiagpu.conf
UserParameter=gpu.temp,nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits -i 0
UserParameter=gpu.memtotal,nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits -i 0
UserParameter=gpu.used,nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits -i 0
UserParameter=gpu.free,nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits -i 0
UserParameter=gpu.fanspeed,nvidia-smi --query-gpu=fan.speed --format=csv,noheader,nounits -i 0
UserParameter=gpu.utilisation,nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i 0
UserParameter=gpu.power,nvidia-smi --query-gpu=power.draw --format=csv,noheader,nounits -i 0
UserParameter=cpu.temp,sensors | grep "CPU Temperature" | awk '{print $ 3}' | cut -c 2-5
chown -R zabbix-agent:zabbix-agent /etc/zabbix/zabbix_agent2.conf.d

Enable and start the zabbix-agent service

systemctl enable zabbix-agent2
systemctl start zabbix-agent2
systemctl status zabbix-agent2

Andreas Bauer. All rights reserved.

Fail2ban

How to protect your server from Brute-force attacks and prevent intrusions with Fail2ban




Installation

pacman -Syu firewalld fail2ban ipset

Enable and start services

systemctl enable firewalld
systemctl start firewalld
systemctl enable fail2ban
systemctl start fail2ban

Firewalld configuration

Set the default zone

firewall-cmd --set-default-zone=public

Add an interface to a zone

firewall-cmd --permanent --zone=public --add-interface=enp1s0

Get active zones

firewall-cmd --get-active-zones

Get a list of all supported services

firewall-cmd --get-services

Enable firewalld services in a zone

firewall-cmd --permanent --zone=public --add-service=ssh
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-service=zabbix-agent
firewall-cmd --permanent --zone=public --add-service=smtp
firewall-cmd --reload
firewall-cmd --list-all
firewall-cmd --state

Fail2ban configuration

Copy default fail2ban configuration from “jail.conf” to “jail.local”

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Set default values

/etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/22

bantime  = 1w
findtime  = 1d
maxretry = 3

backend = auto

action = %(action_)s
[recidive]

enabled = true
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = -1        ; permanent
findtime = 1d
maxretry = 6

Setup jails

/etc/fail2ban/jail.d/nginx.local
[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/*access.log
maxretry = 1
bantime  = 86400

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/*access.log
bantime = 86400
maxretry = 1

[nginx-nohome]
enabled = true
port = http,https
filter = nginx-nohome
logpath = /var/log/nginx/*access.log
bantime = 600
maxretry = 2

[nginx-noproxy]
enabled = true
port = http,https
filter = nginx-noproxy
logpath  = /var/log/nginx/*access.log
maxretry = 2
bantime  = 86400

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/*error.log
bantime = 600
maxretry = 6

[nginx-login]
enabled = true
port = http,https
filter = nginx-login
logpath  = /var/log/nginx/*access.log
bantime = 600
maxretry = 6

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/*error.log
bantime = 7200
maxretry = 10

Filter definitions

/etc/fail2ban/filter.d/nginx-badbots.conf
# Fail2Ban configuration file
#
# Regexp to catch known spambots and software alike. Please verify
# that it is your intent to block IPs which were driven by
# above mentioned bots.


[Definition]

badbotscustom = EmailCollector|WebEMailExtrac|TrackBack/1\.02|sogou music spider|(?:Mozilla/\d+\.\d+ )?Jorgee
badbots = Atomic_Email_Hunter/4\.0|atSpider/1\.0|autoemailspider|bwh3_user_agent|China Local Browse 2\.6|ContactBot/0\.2|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailSpider|EmailWolf 1\.00|ESurf15a 15|ExtractorPro|Franklin Locator 1\.8|FSurf15a 01|Full Web Bot 0416B|Full Web Bot 0516B|Full Web Bot 2816B|Guestbook Auto Submitter|Industry Program 1\.0\.x|ISC Systems iRc Search 2\.1|IUPUI Research Bot v 1\.9a|LARBIN-EXPERIMENTAL \(efp@gmx\.net\)|LetsCrawl\.com/1\.0 \+http\://letscrawl\.com/|Lincoln State Web Browser|LMQueueBot/0\.2|LWP\:\:Simple/5\.803|Mac Finder 1\.0\.xx|MFC Foundation Class Library 4\.0|Microsoft URL Control - 6\.00\.8xxx|Missauga Locate 1\.0\.0|Missigua Locator 1\.9|Missouri College Browse|Mizzu Labs 2\.2|Mo College 1\.9|MVAClient|Mozilla/2\.0 \(compatible; NEWT ActiveX; Win32\)|Mozilla/3\.0 \(compatible; Indy Library\)|Mozilla/3\.0 \(compatible; scan4mail \(advanced version\) http\://www\.peterspages\.net/?scan4mail\)|Mozilla/4\.0 \(compatible; Advanced Email Extractor v2\.xx\)|Mozilla/4\.0 \(compatible; Iplexx Spider/1\.0 http\://www\.iplexx\.at\)|Mozilla/4\.0 \(compatible; MSIE 5\.0; Windows NT; DigExt; DTS Agent|Mozilla/4\.0 efp@gmx\.net|Mozilla/5\.0 \(Version\: xxxx Type\:xx\)|NameOfAgent \(CMS Spider\)|NASA Search 1\.0|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|Port Huron Labs|Production Bot 0116B|Production Bot 2016B|Production Bot DOT 3016B|Program Shareware 1\.0\.2|PSurf15a 11|PSurf15a 51|PSurf15a VA|psycheclone|RSurf15a 41|RSurf15a 51|RSurf15a 81|searchbot admin@google\.com|ShablastBot 1\.0|snap\.com beta crawler v0|Snapbot/1\.0|Snapbot/1\.0 \(Snap Shots&#44; \+http\://www\.snap\.com\)|sogou develop spider|Sogou Orion spider/3\.0\(\+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sogou spider|Sogou web spider/3\.0\(\+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sohu agent|SSurf15a 11 |TSurf15a 11|Under the Rainbow 2\.2|User-Agent\: Mozilla/4\.0 \(compatible; MSIE 6\.0; Windows NT 5\.1\)|VadixBot|WebVulnCrawl\.unknown/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00

failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$

ignoreregex =

datepattern = ^[^\[]*\[({DATE})
              {^LN-BEG}

# DEV Notes:
# List of bad bots fetched from http://www.user-agents.org
# Generated on Thu Nov  7 14:23:35 PST 2013 by files/gen_badbots.
#
# Author: Yaroslav Halchenko

/etc/fail2ban/filter.d/nginx-http-auth.conf
# fail2ban filter configuration for nginx


[Definition]


failregex = ^ \[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
            ^ \[error\] \d+#\d+: \*\d+ no user/password was provided for basic authentication, client: <HOST>, server: \S+, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"\s*$
ignoreregex = 

datepattern = {^LN-BEG}

# DEV NOTES:
# Based on samples in https://github.com/fail2ban/fail2ban/pull/43/files
# Extensive search of all nginx auth failures not done yet.
# 
# Author: Daniel Black

/etc/fail2ban/filter.d/nginx-limit-req.conf
# Fail2ban filter configuration for nginx :: limit_req
# used to ban hosts, that were failed through nginx by limit request processing rate 
#
# Author: Serg G. Brester (sebres)
#
# To use 'nginx-limit-req' filter you should have `ngx_http_limit_req_module`
# and define `limit_req` and `limit_req_zone` as described in nginx documentation
# http://nginx.org/en/docs/http/ngx_http_limit_req_module.html
#
# Example:
#
#   http {
#     ...
#     limit_req_zone $binary_remote_addr zone=lr_zone:10m rate=1r/s;
#     ...
#     # http, server, or location:
#     location ... {
#       limit_req zone=lr_zone burst=1 nodelay;
#       ...
#     }
#     ...
#   }
#   ...
#

[Definition]

# Specify following expression to define exact zones, if you want to ban IPs limited 
# from specified zones only.
# Example:
#
#   ngx_limit_req_zones = lr_zone|lr_zone2
#
ngx_limit_req_zones = [^"]+

# Use following full expression if you should range limit request to specified 
# servers, requests, referrers etc. only :
#
# failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$

# Shortly, much faster and stable version of regexp:
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,

ignoreregex = 

datepattern = {^LN-BEG}

/etc/fail2ban/filter.d/nginx-login.conf
# Login filter /etc/fail2ban/filter.d/nginx-login.conf: Blocks IPs that fail to 
# authenticate using web application's log in page
#
# Scan access log for HTTP 200 + POST /sessions => failed log in
[Definition]
failregex = ^<HOST> -.*POST /sessions HTTP/1\.." 200
ignoreregex =

/etc/fail2ban/filter.d/nginx-nohome.conf
[Definition]

failregex = ^<HOST> -.*GET .*/~.*

maxlines = 1
[^\]]*)?\] (?:for user (?:"[^"]*" )?)?failed\.\s*$
datepattern = ^%%H:%%M:%%S\.%%f

ignoreregex =

/etc/fail2ban/filter.d/nginx-noproxy.conf
[Definition]
failregex = ^<HOST> -.*GET http.*
ignoreregex =

/etc/fail2ban/filter.d/nginx-noscript.conf
# Noscript filter /etc/fail2ban/filter.d/nginx-noscript.conf:
# Block IPs trying to execute scripts such as .php, .pl, .exe and other funny scripts.
# Matches e.g.
# 192.168.1.1 - - "GET /something.php
[Definition]

failregex = ^<HOST> -.*"GET .*(\.php|\.asp|\.exe|\.pl|\.cgi|\.scgi)[ /\?].*" .*$

ignoreregex = ^<HOST> -.*GET.*(/zabbix.php|/jsLoader.php|https://app.plex.tv/)

Set permissions

chmod 644 /etc/fail2ban/filter.d/nginx-*
systemctl restart fail2ban
fail2ban-client status
fail2ban-client banned
firewall-cmd --list-rich-rules
fail2ban-client get nginx-badbots actions
fail2ban-client unban IPADRESS

/etc/fail2ban/jail.d/00-firewalld.local
[DEFAULT]
banaction = firewallcmd-ipset

Service hardening

Currently, Fail2ban must be run as root. Therefore, you may wish to consider hardening the process with systemd.

/etc/systemd/system/fail2ban.service.d/override.conf
[Service]
PrivateDevices=yes
PrivateTmp=yes
ProtectHome=read-only
ProtectSystem=strict
ReadWritePaths=-/var/run/fail2ban
ReadWritePaths=-/var/lib/fail2ban
ReadWritePaths=-/var/log/fail2ban
ReadWritePaths=-/var/spool/postfix/maildrop
ReadWritePaths=-/run/xtables.lock
CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW

The CapabilityBoundingSet parameters CAP_DAC_READ_SEARCH will allow Fail2ban full read access to every directory and file.
CAP_NET_ADMIN and CAP_NET_RAW allow Fail2ban to operate on any firewall that has command-line shell interface.
By using ProtectSystem=strict the filesystem hierarchy will only be read-only,
ReadWritePaths allows Fail2ban to have write access on required paths.

Create /etc/fail2ban/fail2ban.local with the correct logtarget path

/etc/fail2ban/fail2ban.local
[Definition]
logtarget = /var/log/fail2ban/fail2ban.log

Create the /var/log/fail2ban/ directory as root.

mkdir /var/log/fail2ban/

reload systemd daemon to apply the changes of the unit and restart fail2ban.service


Debug filter

fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-http-auth.conf
fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-noscript.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-badbots.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-nohome.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-noproxy.conf
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-login.conf

Check status

fail2ban-client status
fail2ban-client banned
cat /var/log/fail2ban/fail2ban.log
tail -f /var/log/fail2ban/fail2ban.log

Manually ban IP

fail2ban-client -vvv set recidive banip 83.97.73.87
fail2ban-client status recidive

Manually unban IP

fail2ban-client banned
fail2ban-client unban 192.168.0.100

Andreas Bauer. All rights reserved.

Hugo

How to build a static website with Hugo




Generate a static website with Hugo

pacman -Syu hugo
hugo new site wildw1ng.com
cd wildw1ng.com

Download a theme

git init
git submodule add https://github.com/McShelby/hugo-theme-relearn.git themes/relearn
hugo new _index.md
hugo new --kind chapter arch/_index.md
hugo new arch/installation.md

Preview the website

hugo server

http://localhost:1313


Build the website

hugo -D

A public folder will be generated, containing all static content and assets for your website which can now be deployed on any web server.


Deploy the website

~/bin/publish
#!/bin/bash
echo 'Build static site' &&
hugo -D -s ~/sites/wildw1ng.com/ &&
echo 'Change the owner to user' &&
ssh archlinux-nginx 'sudo chown -R wildw1ng:users /srv/http/wildw1ng.com' &&
echo 'Delete old website data' &&
ssh archlinux-nginx 'rm -rfv /srv/http/wildw1ng.com/*' &&
echo 'Upload new website data' &&
rsync -ra --info=progress2 ~/sites/wildw1ng.com/public/ archlinux-nginx:/srv/http/wildw1ng.com &&
echo 'Change the owner to root' &&
ssh archlinux-nginx 'sudo chown -R root:root /srv/http/wildw1ng.com' &&
echo 'Show new website files' &&
ssh archlinux-nginx 'ls -la /srv/http/wildw1ng.com'
chmod 700 ~/bin/publish

Andreas Bauer. All rights reserved.

Let’s Encrypt

How to automatically renew Let’s Encrypt wildcard certificates with Certbot




IONOS API

Getting Started with the IONOS APIs

Lookup your API key


IONOS authentication hook

This hook is executed before certbot creates the DNS record.
It creates a temporary file containing a JSON payload with the DNS record data,
then uses the curl command to send a PUT request to the IONOS API to create the record.

/home/wildw1ng/bin/ionos-auth-hook
#!/bin/bash
IONOS_PUBLICPREFIX="YOUR_API_KEY"
IONOS_SECRET="YOUR_API_SECRET"


echo "{ \"data\": \"\$CERTBOT_VALIDATION\" }" > /tmp/ionos_payload.json
curl -s -X PUT -H "Content-Type: application/json" -H "Authorization: Basic \$(echo -n "$IONOS_PUBLICPREFIX:$IONOS_SECRET" | base64 -w 0)" -d @/tmp/ionos_payload.json "https://api.hosting.ionos.com/dns/v1/zones/\$CERTBOT_DOMAIN." -o /dev/null
chmod +x /home/wildw1ng/bin/ionos-auth-hook

IONOS cleanup hook

This hook is executed after certbot removes the DNS record.
It creates a temporary file containing a JSON payload with the DNS record data,
then uses the curl command to send a DELETE request to the IONOS API to delete the record.

/home/wildw1ng/bin/ionos-cleanup-hook
#!/bin/bash
IONOS_PUBLICPREFIX="YOUR_API_KEY"
IONOS_SECRET="YOUR_API_SECRET"

echo "{ \"data\": \"\$CERTBOT_VALIDATION\" }" > /tmp/ionos_payload.json
curl -s -X DELETE -H "Content-Type: application/json" -H "Authorization: Basic \$(echo -n "$IONOS_PUBLICPREFIX:$IONOS_SECRET" | base64 -w 0)" -d @/tmp/ionos_payload.json "https://api.hosting.ionos.com/dns/v1/zones/\$CERTBOT_DOMAIN." -o /dev/null
chmod +x /home/wildw1ng/bin/ionos-cleanup-hook

Note

Make sure to replace YOUR_API_KEY and YOUR_API_SECRET with your actual IONOS API credentials.


Renew Let’s Encrypt certificates with Certbot

/home/wildw1ng/bin/wildcard-renewal
#!/bin/bash
# Domain to renew
DOMAIN="wildw1ng.com"

# Check if certbot is installed
if ! command -v certbot &> /dev/null
then
    echo "Certbot could not be found. Please install it first."
    exit
fi

# Renew wildcard certificate
sudo certbot certonly \
    --non-interactive \
    --no-eff-email \
    --agree-tos \
    --staple-ocsp \
    --manual \
    --preferred-challenges=dns \
    --manual-auth-hook /home/wildw1ng/bin/ionos-auth-hook \
    --manual-cleanup-hook /home/wildw1ng/bin/ionos-cleanup-hook \
    -d "$DOMAIN" \
    -d "*.$DOMAIN" \
    -d "*.cozy.$DOMAIN"
chmod +x /home/wildw1ng/bin/wildcard-renewal

Service and timer for automatic renewal

/etc/systemd/system/certbot.service
[Unit]
Description=Let's Encrypt renewal

[Service]
Type=oneshot
ExecStart=/home/wildw1ng/bin/wildcard-renewal

/etc/systemd/system/certbot.timer
[Unit]
Description=Twice daily renewal of Let's Encrypt's certificates

[Timer]
OnCalendar=0/12:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target

Enable renewal service

systemctl enable certbot.timer

Andreas Bauer. All rights reserved.

NGINX

How to self host a NGINX HTTP server and reverse proxy




Installation

pacman -Syu nginx-mainline certbot certbot-nginx

Configuration

/etc/nginx/nginx.conf
user http;
worker_processes auto;
worker_cpu_affinity auto;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    multi_accept on;
    worker_connections  1024;
}


http {
    charset utf-8;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    log_not_found off;
    types_hash_max_size 4096;
    client_max_body_size 32M;

    # Excessive requests within the burst limit will be served immediately regardless of the specified rate,
    # requests above the burst limit will be rejected with the 503 error.
    # limit_req_zone $binary_remote_addr zone=one:20m rate=5r/s;
    # limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    # MIME
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;
    #access_log  logs/access.log  main;
    
    # load configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    gzip  on;
    gzip_vary on;
    gzip_min_length 10240;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
    gzip_disable "MSIE [1-6]\.";
}


include /etc/nginx/passthrough.conf;

Managing server entries

Put different server blocks in different files.
This allows you to easily enable or disable certain sites.

Server block configuration files

mkdir /etc/nginx/sites-available

Symlinks to enable sites

mkdir /etc/nginx/sites-enabled

Enable HTTP server

systemctl enable nginx

Configure SSL

/etc/letsencrypt/options-ssl-nginx.conf
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;

Andreas Bauer. All rights reserved.

Website server block

How to setup a server block for your website




Server block configuration

/etc/nginx/sites-available/lnxsrv.org.conf
server {
    listen 443 ssl;
    http2  on;
    
#   listen [::]:443 ssl http2;

    server_name lnxsrv.org;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/lnxsrv.org.error.log;
    access_log  /var/log/nginx/lnxsrv.org.access.log;

    # How long Nginx is waiting between the writes of the client body
    # client_body_timeout 10s;
    # How long Nginx is waiting between the writes of client header
    # client_header_timeout 10s;

        location / {
            root   /srv/http/lnxsrv.org;
            index  index.html index.htm;
	    # limit_req zone=one burst=60 nodelay;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/lnxsrv.org/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/lnxsrv.org/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;
    
    # Cache-control Directive Header
    #add_header Surrogate-Control "public, no-transform, no-cache, max-age=86400";
    expires 1d;    
    add_header Cache-Control "public, no-transform";

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Content Security Policy (CSP) Header
    # add_header Content-Security-Policy "default-src 'self';" always;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/lnxsrv.org/chain.pem; # managed by Certbot
    
    # OCSP stapling   
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
}

server {
    if ($host = lnxsrv.org) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
#   listen  [::]:80;
    server_name  lnxsrv.org;
    return 404; # managed by Certbot
}

ln -s /etc/nginx/sites-available/lnxsrv.org.conf /etc/nginx/sites-enabled/lnxsrv.org.conf

Check nginx configuration file syntax

nginx -t

Restart service

systemctl restart nginx.service

unlink /etc/nginx/sites-enabled/lnxsrv.org.conf

Andreas Bauer. All rights reserved.

Cozy reverse proxy

How to setup a reverse proxy for Cozy




Server block configuration

/etc/nginx/sites-available/cozy.wildw1ng.com.conf
server {
    listen 443 ssl;
    http2  on;
    
#   listen [::]:443 ssl http2;

    server_name .cozy.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/cozy.wildw1ng.com.error.log;
    access_log  /var/log/nginx/cozy.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;
    
    # Limit max upload size
    client_max_body_size 1g;

    location / {
        # IP address of cozy server
	    proxy_pass         http://10.0.1.15:8080;        
        proxy_http_version 1.1;
        proxy_redirect http:// https://;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection connection_upgrade;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;
    
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/wildw1ng.com/chain.pem; # managed by Certbot

    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
}

server {
    if ($host = .cozy.wildw1ng.com) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
#   listen  [::]:80;
    server_name *.cozy.wildw1ng.com;
    return 404; # managed by Certbot
}

ln -s /etc/nginx/sites-available/cozy.wildw1ng.com.conf /etc/nginx/sites-enabled/cozy.wildw1ng.com.conf

Check nginx configuration file syntax

nginx -t

Restart service

systemctl restart nginx.service

unlink /etc/nginx/sites-enabled/cozy.wildw1ng.com.conf

Andreas Bauer. All rights reserved.

Guacamole reverse proxy

How to setup a reverse proxy for Guacamole




Server block configuration

/etc/nginx/sites-available/guac.wildw1ng.com.conf
server {
    listen 443 ssl;
    http2  on;
    
#   listen [::]:443 ssl http2;

    server_name guac.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/guac.wildw1ng.com.error.log;
    access_log  /var/log/nginx/guac.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;

    location / {
        # IP address of guacamole server
        proxy_pass         http://10.0.1.12:8080/guacamole/;        
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;
    
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/wildw1ng.com/chain.pem; # managed by Certbot

    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

}

server {
    if ($host = guac.wildw1ng.com) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
#   listen  [::]:80;
    server_name  guac.wildw1ng.com;
    return 404; # managed by Certbot
}

ln -s /etc/nginx/sites-available/guac.wildw1ng.com.conf /etc/nginx/sites-enabled/guac.wildw1ng.com.conf

Check nginx configuration file syntax

nginx -t

Restart service

systemctl restart nginx.service

unlink /etc/nginx/sites-enabled/guac.wildw1ng.com.conf

Andreas Bauer. All rights reserved.

Plex reverse proxy

How to setup a reverse proxy for Plex




Server block configuration

/etc/nginx/sites-available/plex.wildw1ng.com.conf
server {
    listen 443 ssl;
    http2  on;
    
#   listen [::]:443 ssl http2;

    server_name plex.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/plex.wildw1ng.com.error.log;
    access_log  /var/log/nginx/plex.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;

    location / {
        # IP address of Plex Media Server
        proxy_pass http://10.0.1.11:32400;
        proxy_buffering     off;
        proxy_redirect      off;
        proxy_http_version  1.1;
        proxy_set_header    X-Real-IP       $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header    Upgrade         $http_upgrade;
        proxy_set_header    Connection      $http_connection;
        proxy_cookie_path   /web/           /;
        # access_log          off;
	}
        
	error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/wildw1ng.com/chain.pem; # managed by Certbot
    
    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot

}

server {
	if ($host = plex.wildw1ng.com) {
	return 301 https://$host$request_uri;
	} # managed by Certbot
	listen       80;
#   	listen  [::]:80;
	server_name  plex.wildw1ng.com;
	return 404; # managed by Certbot
}

ln -s /etc/nginx/sites-available/plex.wildw1ng.com.conf /etc/nginx/sites-enabled/plex.wildw1ng.com.conf

Check nginx configuration file syntax

nginx -t

Restart service

systemctl restart nginx.service

Configuring the Plex Media Server

Browse to http://localhost:32400/web/

Settings > Network

plex-custom-server-access-url Within the field Custom Server Access URL’s add http://plex.wildw1ng.com:80,https://plex.wildw1ng.com:443

plex-secure-connections Also make sure to change the Secure Connections setting to ‘Preferred’.


unlink /etc/nginx/sites-enabled/plex.wildw1ng.com.conf

Andreas Bauer. All rights reserved.

PostfixAdmin reverse proxy

How to setup a reverse proxy for PostfixAdmin




Prepare server block for certbot

/etc/nginx/sites-available/postfixadmin.wildw1ng.com
server {
    listen 80;

    server_name postfixadmin.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/postfixadmin.wildw1ng.com.error.log;
    access_log  /var/log/nginx/postfixadmin.wildw1ng.com.access.log;

    location / {
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Server $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://10.0.1.18/postfixadmin/;
    } 

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
}

ln -s /etc/nginx/sites-available/postfixadmin.wildw1ng.com /etc/nginx/sites-enabled/postfixadmin.wildw1ng.com

Get SSL certificates with Certbot via Let’s Encrypt

certbot --nginx --staple-ocsp

Server block configuration

/etc/nginx/sites-available/postfixadmin.wildw1ng.com
erver {
    listen 443 ssl http2;
#   listen [::]:443 ssl http2;

    server_name postfixadmin.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/postfixadmin.wildw1ng.com.error.log;
    access_log  /var/log/nginx/postfixadmin.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/postfixadmin.wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/postfixadmin.wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;

    location / {
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Server $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://10.0.1.18/postfixadmin/;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/postfixadmin.wildw1ng.com/chain.pem; # managed by Certbot

    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 30s;
}

server {
    if ($host = postfixadmin.wildw1ng.com) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
#   listen  [::]:80;
    server_name  postfixadmin.wildw1ng.com;
    return 404; # managed by Certbot
}

Restart service

systemctl restart nginx.service

unlink ln -s /etc/nginx/sites-enabled/plex.wildw1ng.com

Andreas Bauer. All rights reserved.

Virtual Mail Server reverse proxy

How to setup a reverse proxy for Virtual Mail Server




Prepare server block for certbot

/etc/nginx/sites-available/mail.wildw1ng.com
server {
    listen 80;

    server_name mail.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/mail.wildw1ng.com.error.log;
    access_log  /var/log/nginx/mail.wildw1ng.com.access.log;

    location / {
        # IP address of mail server
        proxy_pass         http://10.0.1.18/;
        proxy_set_header X-Real-IP $remote_addr;    
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
}

ln -s /etc/nginx/sites-available/mail.wildw1ng.com /etc/nginx/sites-enabled/mail.wildw1ng.com

Get SSL certificates with Certbot via Let’s Encrypt

certbot --nginx --staple-ocsp

Server block configuration

/etc/nginx/sites-available/mail.wildw1ng.com
server {
    listen 443 ssl http2;

    server_name mail.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/mail.wildw1ng.com.error.log;
    access_log  /var/log/nginx/mail.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/mail.wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mail.wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;

    location / {
        # IP address of mail server
        proxy_pass         http://10.0.1.18/;
        proxy_set_header X-Real-IP $remote_addr;

        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/mail.wildw1ng.com/chain.pem; # managed by Certbot

    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
}

server {
    if ($host = mail.wildw1ng.com) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
    server_name  mail.wildw1ng.com;
    return 404; # managed by Certbot
}

Restart service

systemctl restart nginx.service

unlink ln -s /etc/nginx/sites-enabled/plex.wildw1ng.com

Andreas Bauer. All rights reserved.

Zabbix reverse proxy

How to setup a reverse proxy for Zabbix




Prepare server block for certbot

/etc/nginx/sites-available/zabbix.wildw1ng.com
server {
    listen 80;

    server_name zabbix.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/zabbix.wildw1ng.com.error.log;
    access_log  /var/log/nginx/zabbix.wildw1ng.com.access.log;

    location / {
        # IP address of Zabbix server
        proxy_pass         http://archlinux-zabbix/zabbix/;
        proxy_http_version 1.1;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_set_header   X-Forwarded-Server $host;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cookie_path /zabbix /;       
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
}

ln -s /etc/nginx/sites-available/zabbix.wildw1ng.com /etc/nginx/sites-enabled/zabbix.wildw1ng.com

Get SSL certificates with Certbot via Let’s Encrypt

certbot --nginx --staple-ocsp

Server block configuration

/etc/nginx/sites-available/zabbix.wildw1ng.com
server {
    listen 443 ssl http2;
#   listen [::]:443 ssl http2;

    server_name zabbix.wildw1ng.com;

    rewrite     https://$host$request_uri?  permanent;

    error_log   /var/log/nginx/zabbix.wildw1ng.com.error.log;
    access_log  /var/log/nginx/zabbix.wildw1ng.com.access.log;

    # These are the paths to your generated Let's Encrypt SSL certificates.
    ssl_certificate /etc/letsencrypt/live/zabbix.wildw1ng.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/zabbix.wildw1ng.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_session_cache   shared:SSL:60m;

    location / {
        # IP address of Zabbix server
        proxy_pass         http://archlinux-zabbix/zabbix/;
        proxy_http_version 1.1;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_set_header   X-Forwarded-Server $host;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cookie_path /zabbix /;       
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    
    # Anti-MIME-Sniffing header
    add_header X-Content-Type-Options nosniff;

    # Anti-ClickJacking Header
    add_header  X-Frame-Options "SAMEORIGIN" always;

    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000" always;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/zabbix.wildw1ng.com/chain.pem; # managed by Certbot

    # OCSP stapling
    ssl_stapling on; # managed by Certbot
    ssl_stapling_verify on; # managed by Certbot
}

server {
    if ($host = zabbix.wildw1ng.com) {
    return 301 https://$host$request_uri;
    } # managed by Certbot
    listen       80;
#   listen  [::]:80;
    server_name  zabbix.wildw1ng.com;
    return 404; # managed by Certbot
}

Restart service

systemctl restart nginx.service

unlink /etc/nginx/sites-enabled/zabbix.wildw1ng.com

Andreas Bauer. All rights reserved.

Samba active directory

How to setup an active directory domain controller in Linux using Samba




Install packages

pacman -Syu krb5 python-dnspython openresolv samba bind

Rename machine

Note

Windows NetBIOS names are limited to 15 characters (16-bytes)

/etc/hostname
arch-vm-addc

Setup network

Wired NAT adapter using a static IP

/etc/systemd/network/20-wired.network
[Match]
Name=enp1*

[Network]
Address=192.168.122.30/24
Gateway=192.168.122.1
DNS=127.0.0.1
chmod 644 /etc/systemd/network/20-wired.network

Tip

Second bridged wired adapter using DHCP for ssh access

/etc/systemd/network/21-wired.network
[Match]
Name=enp8*

[Network]
DHCP=yes
chmod 644 /etc/systemd/network/21-wired.network

Use local DNS server

Reconfigure resolvconf to use only localhost for DNS lookups.

/etc/resolv.conf.tail
# Samba configuration
search wildw1ng.local
nameserver 127.0.0.1

Set permissions

chmod 644 /etc/resolv.conf.tail

Regenerate the new file

resolvconf -u

read more…


System clock synchronization

read about systemd-timesyncd


Provisioning

samba-tool-provisioning Performing basic directory configuration.

samba-tool domain provision --use-rfc2307 --interactive

–use-rfc2307

this argument adds POSIX attributes (UID/GID) to the AD Schema. This will be necessary if you intend to authenticate Linux, BSD, or macOS clients (including the local machine) in addition to Microsoft Windows.

–interactive

this parameter forces the provision script to run interactively.


BIND configuration

/etc/named.conf
// vim:set ts=4 sw=4 et:
acl local-networks {
    127.0.0.0/8;
    192.168.122.0/24;
};

options {
    directory "/var/named";
    pid-file "/run/named/named.pid";
    session-keyfile "/run/named/session.key";

    // Uncomment this line to enable IPv6 connections support
    //  listen-on-v6 { any; };
    // Add this for no IPv4:
    //  listen-on { none; };

    // Add any subnets or hosts you want to allow to the local-networks acl
    allow-query       { local-networks; };
    allow-recursion   { local-networks; };
    allow-query-cache { local-networks; };
    allow-transfer    { none; };
    allow-update      { none; };

    version none;
    hostname none;
    server-id none;

    auth-nxdomain yes;
    datasize default;
    empty-zones-enable no;
    tkey-gssapi-keytab "/var/lib/samba/private/dns.keytab";

    // Uncomment if you wish to use ISP forwarders
    // Google (8.8.8.8, 8.8.4.4, 2001:4860:4860::8888, and 2001:4860:4860::8844)
    // OpenDNS (208.67.222.222, 208.67.220.220, 2620:0:ccc::2 and 2620:0:ccd::2)
    // Appropriate values for subnets are specific to your network.
    // forwarders { 8.8.8.8; 8.8.8.4; };

};

zone "localhost" IN {
    type master;
    file "localhost.zone";
};

zone "0.0.127.in-addr.arpa" IN {
    type master;
    file "127.0.0.zone";
};

zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" {
    type master;
    file "localhost.ip6.zone";
};

// Load AD integrated zones
dlz "AD DNS Zones" {
    database "dlopen /usr/lib/samba/bind9/dlz_bind9_12.so";
};

//zone "example.org" IN {
//    type slave;
//    file "example.zone";
//    masters {
//        192.168.1.100;
//    };
//    allow-query { any; };
//    allow-transfer { any; };
//};

logging {
    channel xfer-log {
        file "/var/log/named.log";
            print-category yes;
            print-severity yes;
            severity info;
        };
        category xfer-in { xfer-log; };
        category xfer-out { xfer-log; };
        category notify { xfer-log; };
};

chmod 644 /etc/named.conf
chgrp named /var/lib/samba/private/dns.keytab
chmod g+r /var/lib/samba/private/dns.keytab
touch /var/log/named.log
chown root:named /var/log/named.log
chmod 664 /var/log/named.log

Kerberos

Provisioning created a krb5.conf file for use with a Samba domain controller.

mv /etc/krb5.conf{,.default}
cp /var/lib/samba/private/krb5.conf /etc

/etc/krb5.conf
[libdefaults]
        default_realm = WILDW1NG.LOCAL
        dns_lookup_realm = false
        dns_lookup_kdc = true

[realms]
WILDW1NG.LOCAL = {
        default_domain = WILDW1NG.LOCAL
}

[domain_realm]
        arch-vm-addc = WILDW1NG.LOCAL
chmod 644 /etc/krb5.conf

Samba

Enable printing and automatic sharing of all CUPS print queues

/etc/samba/smb.conf
[global]
        rpc_server:spoolss = external
        rpc_daemon:spoolssd = fork
        printing = CUPS

[printers]
       path = /var/spool/samba/
       printable = yes

Share only specific print queues

/etc/samba/smb.conf
[global]
        load printers = no

# Add and example print share
[HPDJ3050]
       path = /var/spool/samba/
       printable = yes
       printer name = hpdj3050

Roaming profiles

chmod 0777 /profiles

Create samba share

/etc/samba/smb.conf
[profiles]
    comment = User Profiles
    path = /profiles
    browseable = no
    read only = no
    csc policy = disable
    vfs objects = acl_xattr

/etc/samba/smb.conf
# Global parameters
[global]
        netbios name = ARCH-VM-ADDC
        realm = WILDW1NG.LOCAL
        server role = active directory domain controller
        server services = s3fs, rpc, nbt, wrepl, ldap, cldap, kdc, drepl, winbindd, ntp_signd, kcc, dnsupdate
        workgroup = WILDW1NG
        idmap_ldb:use rfc2307 = yes
        tls enabled = yes
        tls keyfile = tls/key.pem
        tls certfile = tls/cert.pem
        tls cafile = tls/ca.pem
        # rpc_server:spoolss = external
        # rpc_daemon:spoolssd = fork
        # printing = CUPS
[sysvol]
        path = /var/lib/samba/sysvol
        read only = No

[netlogon]
        path = /var/lib/samba/sysvol/wildw1ng.local/scripts
        read only = No

# [printers]
        # path = /var/spool/samba
        # printable = yes

[profiles]
        comment = User Profiles
        path = /profiles
        browseable = no
        read only = no
        csc policy = disable
        vfs objects = acl_xattr
chmod 644 /etc/samba/smb.conf

LDB utilities

/etc/profile.d/sambaldb.sh
export LDB_MODULES_PATH="${LDB_MODULES_PATH}:/usr/lib/samba/ldb"
chmod 0755 /etc/profile.d/sambaldb.sh
. /etc/profile.d/sambaldb.sh

Testing the installation

Verify tcp-based _ldap SRV record in the domain verify-tcp-based_ldap-srv-record-in-the-domain

host -t SRV _ldap._tcp.wildw1ng.local

Verify udp-based _kerberos SRV resource record in the domain verify-udp-based_kerberos-srv-resource-record

host -t SRV _kerberos._udp.wildw1ng.local

Verify A record of the domain controller verify-a-record-of-the-domain-controller

host -t A arch-vm-addc.wildw1ng.local

Verify NT password authentication verify-nt-password-authentication

smbclient //localhost/netlogon -U Administrator -c 'ls'

Verify Kerberos is working as expected verify-kerberos-is-working-as-expected

kinit Administrator@wildw1ng.local
Note

If the “KDC reply did not match expectations while getting initial credentials” error occurs, check your /etc/krb5.conf.
Ensure that all Realm names are in upper case letters.

List cached Kerberos tickets list-cached-kerberos-tickets

klist

Use smbclient with acquired ticket use-smbclient-with-acquired-ticket

smbclient //arch-vm-addc/netlogon -k -c 'ls'

DNS reverse lookup

Create a reverse lookup zone for each subnet in your environment in DNS.
It is important that this is kept in Samba’s DNS as opposed to BIND to allow for dynamic updates by clients.
Use the first three octets of the subnet in reverse order (for example: 192.168.0.0/24 becomes 0.168.192)

Create a reverse lookup zone for each subnet

samba-tool dns zonecreate arch-vm-addc.wildw1ng.local 122.168.192.in-addr.arpa -U Administrator

Add a record for you server (if your server is multi-homed, add for each subnet). Add the fourth octet of the IP for the server.

samba-tool dns add arch-vm-addc.wildw1ng.local 122.168.192.in-addr.arpa 30 PTR arch-vm-addc.wildw1ng.local -U Administrator

Verify the lookup verify-the-lookup

host -t PTR 192.168.122.30

Verify the file server verify-the-file-server

smbclient -L localhost -N

Enable services

systemctl enable named
systemctl enable samba

read more…


Manage roaming user profiles

Windows RSAT tools on Windows Client

roaming-profiles

Use ‘Active Directory Users and Computers’ application on a Windows client to set the path to the user’s roaming profile and shared home directory. profile-properties

User profile \\arch-vm-addc\profiles\%username%

Home folder \\arch-vm-addc\shared\%username%

Windows client OS sersion Windows Server OS version Profile suffix Profile directory name
Windows NT 4.0 - Windows Vista Windows NT Server 4.0 - Windows Server 2008 none user
Windows 7 Windows Server 2008 R2 V2 user.V2
Windows 8.0 - 8.1* Windows Server 2012 - 2012 R2* V3 user.V3
Windows 8.1* Windows Server 2012 R2* V4 user.V4
Windows 10 (1507 to 1511) Windows Server 2016 V5 user.V5
Windows 10 (1607 and later) V6 user.V6

Manage user profiles with Samba

samba-tool user list
samba-tool user create User11 Password11
 --use-username-as-cn --surname="User"
 --given-name="11" --initials=U11
 --mail-address=User11@wildw1ng.local
 --company="Company inc." --script-path=shire.bat
 --profile-path=\\\\arch-vm\\profiles\\User11
 --home-drive=Z
 --home-directory=\\\\arch-vm\\shared\\User11
 --job-title="Fancy title"

read more…


Manage group policies

group-policy-management Samba policies can be found in the ‘Group Policy Management Editor’ within User or

Computer Configuration > Policies > Administrative Templates > Samba

For Samba Domain Controllers, the Password and Kerberos settings are also applied, which are found in

Computer Configuration > Policies > OS Settings > Security Settings > Account Policy.


Andreas Bauer. All rights reserved.

Additional domain controllers

How to add additional domain controllers to an existing domain in Linux




Install packages

pacman -Syu krb5 python-dnspython openresolv samba bind

Rename machine

Note

Windows NetBIOS names are limited to 15 characters (16-bytes)

/etc/hostname
arch-vm-dc

Setup network

Wired adapter using a static IP (NAT)

/etc/systemd/network/20-wired.network
[Match]
Name=enp1*

[Network]
Address=192.168.122.31/24
Gateway=192.168.122.1
DNS=192.168.122.30

chmod 644 /etc/systemd/network/20-wired.network
Tip

Second bridged wired adapter using DHCP for ssh access

/etc/systemd/network/21-wired.network
[Match]
Name=enp8*

[Network]
DHCP=yes
chmod 644 /etc/systemd/network/21-wired.network

Use local DNS server

Reconfigure resolvconf to use only localhost for DNS lookups.

/etc/resolv.conf.tail
# Samba configuration
search wildw1ng.local
nameserver 192.168.122.30

Set permissions

chmod 644 /etc/resolv.conf.tail

Regenerate the new file

resolvconf -u

System clock synchronization

read about systemd-timesyncd


Join an existing domain as a new Domain Controller

join-an-existing-domain-as-a-new-domain-controller

samba-tool domain join wildw1ng.local DC -U "WILDW1NG\Administrator"

Copy the krb5.conf:

cp /var/lib/samba/private/krb5.conf /etc/krb5.conf
/etc/krb5.conf

[libdefaults]
        default_realm = WILDW1NG.LOCAL
        dns_lookup_realm = false
        dns_lookup_kdc = true

[realms]
WILDW1NG.LOCAL = {
        default_domain = wildw1ng.local
}

[domain_realm]
        ARCH-VM-DC2 = WILDW1NG.LOCAL
chmod 644 /etc/krb5.conf

Copy the idmap

from existing domain controller machine

tdbbackup -s .bak /var/lib/samba/private/idmap.ldb
mv  /var/lib/samba/private/idmap.ldb.bak /home/wildw1ng/
chown wildw1ng:users idmap.ldb.bak
rsync -avhP ~/idmap.ldb.bak 192.168.122.33:/home/wildw1ng/

to new machine

mv ~/idmap.ldb.bak /var/lib/samba/private/idmap.ldb
chown root:root /var/lib/samba/private/idmap.ldb
chmod 600 /var/lib/samba/private/idmap.ldb
Note

If you intend to keep multiple DCs, you will need to automate this process going forward using one of the methods listed on the Samba website here.
This also applies to transferring the idmap from Windows DCs.


Enable services

systemctl enable named
systemctl enable samba

BIND9_DLZ DNS backend

samba_upgradedns --dns-backend=BIND9_DLZ

Restart named.service

systemctl restart named

Update DNS records

samba_dnsupdate --all-names --use-samba-tool --verbose

domain-controllers

read more…