How to create a router from Centos 8 Stream

Today, I need to rebuild and create a new one linux router. It will work with many networks, there will be DHCP and DNS server as well.

So, at first, we will secure our server on service: ssh. I assume, that you already have enabled firewalld and public zone, only for sshd service. And internal network with dhcp or anything else.

SSHD deamon

At first, we configure our ssh service to be secure. Edit ssh configuration file and adjust there variables:

vim /etc/ssh/sshd_config
Port 22
Protocol 2
PermitRootLogin no
PubkeyAuthentication yes
PermitEmptyPasswords no
PasswordAuthentication no
AllowUsers bob

Now, we create on our client machine (in my place Ubuntu desktop PC) a new rsa-key pair for ssh authentication. You can set some passphrase, but it is you opinion. In quotas, there is a comment for there key pair.

ssh-keygen -t rsa -b 4096 -C "bob@example.com"
means:
-b bits. Number of bits in the key to create
-t type. Specify type of key to create
-C comment

When you had these key pairs generated (under ~/.ssh/) like: id_rsa and id_rsa_pub, you can adjust your server, for accepting these key pair. Now, you must authorize to your server by password (last time).

ssh-copy-id bob@IP-OF-SERVER

If everything is OK, now you can restart your sshd service on server, to load new configurations:

systemctl restart sshd.service

And we can test, that other users is NOT allowed, and there is a need for key authentication:

ssh Alice@192.168.1.2
  Alice@192.168.1.2: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

But Bob is allowed:

ssh bob@192.168.1.2
   Last login: Tue Aug 31 14:47:13 2021 from...

Setting up public interface

We use nmcli (command-line tool for controlling NetworkManager) for watching out of our interfaces:

nmcli device status
DEVICE  TYPE      STATE        CONNECTION 
ens1   ethernet  connected    ens1       
ens2    ethernet  unmanaged    --         
ens3    ethernet  unmanaged    --         
ens4   ethernet  unmanaged    --         
ens5   ethernet  unmanaged    --         
lo      loopback  unmanaged    -- 

Unmanaged interfaces are edited here:

vim /etc/NetworkManager/conf.d/99-unmanaged-devices.conf
[keyfile]
unmanaged-devices=interface-name:ens2;interface-name:ens3;interface-name:ens4;interface-name:ens5

And our ens1 is in config below. UUID we can generate with command:

uuidgen ens1
cat /etc/sysconfig/network-scripts/ifcfg-ens1 
#ens1 - Internal Subnet
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=none
DEFROUTE=no
IPV4_FAILURE_FATAL=no
IPV6_FAILURE_FATAL=no
IPV6INIT=no
NAME=ens1
UUID=e765c8d4-f05e-43b2-96dc-71eef59a908a
DEVICE=ens1
ONBOOT=yes
IPADDR=192.168.1.2
PREFIX=24
GATEWAY=192.168.1.1
DNS1=192.168.1.1

Now, we can configure our public interface. In our example, it will be interface ens2. So, we can copy our first network-script for interface ens2 and edit it. First, generate uuid for this interface. Then adjust your public IP numbers.

uuidgen ens2
cp /etc/sysconfig/network-scripts/ifcfg-ens1 /etc/sysconfig/network-scripts/ifcfg-ens2
vim /etc/sysconfig/network-scripts/ifcfg-ens2
----reboot----
nmcli device status
DEVICE  TYPE      STATE        CONNECTION 
ens1   ethernet  connected    ens1       
ens2    ethernet  connected    ens2  

Now, after some time, we can see many ssh attacks to our system. We can make more things. One is, change the port for ssh daemon. Or I use fail2ban services, for ban IP, that try to hack our server. Like:

Aug 31 13:50:45 r3 sshd[3551]: Invalid user support from 116.98.160.162 port 17306
Aug 31 13:50:48 r3 sshd[3553]: Invalid user geral from 116.98.160.162 port 17006
Aug 31 13:50:49 r3 sshd[3555]: Invalid user omega from 116.110.97.171 port 57344
Aug 31 13:50:51 r3 sshd[3557]: Invalid user matt from 116.98.160.162 port 49776
Aug 31 13:50:57 r3 sshd[3559]: Invalid user rebecca from 116.110.97.171 port 38964
Aug 31 13:51:02 r3 sshd[3561]: Invalid user admin from 171.240.203.115 port 32926
Aug 31 13:51:24 r3 sshd[3564]: Invalid user support from 116.110.97.171 port 49868
Aug 31 13:55:38 r3 sshd[3603]: Invalid user vasilias from 161.97.112.53 port 54654
Aug 31 13:55:42 r3 sshd[3605]: Invalid user admin from 161.97.112.53 port 54656
Aug 31 13:59:53 r3 sshd[3734]: Invalid user user from 2.56.59.30 port 45564

Or we can count it (after two hours). We had 644 ssh login attempts to our server:

cat /var/log/secure | grep Invalid | wc -l
644

So, find I will install fail2ban services, for ban these IPs…

dnf install -y fail2ban-firewalld.noarch

Now, we must create a new jail.local, where we define actions for those IPs and services. So, create this file and add some variables. If you wish, adjust by your needs:

vim /etc/fail2ban/jail.local

[DEFAULT]
# Ban IP/hosts for 24 hour ( 24h*3600s = 86400s):
bantime = 86400
 
# An ip address/host is banned if it has generated "maxretry" during the last "findtime" seconds.
findtime = 900
maxretry = 2

# Enable sshd protection
[sshd]
enabled = true

Now, we can start/restart fail2ban services and view its status. And next, use fail2ban client to view detailed status:

systemctl restart fail2ban
systemctl status fail2ban

fail2ban-client status
fail2ban-client status sshd

Now, we must see some IPs, that are already banned by our fail2ban. If you wish to manually ban/unban IP, lets do this:

fail2ban-client unban 192.168.1.1
fail2ban-client set sshd banip 18.188.124.148

Or you can set your trusted IPs to whitelist:

vim /etc/fail2ban/jail.conf

[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.1

Time server – chronyd

Now, we install chronyd service, for obtain and distributing time from world to our subnets. So, install chronyd, and set localisation to you system

dnf install chrony -y
systemctl enable chronyd
timedatectl set-timezone Europe/Bratislava
timedatectl

Now, edit some variables in configurations file for chronyd. Add some servers from pool, and edit local subnets, where we delived time:

vim /etc/chrony.conf

pool 2.centos.pool.ntp.org iburst
pool 1.centos.pool.ntp.org iburst
pool 3.centos.pool.ntp.org iburst
allow 192.168.1.0/24

Now start/restart our service, and check, if it is working:

systemctl restart chronyd
systemctl status chronyd.service
chronyc sources

IPV4 forwarding

As the first things, we must check, if our system has enabled IP forwarding in kernel. Check it out:

sysctl -a | grep ip_forward

net.ipv4.ip_forward = 1

If not, enable it:

sysctl -w net.ipv4.ip_forward=1

vim /etc/sysctl.d/99-sysctl.conf
#insert:
net.ipv4.ip_forward=1

Setting up nftables

In out environment, we use nftables. It is default backend firewall module in Centos 8/ RHEL 8. It is already managed by firewalld, but we gonna use native nft command. The nftables is able to collapse firewall management for IPv4, IPv6 and bridging into the single command line utility: nft.

So, at the beginning (or before run our later nft script), we disable firewalld utility:

systemctl disable --now firewalld
systemctl mask firewalld
reboot

Creating Tables and Chains

Rebooting the system after disabling firewalld will ensure that we have no remnants of the tables, chains and rules added by firewalld. We start up with nothing in-place. Unlike iptables, nftables does not have any tables or chains created by default. Managing nftables natively requires us to create these objects:

protocol family: At the top level of the configuration tree we have protocol families: ip, ip6, arp, bridge, netdev and inet. Using inet we can cater for both IPv4 and IPv6 rules in the one table.

table: Below the protocol family we define tables to group chains together. The default table was the filter table in iptables but we also had other tables such as the nat table.

chain: A chain presents a set of rules that are read from the top down. We stop reading once we match a rule. When creating a chain in nftables we can specify the type of chain – we use the filter type, the hook – we will be working with input hook and a priority. The lower the number the higher the priority and the highest priority chain takes precedence. We will also specify the default policy which defines the action if no rule is met.

To list existing tables, we shouldn’t have any, we use the following:

nft list tables

Now, we create s script file, in which will be all our rules. So, you have to do some learning about nft. Here you are the basic rules:

cat /root/nftables.sh

#!/bin/bash
# the executable for nftables
nft="/usr/sbin/nft"

# wan and lan ports. Home routers typically have two ports minimum.
wan=ens2
lan=ens1
wan6=ens3

# flush/reset rules
${nft} flush ruleset

#create tables called "filter" for ipv4 and ipv6
${nft} add table ip filter
${nft} add table ip6 filter

# one more table called 'nat' for our NAT/masquerading
${nft} add table nat

#create chains
${nft} add chain filter input { type filter hook input priority 0 \; }
${nft} add chain filter output {type filter hook output priority 0 \; }
${nft} add chain filter forward {type filter hook forward priority 0 \; }
${nft} add chain filter postrouting {type filter hook postrouting priority 0 \; }
${nft} add chain nat postrouting {type nat hook postrouting priority 100 \; }

# and for ipv6
${nft} add chain ip6 filter input { type filter hook input priority 0 \; }
${nft} add chain ip6 filter output {type filter hook output priority 0 \; }
${nft} add chain ip6 filter forward {type filter hook forward priority 0 \; }
${nft} add chain ip6 filter postrouting {type filter hook postrouting priority 0 \; }
${nft} add chain ip6 filter nat {type nat hook postrouting priority 100 \; }

#FORWARDING RULESET

#forward traffic from WAN to LAN if related to established context
${nft} add rule filter forward iif $wan oif $lan ct state { established, related } accept

#forward from LAN to WAN always
${nft} add rule filter forward iif $lan oif $wan accept

#drop everything else from WAN to LAN
${nft} add rule filter forward iif $wan oif $lan counter drop

#ipv6 just in case we have this in future.
${nft} add rule ip6 filter forward iif $wan6 oif $lan ct state { established,related } accept
${nft} add rule ip6 filter forward iif $wan6 oif $lan icmpv6 type echo-request accept

#forward ipv6 from LAN to WAN.
${nft} add rule ip6 filter forward iif $lan oif $wan6 counter accept

#drop any other ipv6 from WAN to LAN
${nft} add rule filter forward iif $wan6 oif $lan counter drop

#INPUT CHAIN RULESET
#============================================================================
${nft} add rule filter input ct state { established, related } accept

# always accept loopback
${nft} add rule filter input iif lo accept
# comment next rule to disallow ssh in
${nft} add rule filter input tcp dport ssh counter log accept

#accept DNS, SSH, from LAN
${nft} add rule filter input iif $lan tcp dport { 53, 22 } counter log accept
#accept DNS on LAN
${nft} add rule filter input iif $lan udp dport { 53 } accept

#accept ICMP on the LAN,WAN
${nft} add rule filter input iif $lan ip protocol icmp accept
${nft} add rule filter input iif $wan ip protocol icmp accept

${nft} add rule filter input counter drop

################# ipv6 ###################
${nft} add rule ip6 filter input ct state { established, related } accept
${nft} add rule ip6 filter input iif lo accept
#uncomment next rule to allow ssh in over ipv6
#${nft} add rule ip6 filter input tcp dport ssh counter log accept

${nft} add rule ip6 filter input icmpv6 type { nd-neighbor-solicit, echo-request, nd-router-advert, nd-neighbor-advert } accept

${nft} add rule ip6 filter input counter drop


#OUTPUT CHAIN RULESET
#=======================================================
# allow output from us for new, or existing connections.
${nft} add rule filter output ct state { established, related, new } accept

# Always allow loopback traffic
${nft} add rule filter output iif lo accept

${nft} add rule ip6 filter output ct state { established, related, new } accept
${nft} add rule ip6 filter output oif lo accept


#SET MASQUERADING DIRECTIVE
${nft} add rule nat postrouting masquerade

So, this script can be run by:

sh /root/nftables.sh

And now, we can view our filter and rules:

nft list tables
nft list table filter

This is only for this session, and after reboot, there will be empty list. To ensure, that filter persist reboot, run following:

nft list ruleset > /etc/sysconfig/nftables.conf
systemctl enable nftables.service
reboot
nft list table inet filter

Install and create own DNS resolver

Now we set up a local DNS resolver on CentOS 8/RHEL 8, with the widely-used BIND9 DNS software.

Be aware that a DNS server can also called a name server. Examples of DNS resolver are 8.8.8.8 (Google public DNS server) and 1.1.1.1 (Cloudflare public DNS server).

Normally, your computer or router uses your ISP’s DNS resolver to query domain names in order to get an IP address. Running your own local DNS resolver can speed up DNS lookups, because

  1. The local DNS resolver only listens to your DNS requests and does not answer other people’s DNS requests, so you have a much higher chance of getting DNS responese directly from the cache on the resolver.
  2. The network latency between your computer and DNS resolver is eliminated (almost zero), so DNS queries can be sent to root DNS servers more quickly.

If you own a website and want your own DNS server to handle name resolution for your domain name instead of using your domain registrar’s DNS server, then you will need to set up an authoritative DNS server, which is different than a DNS resolver. BIND can act as an authoritative DNS server and a DNS resolver at the same time

BIND (Berkeley Internet Name Domain) is an open-source DNS server software widely used on Unix/Linux due to it’s stability and high quality.

So, install it first, check version and start it. The BIND daemon is called named.

dnf update
dnf install bind bind-utils

named -v
    BIND 9.11.26-RedHat-9.11.26-6.el8 (Extended Support Version) <id:3ff8620>

systemctl start named
systemctl enable named

Usually, DNS queries are sent to the UDP port 53. The TCP port 53 is for responses size larger than 512 bytes. You must adjust your firewall to allow tcp and udp packets on port 53.

We can check the status of the BIND name server.:

rndc status

Configurations for a Local DNS Resolver

Out of the box, the BIND9 server on CentOS/RHEL provides recursive service for localhost only. Outside queries will be denied. Edit the BIND main configuration file:

vim /etc/named.conf

In the options clause, you can find the following two lines:

listen-on port 53 { 127.0.0.1; };
listen-on-v6 port 53 { ::1; };

This makes named listen on localhost only. If you want to allow clients in the same network to query domain names, then comment out these two lines. (add double slashes at the beginning of each line)

// listen-on port 53 { 127.0.0.1; };
// listen-on-v6 port 53 { ::1; };

Find and adjust these variables:

allow-query { localhost; 192.168.1.0/24; };
recursion yes;
 // hide version number from clients for security reasons.
 version "not currently available";
 // enable the query log
 querylog yes;

Now, check our config (silent output if is OK), and restart named:

named-checkconf
systemctl restart named

Now, you have enabled DNS resolver.

Create own DNS zone, dnssec signed

If you want your own DNS server for your domain, accessible from world and you don’t want you domain provider, to be a DNS server for this domain, let read next.

First, create a new database file for this domain:

vim /var/named/db.example.com

;
; BIND data file for example.com
;
$TTL 604800
@       IN      SOA     example.com.     root.example.com.        (
        2021090608      ; Serial
        3600            ; Refresh
        86400           ; Retry
        2419200         ; Expire
        3600    )       ; Default TTL

        IN      NS      ns1.example.com.

@	IN	A	212.5.215.172
ns1     IN      A       212.5.215.172
www	IN	CNAME	ns1

We have three dns names, that direct to our domain (www, ns1 and the domain itself: example.com). Now, we must edit the main configuration file for named, and add point to this zone/domain/file. And add som variables for dnssec. Now we comment out previously commented two lines and edit some variables:

vim /etc/named.conf

        listen-on port 53 { any; };
        listen-on-v6 port 53 { any; };
        dnssec-enable yes;
        dnssec-validation yes;
        recursion no;


zone    "example.com" IN {
        type master;
        file "db.example.com";
        allow-transfer { none; };
};

Now, check the config and restart named:

named-checkconf
named-checkzone example.com /var/named/db.example.com
systemctl restart named

Now, we can reach our domain from world, if domain registrator set NameServer (ns1.example.com) to point to our public IP and let us for managing our dns zone.

Now, we can continue for dnssec with some theory:

DNS by itself is not secure. DNS was designed in the 1980s when the Internet was much smaller, and security was not a primary consideration in its design. As a result, when a recursive resolver sends a query to an authoritative name server, the resolver has no way to verify the authenticity of the response. The resolver can only check that a response appears to come from the same IP address where the resolver sent the original query. But relying on the source IP address of a response is not a strong authentication mechanism, since the source IP address of a DNS response packet can be easily forged, or spoofed.

DNSSEC strengthens authentication in DNS using digital signatures based on public key cryptography. With DNSSEC, it’s not DNS queries and responses themselves that are cryptographically signed, but rather DNS data itself is signed by the owner of the data…. More about this: https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-05-en

So, lets create a key-pairs:

1 – Create a Zone Signing Key (ZSK)
2 – Create a Key Signing Key (KSK)

cd /var/named
dnssec-keygen -a RSASHA256 -b 2048 -r /dev/urandom example.com
dnssec-keygen -a RSASHA256 -b 4096 -r /dev/urandom -f KSK example.com

eg: Generating key pair......................++ .............................................................................................................................................................................................................++
Kexample.com.+007+18522

The directory will now have 4 keys – private/public pairs of ZSK and KSK. We have to add the public keys which contain the DNSKEY record to the zone file. For example:

cat Kexample.com.+008+18522.key >> /var/named/db.example.com
cat Kexample.com.+008+56121.key >> /var/named/db.example.com

And now, check our zone and if is everything ok, sign the zone:

named-checkzone example.com /var/named/db.example.com

zone example.com/IN: loaded serial 2021090901
OK

dnssec-signzone -A -3 $(head -c 1000 /dev/urandom | sha1sum | cut -b 1-16) -N INCREMENT -o example.com -t db.example.com

Verifying the zone using the following algorithms: RSASHA256.
Zone fully signed:
Algorithm: RSASHA256: KSKs: 1 active, 0 stand-by, 0 revoked
                      ZSKs: 1 active, 0 stand-by, 0 revoked
db.example.com.signed
Signatures generated:                       13
Signatures retained:                         0
Signatures dropped:                          0
Signatures successfully verified:            0
Signatures unsuccessfully verified:          0
Signing time in seconds:                 0.020
Signatures per second:                 649.707
Runtime in seconds:                      0.025

Above command created a signed zone file for example.com zone: db.example.com.signed.

We are now required to edit zone configuration to use new file instead of db.example.com old file, and add som variables:

vim /etc/named.conf

zone    "example.com" IN {
        type master;
        file "db.example.com.signed";
        allow-transfer { none; };
   # DNSSEC keys Location
   key-directory "/var/named/"; 

   # Publish and Activate DNSSEC keys
   auto-dnssec maintain;


   # Use Inline Signing
   inline-signing yes;

};

Now, check named config, and our zone:

named-checkconf
named-checkzone example.com /var/named/db.example.com.signed

zone example.com/IN: loaded serial 2021090902 (DNSSEC signed)
OK

systemctl restart named

And now, we can try our zone, if dnssec is working:

dig DNSKEY example.com. +multiline

; <<>> DiG 9.11.26-RedHat-9.11.26-6.el8 <<>> DNSKEY example.com. +multiline
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19926
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.		IN DNSKEY

;; ANSWER SECTION:
example.com.		604795 IN DNSKEY 256 3 8 (
				AwEAAbfz4OM2hWwBKTlQuvP+q5SxJWkQo0SYbHLjJKiw
				J0qwgsTsXQM8uYa8tHcFdZFSNu7Qrs14CHEGimsufpGO
				d1TgRUeJxDI9Yrl27hg+rd+58HUiwkxQpTOFS4FXx2mw
				/TsTREFtiC16H2ZA/Bgw4N/C4HO2JfBIyOt6YdA4labS
				KBGtBXcR5tbckXh706JwAaVFliDDnFNkPh0L5UUDpkUG
				eKLVis4n7WqQdtv0/WRbURm0HiyMwAdovvr1q1YUmRni
				xAN/5lBAWOID6YETSx+QROEoxkveHJpctE3knRTC3ZnR
				KqF6z25nrWv9oJeghk6niq6Eyt5+dibMLJaJ8Cc=
				) ; ZSK; alg = RSASHA256 ; key id = 56241
example.com.		604795 IN DNSKEY 257 3 8 (
				AwEAAduWjrX7Rv6A3b7VA0t0Q2dQLawRP9F4A86eIScD
				FpDpGFjp8T6U2nFcNrqlQrgXKJ5DUXIxkWZd2mbtjTWn
				TDp6cjnuudYispnV3EyiXhhpGUyEBlOGUvZ8f55xd3EK
				f+p1inceqzYL0VV2qaAMmPBR8gUhtDDj9hlIhJfEC0b9
				JeIVHO01U0IIFsHCDNnykLwpibeK1e/TWwQ3ipoaHshJ
				dJS3ZdfFS0zHBn736v5wneJVusR5AaBcFbeVWdW3bZR9
				EPwh97nVEdvZk2UVfQtDy3KNpwzbvaB19EbBdaeiDEr8
				8+Tho3vRuo1uphhFtTbWWMHnyY5WKDcmBEtAVH1WhsEW
				p4cyMa4ASVz8Zr10b88g7EN8i4lm6X7hNFVVjZ75N+MI
				YNbuxHF1C7FDh3ACKVMk8qveYQx37iS8G5RID8COzpej
				N9L+o/3xlAd6k8hnKqvU+TE1xcOM2Q936Rpudzs6vKDz
				Qvy5h94J9b/5ZPkb4CPOto9jWzT/t/lv96cvtG8qKTDH
				Spg9WtTf42DSdFHflTof5Fqlzjy2Fq0UQXlJNGjOHoii
				TBcQATlSC1yxQALj3+1fvTSe9yZ+PGwxnGUKijTvfcpP
				OetqN8T9261Kv5u/kOKjDM9DxLYYdfkqV6dKAooWaIUS
				wPFkk+zbN7YnNoasHRGSF+/hC+lV
				) ; KSK; alg = RSASHA256 ; key id = 18544

We have configured DNSSEC on our master DNS server.

Total Page Visits: 83364 - Today Page Visits: 68

How to install mail server (postfix, dovecot, webmail and spamassasin) on Centos 8 with selinux enabled

Introduction

In order to set up a full simple mail server, this guide takes advantage of Postfix as an SMTP server, Dovecot to provide POP/IMAP functionality, and RoundCube as a webmail program or client so that users can check and receive email from their favorite web browsers.

Dovecot: Dovecot is an open-source IMAP and POP3 email server for Linux/UNIX-like systems, written with security primarily in mind.
Postfix: Postfix is a free and open-source mail transfer agent (MTA) that routes and delivers electronic mail from one server to another over the internet.
Roundcube: Once the mails have been delivered into a mailbox, most users would need an easy to use interface to read their mails. Roundcube does this pretty well. It is a browser-based multilingual IMAP client with an application-like user interface. It provides full functionality you expect from an email client, including MIME support, address book, folder manipulation, message searching and spell checking.

So, at first, as usual, we use fully updated system:

dnf update -y

Now, configure some prerequisites.

Before proceeding further, also ensure that no other MTAs such as Sendmail are existing as this will cause conflict with Postfix configuration. To remove Sendmail, for example, run the command:

dnf remove sendmail

Now set FQDN (Fully Qualifed Domain Name) and set hostname:

hostnamectl set-hostname mail.example.com
exec bash
vim /etc/hosts
192.0.2.1 mail.example.com

How to install mariadb server, apache web server and php version 7.3 (or 7.4) you can find at another post, like: https://www.gonscak.sk/?p=530

So, now, we can install out MTA Postfix, very simple, with mysql support (our users will be stored in mysql database):

dnf install postfix postfix-mysql -y
systemctl start postfix
systemctl enable postfix

To check postfix status, write this command:

systemctl status postfix

Now, we enable some ports of firewall. If you want use POP3, enable it. I prefer not to use. In order to send emails from your server, port 25 (outbound) must be open. To be able to send emails using a desktop email client (Thunderbird or Outlook), we need to enable the submission service in Postfix. And to encrypt our communications, we need a TLS certificate.

firewall-cmd --permanent --add-service={http,https,smtp-submission,smtps,imap,imaps}
systemctl reload firewalld

sudo firewall-cmd --permanent --add-service={pop3,pop3s}
systemctl reload firewalld

When we configure a desktop email client, enabling encryption is always a good idea. We can easily obtain a free TLS certificate from Let’s Encrypt. Issue the following commands to install Let’s Encrypt client (certbot) on CentOS 8/RHEL 8 from the EPEL repository. If you don’t have a web server running yet, I recommend you install one (Apache).

dnf install epel-release -y
dnf install certbot python3-certbot-apache
dnf install httpd
systemctl start httpd
systemctl enable httpd

We create and simple virtual host for Apache to obtain certificate. Like this:

vim /etc/httpd/conf.d/mail.gonscak.sk.conf

        
        ServerName mail.gonscak.sk

        DocumentRoot /var/www/html/


systemctl reload httpd

Now, if everything is ok (Apache is realoaded), we can obtain our TLS certificate for postfix/dovecot in future settings:

certbot --apache --email you@example.com -d mail.example.com

and the results:

Congratulations! You have successfully enabled https://mail.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/mail.example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/mail.example.com/privkey.pem

Configuring Postfix

To send emails from a desktop email client, we need to enable the submission service of Postfix so that the email client can submit emails to Postfix SMTP server. Edit the master.cf file.

vim /etc/postfix/master.cf

In submission section, uncomment or add the following lines. Please allow at least one whitespace (tab or spacebar) before each -o.  In postfix configurations, a preceding whitespace character means that this line is continuation of the previous line.

submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_wrappermode=no
-o smtpd_sasl_auth_enable=yes
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth

The above configuration enables the submission daemon of Postfix and requires TLS encryption. So later on our desktop email client can connect to the submission daemon in TLS encryption. The submission daemon listens on TCP port 587. STARTTLS is used to encrypt communications between email client and the submission daemon.

Microsoft Outlook only supports submission over port 465. If you are going to use Microsoft outlook mail client, then you also need to enable submission service on port 465 by adding the following lines in the file.

smtps inet n - n - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth

Save and close this file for the moment. Now we configure main configurations of Postfix. open file and edit this lines as mine. If you dont have these lines, please add they.

cp /etc/postfix/main.cf /etc/postfix/main.cf.orig
vim /etc/postfix/main.cf

smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1

#Force TLSv1.3 or TLSv1.2 
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

myhostname = mail.example.com
mydomain = example.com
message_size_limit = 31457280

Save and close file. Now restart postfix to ensure, that the change of settings take effect:

systemctl restart postfix

If you run the following command, you will see Postfix is now listening on port 587 and 465.

netstat -lnpt | grep master

bash: netstat: command not found
#if we havent's these command, check who provide it:

dnf provides netstat

Output:

net-tools-2.0-0.51.20160912git.el8.x86_64 : Basic networking tools
Repo : BaseOS
Matched from:
Filename : /usr/bin/netstat

So install it:

dnf install net-tools
netstat -lnpt | grep master

tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 62343/master
tcp 0 0 127.0.0.1:587 0.0.0.0:* LISTEN 62343/master
tcp 0 0 127.0.0.1:465 0.0.0.0:* LISTEN 62343/master

Installing Dovecot IMAP Server and configuring

So, as usual, install imap server dovecot with mysql support:

dnf install dovecot dovecot-mysql
dovecot --version

2.3.8 (9df20d2db)

Now start it and enable after boot:

systemctl start dovecot
systemctl enable dovecot

Open dovecot config and edit or add this line:

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
vim /etc/dovecot/dovecot.con

protocols = imap

Save and close file. then restart dovecot:

systemctl restart dovecot.service

systemctl status dovecot.service
● dovecot.service - Dovecot IMAP/POP3 email server
Loaded: loaded (/usr/lib/systemd/system/dovecot.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2020-09-18 11:32:49 CEST; 28s ago

For storing messages, we use Maildir format. Every mail is stored in separate file in precise directory structure. So, create a directory for your domain/domains and edit line/lines like next:

mkdir -p /var/vmail/vhosts/example.com
cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
vim /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/vmail/vhosts/%d/%n
mail_privileged_group = mail

Save file and exit. Now assign user dovecot to group mail and vmail, for reading Inbox and writing to folder destinations:

gpasswd -a dovecot mail
usermod -a -G vmail dovecot

Set a database for users, domains and aliases

So, log in mysql as root and create a database, in which we will be storring informations about used domains, users, passwords and mail aliases for users. Then create tables, for this informations. Adjust your informations…

mysql -u root -p

CREATE DATABASE maildb;
GRANT SELECT ON maildb.* TO 'usermail'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT update ON maildb.* TO 'usermail'@'localhost' IDENTIFIED BY 'PASSWORD';
FLUSH PRIVILEGES;

USE maildb;

CREATE TABLE virtual_domains (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE virtual_users (
id INT NOT NULL AUTO_INCREMENT,
domain_id INT NOT NULL,
password VARCHAR(106) NOT NULL,
email VARCHAR(120) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY email (email),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE virtual_aliases (
id INT NOT NULL AUTO_INCREMENT,
domain_id INT NOT NULL,
source varchar(200) NOT NULL,
destination varchar(100) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO maildb.virtual_domains
(id ,name)
VALUES
('1', 'example.com');

INSERT INTO maildb.virtual_users
(id, domain_id, password , email)
VALUES
('1', '7', 'UserPassword', 'user1@example.com');

INSERT INTO maildb.virtual_aliases
(id, domain_id, source, destination)
VALUES
('1', '1', 'alias1@example.com', 'user1@example.com');

Now, we can see, that the password is stored in our databases in plaintext:

MariaDB [maildb]> select * from virtual_users;
+----+-----------+------------+----------------------+
| id | domain_id | password | email |
+----+-----------+------------+----------------------+
| 1 | 7 | UserPassword | user1@example.com |
+----+-----------+------------+----------------------+
1 row in set (0.000 sec)

So we change it:

update virtual_users set password = ENCRYPT('UserPassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))) where email = 'user1@example.com';

select * from virtual_users;

MariaDB [maildb]> select * from virtual_users;
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+
| id | domain_id | password | email |
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+
| 1 | 7 | $6$b308975352080ba6$TFt0bZNCPZdgLtn2S9hHMQSdxFikxGDpLqNVap7r/q9OgHGP/EddEzc9Oc3Ww4nvinbrR2pGNgLUpK.PQ1JVD/ | user1@example.com |
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+
1 row in set (0.000 sec)

And if you want, now you can add your user right with shit encrypt form:

INSERT INTO maildb.virtual_users
(id, domain_id, password , email)
VALUES
('2', '7', ENCRYPT('password2', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'user2@example.com');

And now, we can see:

select * from virtual_users;
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+
| id | domain_id | password | email |
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+
| 1 | 7 | $6$b308975352080ba6$TFt0bZNCPZdgLtn2S9hHMQSdxFikxGDpLqNVap7r/q9OgHGP/EddEzc9Oc3Ww4nvinbrR2pGNgLUpK.PQ1JVD/ | user1@example.com |
| 2 | 7 | $6$93809b2da2242ede$toapOIav4kqmLiFl03xvZiEe9LXvqDs.nT5Ristkmy0zCyk6fc.JjjlekElcJ9MczPv5e9b4eH/lumkgOpZq6/ | user2@example.com |
+----+-----------+------------------------------------------------------------------------------------------------------------+----------------------+

Now we can exit from mariadb server with command “exit;”and continue

Now, we add som configuration files for postfix to be sure, that postfix will understand, where our information about users, domains and aliases are and how to connect to them:

cat > /etc/postfix/mysql-virtual-mailbox-domains.cf << EOF
user = usermail
password = PASSWORD
hosts = 127.0.0.1
dbname = maildb
query = SELECT 1 FROM virtual_domains WHERE name='%s'
EOF

cat > /etc/postfix/mysql-virtual-mailbox-maps.cf << EOF
user = usermail
password = PASSWORD
hosts = 127.0.0.1
dbname = maildb
query = SELECT 1 FROM virtual_users WHERE email='%s'
EOF

cat > /etc/postfix/mysql-virtual-alias-maps.cf << EOF
user = usermail
password = PASSWORD
hosts = 127.0.0.1
dbname = maildb
query = SELECT destination FROM virtual_aliases WHERE source='%s'
EOF

cat > /etc/postfix/mysql-virtual-email2email.cf << EOF
user = usermail
password = PASSWORD
hosts = 127.0.0.1
dbname = maildb
query = SELECT email FROM virtual_users WHERE email='%s'
EOF

cat > mysql-virtual-sender-alias-maps.cf << EOF
user = usermail
password = PASSWORD
hosts = 127.0.0.1
dbname = maildb
query = select destination from virtual_aliases where source='%s'
EOF

Now, we can check, if postifx understand this:

postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
1

postmap -q user1@example.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
1

postmap -q alias1@example.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf
user1@example.com

Now, we can continue with configuring again Postfix and Dovecot. In Postfix add these lines, we created before:

vim /etc/postfix/main.cf

virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual-email2email.cf, mysql:/etc/postfix/mysql-virtual-sender-alias-maps.cf



smtpd_helo_restrictions =
 permit_sasl_authenticated,
 permit_mynetworks,
 reject_non_fqdn_hostname,
 reject_invalid_hostname,
 reject_unknown_hostname,
 permit

smtpd_recipient_restrictions =
 permit_sasl_authenticated,
 permit_mynetworks,
 reject_unauth_destination,
 reject_invalid_hostname,
 reject_unknown_recipient_domain,
 reject_non_fqdn_recipient,
 permit

smtpd_sender_restrictions =
 reject_authenticated_sender_login_mismatch,
 permit_sasl_authenticated,
 check_sender_access hash:/etc/postfix/access,
 reject_unknown_sender_domain,
 reject_non_fqdn_sender

Check, if ist Postfix properly configured and there is no syntax mistake:

postfix check
#if nothing is displayed, is OK and then:
systemctl restart postfix.service

Now, continue with dovecot. Uncoment lines, or modify/add:

cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
vim /etc/dovecot/conf.d/10-auth.conf

disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-sql.conf.ext

Now, edit /etc/dovecot/conf.d/auth-sql.conf.ext and adjust:

passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}

userdb {
driver = static
args = uid=vmail gid=vmail home=/var/vmail/vhosts/%d/%n
}

And edit/or add file:

/etc/dovecot/dovecot-sql.conf.ext

driver = mysql
connect = host=127.0.0.1 dbname=maildb user=usermail password=PASSWORD
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Next, edit an adjust dovecot ssl for using our LE certificate:

cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig
vim /etc/dovecot/conf.d/10-ssl.conf

ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
ssl_dh = </etc/dovecot/dh.pem

ssl_min_protocol = TLSv1
ssl_prefer_server_ciphers = yes

save and close. Now generate dh.pem. It take a long time, in my case, 10minutes:

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

Now, edit SASL authentication between Postfix and Dovecot:

cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
vim /etc/dovecot/conf.d/10-master.conf

service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}

Then, we set Dovecot, to auto-create folders, after user first login. To enable this, edit lines like below:

cp /etc/dovecot/conf.d/15-mailboxes.conf  /etc/dovecot/conf.d/15-mailboxes.conf.orig
vim /etc/dovecot/conf.d/15-mailboxes.conf

mailbox Trash {
auto = create
special_use = \Trash
}

mailbox Drafts {
auto = create
special_use = \Drafts
}
...

Now, restart dovecot and check, if working, for now somehow 🙂

systemctl restart dovecot
systemctl restart postfix
netstat -lnpt | grep dovecot
tcp 0 0 0.0.0.0:993 0.0.0.0:* LISTEN 164849/dovecot
tcp 0 0 0.0.0.0:143 0.0.0.0:* LISTEN 164849/dovecot
tcp6 0 0 :::993 :::* LISTEN 164849/dovecot
tcp6 0 0 :::143 :::* LISTEN 164849/dovecot

#if problem, watch log why:
systemctl status dovecot

By default, Postfix uses its builtin local delivery agent (LDA) to move inbound emails to the message store (inbox, sent, trash, Junk, etc). We can configure it to use Dovecot to deliver emails, via the LMTP protocol, which is a simplified version of SMTP. LMTP allows for a highly scalable and reliable mail system. This step is required if you want to use the sieve plugin to filter inbound messages to different folders.

Edit Dovecot main configuration files, and next postfix configuration:

vim /etc/dovecot/dovecot.conf

protocols = imap lmtp
vim /etc/dovecot/conf.d/10-master.conf

service lmtp { 
unix_listener /var/spool/postfix/private/dovecot-lmtp { 
mode = 0600 
user = postfix 
group = postfix 
  }
}
vim /etc/postfix/main.cf

mailbox_transport = lmtp:unix:private/dovecot-lmtp
virtual_transport = lmtp:unix:private/dovecot-lmtp
smtputf8_enable = no
systemctl restart postfix dovecot

Now, we can set up our desktop clients for serving our mails. I prefer Mozilla Thunderbird. So , in settings, use this variables:

IMAP, port 993, SSL/TLS, normal password
or
IMAP, port 143, STARTTLS, normal password
or
SMTP, port 587, STARTTLS, normal password
or 
SMPT, port 465, SSL/TLS, normal password
username: user1@example.com
password: yours
server for incoming and outgoing: mail.example.com

I prefer using port 465/SSL and 993/SSL. Because this are native SSL ports.

Improoving email delivery with SPF and DKIM records

Until now, we have working mail server with Postfix and Dovecot with dekstop email clients (Thunderbird). We can send mail to the world, and world to us (of couse, we must have correct DNS names/records, like MX, A and PTR. But sometimes, may happend, that our email is mark as SPAM. So we are going to look at how to improve email delivery to recipient’s inbox by setting up SPF and DKIM on CentOS/RHEL server.

So, what is SPF?

I use wikipedia: Sender Policy Framework (SPF) is an email authentication method designed to detect forging sender addresses during the delivery of the email. SPF alone, though, is limited only to detect a forged sender claimed in the envelope of the email which is used when the mail gets bounced.

SPF record specifies which hosts or IP addresses are allowed to send emails on behalf of a domain. You should allow only your own email server or your ISP’s server to send emails for your domain.

Now, we must add SPF record to our domain. It is TXT record, like this:

TXT  @   "v=spf1 mx -all"

Where:

  • TXT indicates this is a TXT record.
  • @ in the name field represent the apex domain name.
  • v=spf1 indicates this is a SPF record and the SPF record version is SPF1.
  • mx means all hosts listed in the MX records are allowed to send emails for your domain and all other hosts are disallowed.
  • -all indicates that emails from your domain should only come from hosts specified in the SPF record. Emails sent from other hosts will be flagged as fail.

If we test for our domain MX record, this SPF record is pointing only to this one. So receiving mailserver can evaluate, that sender is authorized to use this mail server:

dig -t txt example.com
;; ANSWER SECTION:
example.com. 600 IN TXT "v=spf1 a mx -all"

dig -t mx example.com
;; ANSWER SECTION:
example.com. 600 IN MX 10 mail.example.com.

dig mail.example.com
;; ANSWER SECTION:
mail.example.com. 599 IN A 192.0.2.1

Of course, you can use online SPF validator such as https://mxtoolbox.com/SuperTool.aspx to see which hosts are allowed to send emails for your domain and debug your SPF record if any error occurs.

Configuring SPF Policy Agent

We also need to tell our Postfix SMTP server to check the SPF record of incoming emails to detect forged emails. First install required packages:

dnf install pypolicyd-spf

Then add a user for policyd-spf.

adduser policyd-spf --user-group --no-create-home -s /bin/false

And dit the Postfix master process configuration file. Add the following lines at the end of the file, which tells Postfix to start the SPF policy daemon when it’s starting itself. Policyd-spf will run as the policyd-spf user.

vim /etc/postfix/master.cf

policyd-spf unix - n n - 0 spawn 
  user=policyd-spf argv=/usr/libexec/postfix/policyd-spf

Save and close the file. Next, edit Postfix main configuration file. Append the following lines at the end of the file. The first line specifies the Postfix policy agent timeout setting (for querying DNS). The following lines will impose restrictions on incoming emails by checking SPF record.

vim /etc/postfix/main.cf

smtpd_recipient_restrictions =
permit_sasl_authenticated,
permit_mynetworks,
reject_unauth_destination,
check_policy_service unix:private/policyd-spf

Save and close the file. Then restart Postfix.

systemctl restart postfix

Next time, when you receive an email from a domain that has an SPF record, you can see the SPF check results in the raw email header. The following header indicates the sender sent the email from an authorized host:

Received-SPF: Pass (mailfrom) identity=mailfrom;

To test the SPF records with your domain, try:

https://vamsoft.com/support/tools/spf-policy-tester

So, what is DKIM?

According to wiki:

DomainKeys Identified Mail (DKIM) is an email authentication method designed to detect forged sender addresses in emails (email spoofing), a technique often used in phishing and email spam.

DKIM allows the receiver to check that an email claimed to have come from a specific domain was indeed authorized by the owner of that domain. It achieves this by affixing a digital signature, linked to a domain name, to each outgoing email message. The recipient system can verify this by looking up the sender’s public key published in the DNS. A valid signature also guarantees that some parts of the email (possibly including attachments) have not been modified since the signature was affixed. Usually, DKIM signatures are not visible to end-users, and are affixed or verified by the infrastructure rather than the message’s authors and recipients.

Simply: DKIM uses a private key to add a signature to emails sent from your domain. Receiving SMTP servers verify the signature by using the corresponding public key, which is published in your domain’s DNS records.

Now, we must install som package:

dnf install opendkim

At beginig, we must edit main configuration file of opendkim and adjust the line:

/etc/opendkim.conf

Mode sv

By default, OpenDKIM runs in verification mode (v), which will verify the DKIM signature of incoming email messages. We need to sign outgoing emails, so change this line to the following to enable signing mode.

Find the following line and comment it out, because we will use separate keys for each domain name.

KeyFile /etc/opendkim/keys/default.private

Next, find the following 4 lines and uncomment them.


KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts

Create Signing Table, Key Table and Trusted Hosts File

Edit the signing table file.

vim /etc/opendkim/SigningTable

Add the following line at the end of this file. This tells OpenDKIM that if a sender on your server is using a @your-domain.com address, then it should be signed with the private key identified by 20200925._domainkey.example.com.

*@example.com 20200925._domainkey.example.com

20200925 is the DKIM selector. A domain name might have multiple DKIM keys. The DKIM selector allows you to choose a particular DKIM key. You can use whatever name for the DKIM selector, but I found it’s convenient to use the current date (September 25, 2020) as the DKIM selector. Save and close the file. Then edit the key table file.

vim /etc/opendkim/KeyTable

Add the following line, which specifies the location of the DKIM private key.

20200925._domainkey.example.com example.com:20200925:/etc/opendkim/keys/example.com/20200925.private

Save and close the file. Next, edit the trusted hosts file.

vim /etc/opendkim/TrustedHosts

127.0.0.0.1 and ::1 are included in this file by default. Now add the following line. This tells OpenDKIM that if an email is coming from your own domain name, then OpenDKIM should not perform DKIM verification on the email.

*.example.com

Save and close the file.

Generate Private/Public Keypair

Since DKIM is used to sign outgoing messages and verify incoming messages, you need to generate a private key to sign outgoing emails and a public key for receiving SMTP servers to verify the DKIM signature of your email. Public key will be published in DNS.

Create a separate folder for the domain.

mkdir /etc/opendkim/keys/example.com

Generate keys using opendkim-genkey tool.

opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s 20200925 -v

The above command will create 2048 bits keys. -d (domain) specifies the domain. -D (directory) specifies the directory where the keys will be stored. I use 20200925 as the DKIM selector. Once the command is executed, the private key will be written to 20200925.private file and the public key will be written to 20200925.txt file:

opendkim-genkey: generating private key
opendkim-genkey: private key written to 20200925.private
opendkim-genkey: extracting public key
opendkim-genkey: DNS TXT record written to 20200925.txt

And adjust ownership:

chown opendkim:opendkim /etc/opendkim/keys/ -R

Publish Your Public Key in DNS Records

Display the public key. The string after the p parameter is the public key:

cat /etc/opendkim/keys/example.com/20200925.txt

20200925._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dFfxvjFLHGpPX4chiCrcq+88WkARccudstwfchyioXkzZOicB7cxRd992H3JQFYvdGXQ+KEAO8eEelcQBv4ee+SCQjzVH70k/gvVxbVwJ6IicYyMELy1Mh0alQ7oOAFNIhyafueEzy/sSefXva1dw7lYh6t4NjypFUbMpIWH/sUyLEZqkWBTYfbKzbj52kML8LbWeWoQJHB2a"
"7jd9GPRkXpwMpbumKPdLD+wINIyr9L4r31/TIVpVDq7ZP6JrksyBHVFSWZQsODLIHjLL2ln/o/VSUcPXxy8H/44Xpzw2RHwcGXrMdQ44IXenhel+4A3M/FTK3cLS8EuHVJ2YDwUQIDAQAB" ) ; ----- DKIM key 20200925 for example.com

In you DNS manager, create a TXT record, enter 20200925._domainkey in the name field. (You need to replace 20200925 with your own DKIM selector.) Then go back to the terminal window, copy everything in the parentheses and paste it into the value field of the DNS record. You need to delete all double quotes and line breaks in the value field. If you don’t delete them, then key test in the next step will probably fail.

Test DKIM Key

Enter the following command on your server to test your key.

sudo opendkim-testkey -d example.com -s 20200925 -vvv

If everything is OK, you will see the key OK message.

opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key '20200925._domainkey.example.com'
opendkim-testkey: key OK

Now we can start the opendkim service.

systemctl start opendkim

And enable auto-start at boot time.

systemctl enable opendkim

OpenDKIM listens on 127.0.0.1:8891.

Connect Postfix to OpenDKIM

Edit Postfix main configuration file:

vim /etc/postfix/main.cf

Add the following lines at the end of this file, so Postfix will be able to call OpenDKIM via the milter protocol. Note that you should use 127.0.0.1 as the address. Don’t use localhost.

# Milter configuration 
milter_default_action = accept 
milter_protocol = 6 
smtpd_milters = inet:127.0.0.1:8891 
non_smtpd_milters = $smtpd_milters

Save and close the file. Then add postfix user to opendkim group.

 gpasswd -a postfix opendkim

Restart postfix service.

 systemctl restart postfix

Now, you can send email from gmail.com, maybe, to you. In maillog of your server, you will see that dkim works on incoming mails:

opendkim[]: : mail.example.comk [IP of domain] not internal
opendkim[]: : DKIM verification successful

Now, you can send email from you to anybody. In maillog of your server, you will see that dkim works on outcoming mails and that opendkim add signature:

opendkim[]: : DKIM-Signature field added (s=20200925, d=example.com)

Or you can send an empty mail to adress: check-auth@verifier.port25.com

And during few seconds, you will get back report with some things. If SPF is fine and if dkim works:

Summary of Results
SPF check: pass 
"iprev" check: pass 
DKIM check: pass 
SpamAssassin check: ham

Details:
SPF check details:
DNS record(s): example.com. 188 IN TXT "v=spf1 a mx -all"
example.com. 188 192.0.2.1

DKIM check details:
Result: pass (matches From: user1@example.com) 
ID(s) verified: header.d=example.com

How to fight with spam? I will use spamassasin

So what is Spamassasin? According the project web site:

SpamAssassin is a mature, widely-deployed open source project that serves as a mail filter to identify Spam. SpamAssassin uses a variety of mechanisms including header and text analysis, Bayesian filtering, DNS blocklists, and collaborative filtering databases. SpamAssassin runs on a server, and filters spam before it reaches your mailbox.

So we install it first:

dnf install spamassassin

The server binary installed by the spamassassin package is called spamd, which will be listening on TCP port 783 on localhost. Spamc is the client for SpamAssassin spam filtering daemon. By default, the spamassassin systemd service is disabled, you can enable auto start at boot time with:

systemctl enable spamassassin
systemctl start spamassassin

There are several ways you can use to integrate SpamAssassin with Postfix. I use SpamAssassin via the sendmail milter interface, because it allows me to reject an email when it gets a very high score such as 8, so it will never be seen by the recipient.

dnf install spamass-milter-postfix
systemctl start spamass-milter
systemctl enable spamass-milter

Now edit the Postfix main configuration file and add/edit this lines:

vim /etc/postfix/main.cf

Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891,unix:/run/spamass-milter/postfix/sock
non_smtpd_milters = $smtpd_milters

Save and close the file. Now open the /etc/sysconfig/spamass-milter file and find the following line.

#EXTRA_FLAGS="-m -r 15"

Uncomment this line and change 15 to your preferred reject score such as 8.

EXTRA_FLAGS="-m -r 8"

Previously we discussed 7 effective methods for blocking email spam with Postfix on CentOS/RHEL. In this tutorial, we are going to learn how to use SpamAssassin (SA) to detect spam on CentOS/RHEL mail server. SpamAssassin is a free, open-source, flexible and powerful spam-fighting tool.

SpamAssassin is a score-based system. It will check email message against a large set of rules. Each rule adds or removes points in the message’s score. If the score is high enough (by default 5.0), the message is considered spam.

Set Up SpamAssassin on CentOS RHEL to Block Email Spam

Install antivirus clamd and content filter amavis

What is amavis?

According to wiki:

Amavis is an open-source content filter for electronic mail, implementing mail message transfer, decoding, some processing and checking, and interfacing with external content filters to provide protection against spam and viruses and other malware. It can be considered an interface between a mailer (MTA, Mail Transfer Agent) and one or more content filters.

Amavis can be used to:

  • detect viruses, spam, banned content types or syntax errors in mail messages
  • block, tag, redirect (using sub-addressing), or forward mail depending on its content, origin or size
  • quarantine (and release), or archive mail messages to files, to mailboxes, or to a relational database
  • sanitize passed messages using an external sanitizer
  • generate DKIM signatures
  • verify DKIM signatures and provide DKIM-based whitelisting

And what is clamv?

ClamAV® is an open source antivirus engine for detecting trojans, viruses, malware & other malicious threats.

To install Amavisd and Clamav Server run:

dnf install amavis clamd -y
#it takes 250MB with dependencies

Now edit Clamav configuration file and adjust lines:

vim /etc/clamd.d/scan.conf

#Example
LogFile /var/log/clamd.scan
PidFile /run/clamd.scan/clamd.pid
TemporaryDirectory /var/tmp
LocalSocket /run/clamd.scan/clamd.sock

Save and close. Now create log file for Clamav and start it:

touch /var/log/clamd.scan
chown clamscan. /var/log/clamd.scan
restorecon -v /var/log/clamd.scan

systemctl start clamd@scan.service
systemctl enable clamd@

And now, allow Clamav to scan system for Selinux:

setsebool -P antivirus_can_scan_system on

Now configure Amavisd:

vim /etc/amavisd/amavisd.conf

$mydomain = 'example.com'; # a convenient default for other settings
$myhostname = 'host.example.com'; # must be a fully-qualified domain name!
$inet_socket_bind = '127.0.0.1';
$notify_method = 'smtp:[127.0.0.1]:10025';
$forward_method = 'smtp:[127.0.0.1]:10025'; # set to undef with milter!

And enable it and start it:

systemctl start amavisd.service
systemctl enable amavisd.service

Now edit Postfix main configuration file and add at the end of file:

vim /etc/postfix/main.cf

content_filter=smtp-amavis:[127.0.0.1]:10024

Now edit master.cf and add at the end there lines:

vim /etc/postfix/master.cf

smtp-amavis unix - - n - 2 smtp
 -o smtp_data_done_timeout=1200
 -o smtp_send_xforward_command=yes
 -o disable_dns_lookups=yes
127.0.0.1:10025 inet n - n - - smtpd
 -o content_filter=
 -o local_recipient_maps=
 -o relay_recipient_maps=
 -o smtpd_restriction_classes=
 -o smtpd_client_restrictions=
 -o smtpd_helo_restrictions=
 -o smtpd_sender_restrictions=
 -o smtpd_recipient_restrictions=permit_mynetworks,reject
 -o mynetworks=127.0.0.0/8
 -o strict_rfc821_envelopes=yes
 -o smtpd_error_sleep_time=0
 -o smtpd_soft_error_limit=1001
 -o smtpd_hard_error_limit=1000

And restart Postfix:

systemctl restart postfix.service

And if everything is OK, you can see in the detailed headers of mail, that it has been scanned:

X-Virus-Scanned: Amavisd-new at example.com

And in the maiilog:

amavis[]: () Passed CLEAN {RelayedInbound}
or
amavis[]: () Passed UNCHECKED-ENCRYPTED
or
amavis[]: () Blocked INFECTED (Win.Test.EICAR_HDB-1) {DiscardedInbound,Quarantined}

Total Page Visits: 83364 - Today Page Visits: 68

How to configure Capsman

Today, I meet with a challenge. I need to set up good and working Wifi network over the building. I need to use sixteen access points (AP). My previous configurations was simple deployment of this access points with laborious configuration of each AP. And there were many channels, and things, that I must configure.

So I create centralized Access Point management setup for office environment that is scalable to many Access Point. This can be done by setting up Controlled Access Point system Manager (CAPsMAN) on your router and connecting Controlled Access Points (CAPs) to it. I have two bands: 2,4GHz and 5GHz. Everything with one SSID. I use this howto:

https://wiki.mikrotik.com/wiki/Manual:Simple_CAPsMAN_setup

As CAPSMAN I used powerfull hardware: MikroTik CCR1009-7G-1C-1S+. As CAPs I will use HAP AC – dual band wifi AP.

I assume, that you have some skills with Mikrotiks and configuration. So I will use only terminal commands in this post with explanation.

So, lets begin.

Assume, that we have default VLAN 600, with no DHCP and now internet connection. Its dummy vlan, lead to nowhere. Than we have more 3 VLANs. One vlan is management (3), one si for guests (4) and one for employees (5). Routing between this vlans provides linux router beyond our scope here. We used:

  • 192.168.1.0/24 – management vlan ID 3
  • 192.168.2.0/24 – management vlan ID 4
  • 192.168.3.0/24 – management vlan ID 5

Create a Bond with four links, to high bandwidth, with default vlan 600:

/system identity set name=CAPSMAN
/interface bonding add slaves=ether1,ether2,ether3,ether4 mode=802.3ad lacp-rate=30secs link-monitoring=mii transmit-hash-policy=layer-2-and-3
/interface bridge add name=bridge1 vlan-filtering=no pvid=600
/interface bridge port add bridge=bridge1 interface=bond1 pvid=600

At bridge configuration, create setting for vlan:

/interface bridge vlan
add bridge=bridge1 tagged=bridge1,bond1 vlan-ids=3
add bridge=bridge1 untagged=bridge1,bond1 vlan-ids=600
add bridge=bridge1 tagged=bridge1,bond1 vlan-ids=4
add bridge=bridge1 tagged=bridge1,bond1 vlan-ids=5

Now we set each vlan: name and interfaces, and IP addres for management vlan.

/interface vlan add interface=bridge1 vlan-id=3 name=vlan-management
/ip address add address=192.168.1.2 interface=vlan-management
/ip route add dst-address=192.168.2.0/24 gateway=192.168.1.1
/ip route add dst-address=192.168.3.0/24 gateway=192.168.1.1
/interface vlan add interface=bridge1 vlan-id=4 name=vlan-guests
/interface vlan add interface=bridge1 vlan-id=5 name=vlan-users

And now, we set vlan-filtering, to ensure, that this configuration start working:

/interface bridge set bridge1 vlan-filtering=yes

Corresponding to this, we must set appropriate switch device for bonding in 802.3ad. I use Cisco switch:

interface port-channel 1
description PCH:to-CAPSMAN
switchport mode trunk
no macro auto smartport
no eee enable
switchport trunk allowed vlan add 3
switchport trunk allowed vlan add 4
switchport trunk allowed vlan add 5
switchport trunk native vlan 600
flowcontrol off
exit
interface range giga 1-4
no macro auto smartport
no eee enable
channel-group 1 mode auto
description upport:CAPSMAN
no shutdown
exit
write

Now set some more thinks, like timezone, clock, disable Winbox connect via MAC.

/system clock set time-zone-name=Europe/Bratislava
/system ntp client set enabled=yes primary-ntp=192.168.1.1
/system clock print
/tool mac-server set allowed-interface-list=none
/tool mac-server mac-winbox set allowed-interface-list=none
/tool mac-server ping set enabled=no
/passwd
#I use: test123

I create a CA (Certificate Authority), which ensure, that only approved CAPs will connect and with encrypted data. So:

/certificate
add name=CA-CAPSMAN common-name=CA country=SK key-size=4096 organization=AAA state=Slovakia
add name=CAPSMAN common-name=CAPSMAN
/certificate sign CA-CAPSMAN-new ca-crl-host=192.168.1.2 name=CA
#wait minute for complete
/system resource print
/certificate sign CAPSMAN ca=CA name=CAPSMAN
/certificate export-certificate CA export-passphrase=test123
/certificate scep-server add ca-cert=CA path=/scep/CAPSMAN
/caps-man manager set ca-certificate=CA certificate=CAPSMAN
/caps-man manager set require-peer-certificate=yes

Now, create some configs for CAPs. Security and so on…

/caps-man security
add name="home-employees" authentication-types=wpa2-eap eap-methods=passthrough eap-radius-accounting=yes
add name="home-guests" authentication-types=wpa2-psk passphrase="test12345"
/caps-man configuration
add name="Config_AAA-guests_2-4" ssid="AAA-guests" country=slovakia installation=indoor security=home-guests datapath.bridge=bridge1 datapath.vlan-mode=use-tag datapath.vlan-id=4 channel.band=2ghz-g/n
add name="Config_AAA-employees_2-4" ssid="AAA-employees" country=slovakia installation=indoor security=home-employees security.eap-radius-accounting=no datapath.bridge=bridge1 datapath.vlan-mode=use-tag datapath.vlan-id=5 channel.band=2ghz-g/n
add name="Config_AAA-employees_5" ssid="AAA-employees" country=slovakia installation=indoor security=AAA-employees security.eap-radius-accounting=no datapath.bridge=bridge1 datapath.vlan-mode=use-tag datapath.vlan-id=5 channel.band=5ghz-n/ac
add name="Config_AAA-guests_5" ssid="AAA-guests" country=slovakia installation=indoor security=AAA-guests datapath.bridge=bridge1 datapath.vlan-mode=use-tag datapath.vlan-id=4 channel.band=5ghz-n/ac

Now, we can configure our first CAP. This happened only once. Any WIFI setting will be configured via CAPSMAN itself. So I set CAPs up for using, accessing and sending data only via management vlan (vlan id = 3). Every traffic will be forwarded to the CAPSMAN.

/system identity set name=CAP1
/interface bridge
add name=bridge1 vlan-filtering=no pvid=600
/interface bridge port
add bridge=bridge1 interface=ether1 pvid=600
add bridge=bridge1 interface=ether2 pvid=3
/interface bridge vlan
add bridge=bridge1 tagged=bridge1,ether1 untagged=ether2 vlan-ids=3
add bridge=bridge1 untagged=ether1,bridge1 vlan-ids=600
/interface bridge set bridge1 protocol-mode=none
/interface vlan add interface=bridge1 vlan-id=3 name=vlan-management
/ip address add address=192.168.1.3/24 interface=vlan-management
/system clock set time-zone-name=Europe/Bratislava
/system ntp client set enabled=yes primary-ntp=192.168.1.1
/system clock print
/interface bridge set bridge1 vlan-filtering=yes
/tool mac-server set allowed-interface-list=none
/tool mac-server mac-winbox set allowed-interface-list=none
/tool mac-server ping set enabled=no
/ip service print
/ip service disable numbers=0,1,2,5,7
/password
#set password

Now, we download our CA public certificate from our CAPSMAN, import it. Then we create a local certificate, and send it as template to Scep server running on CAPSMAN. Then we must manually approve this template, and it will be signed by our previously created CA certificate on CAPSMAN. And this signed certificate will by user for encrypted communication between CAPs and CAPSMAN. This step must by manually set for each CAP separately.

/tool fetch address=192.168.1.2 src-path=cert_export_CA.crt user=admin password="test123" mode=ftp
/certificate import file-name=cert_export_CA.crt passphrase=test123
/certificate add name=CAP1 common-name=CAP1 country=SK key-size=4096 organization=AAA state=Slovakia
/certificate add-scep template=CAP1 scep-url="http://192.168.1.2/scep/CAPSMAN"

Now, we can see at CAPSMAN, that there is pending certificate for grant:

/certificate scep-server requests print
0 CA pending CAP1 feb/19/2020 12:21:11 5ceb9b622v8badde58316abtec0b7ecff6a
/certificate scep-server requests grant numbers=0
/certificate scep-server requests print

So, after we grant this certificate, we can continue on CAP1:

/interface wireless cap set certificate=CAP1
/interface wireless cap
set bridge=none discovery-interfaces=vlan-management enabled=yes interfaces=wlan1 lock-to-caps-man=yes caps-man-addresses=192.168.1.2

And finally, we set this on CAPSMAN for provision radio setting to CAP1, or next CAP2…We can limit these for MAC address of CAP1. This my setting allow to connect any CAP with certificate, that has been previously granted.

/caps-man provisioning
add action=create-dynamic-enabled master-configuration="Config_AAA-guests_2-4" slave-configurations=Config_AAA-employees_2-4,Config_AAA-employees_5,Config_AAA-guests_5 name-format=prefix-identity
/caps-man manager interface
set [ find default=yes ] forbid=yes
add disabled=no interface=vlan-management
/caps-man manager
set enabled=yes

And now, we can add next CAP, like CAP2:

/system identity set name=CAP2
/interface bridge
add name=bridge1 vlan-filtering=no pvid=600
/interface bridge port
add bridge=bridge1 interface=ether1 pvid=600
add bridge=bridge1 interface=ether2 pvid=3
/system logging add topics=caps
/system logging add topics=stp
/interface bridge vlan
add bridge=bridge1 tagged=bridge1,ether1 untagged=ether2 vlan-ids=3
add bridge=bridge1 untagged=ether1,bridge1 vlan-ids=600
/interface bridge set bridge1 protocol-mode=none
/interface vlan add interface=bridge1 vlan-id=3 name=vlan-management
/ip address add address=192.168.1.4/24 interface=vlan-management
/system clock set time-zone-name=Europe/Bratislava
/system ntp client set enabled=yes primary-ntp=192.168.1.1
/system clock print
/interface bridge set bridge1 vlan-filtering=yes
/tool mac-server set allowed-interface-list=none
/tool mac-server mac-winbox set allowed-interface-list=none
/tool mac-server ping set enabled=no
/password
/ip service print
/ip service disable numbers=0,1,2,5,7
/tool fetch address=192.168.1.2 src-path=cert_export_CA.crt user=admin password="test123" mode=ftp
/certificate import file-name=cert_export_CA.crt passphrase=test123
/certificate add name=CAP2 common-name=CAP2 country=SK key-size=4096 organization=AAA state=Slovakia
/certificate add-scep template=CAP2 scep-url="http://192.168.1.2/scep/CAPSMAN"
#### now approve certificate on CAPSMAN via: certificate scep-server requests print....
#### after grant we can continue:
/interface wireless cap set certificate=CAP2
/interface wireless cap
set bridge=none discovery-interfaces=vlan-management enabled=yes interfaces=wlan1 lock-to-caps-man=yes caps-man-addresses=192.168.1.2
Total Page Visits: 83364 - Today Page Visits: 68

How to install nextcloud v18 on Centos 8 Stream

I create a basic installation of Centos 8 stream from iso: CentOS-Stream-8-x86_64-20191219-boot.iso

During installation I choose minimal applications and standard utilities. Please, enable, network time and set lvm for virtio disk. I set password for root and create a new user, which have root privileges.

After instalation, I create and LVM encrypted partition, to store encrypted data of nextcloud on it. I will not use nextcloud data encryption. Command below creates encrypted disk. We must enter a passphrase twice

 cryptsetup -y -v luksFormat /dev/vdb

Now, we open this partition and look at status:

cryptsetup luksOpen /dev/vdb vdb_crypt
cryptsetup -v status vdb_crypt

/dev/mapper/vdb_crypt is active.
   type:    LUKS2
   cipher:  aes-xts-plain64
   keysize: 512 bits
   key location: keyring
   device:  /dev/vdb
   sector size:  512
   offset:  32768 sectors
   size:    209682432 sectors
   mode:    read/write
 Command successful.

Now, I write 4GB zeros to this device to see, if everything is OK. It is possible, to full-up tho whole device, but it can take a long time. But the true reason is, that this will allocate block data with zeros. This ensures that outside world will see this as random data i.e. it protect against disclosure of usage patterns.

dd if=/dev/zero of=/dev/mapper/vdb_crypt bs=4M count=1000
4194304000 bytes (4.2 GB, 3.9 GiB) copied, 130.273 s, 32.2 MB/s

Now try close and open this encrypted device. And then, I create an lvm above the luks encrypted disk:

cryptsetup luksClose vdb_crypt
cryptsetup luksOpen /dev/vdb vdb_crypt
cryptsetup -v status vdb_crypt
pvcreate /dev/mapper/vdb_crypt
vgcreate nextcloud /dev/mapper/vdb_crypt
lvcreate -n data -L+30G nextcloud
mkdir /mnt/test
mkfs.xfs /dev/mapper/nextcloud-data
mount /dev/mapper/nextcloud-data /mnt/test/
touch /mnt/test/hello 
ll /mnt/test/hello
umount /mnt/test/

Installing nextcloud and prerequisites

And now, we can start with preparing our Centos for nextcloud

At first, update system. Via dnf (DNF is the next upcoming major version of YUM, a package manager for RPM-based Linux distributions. It roughly maintains CLI compatibility with YUM and defines a strict API for extensions and plugins.)

dnf update -y

Next, we install and create empty database for our nextcloud. Then we start it and enable for autostart after boot.
If you wish, you can skip installations of MariaDB and you can use built-in SQLite. Then you can continue with installing apache web server.

dnf -y install mariadb-server
...
systemctl start mariadb
systemctl enable mariadb

Now, we run post installation script to finish setting up mariaDB server:

mysql_secure_installation
Set root password? [Y/n] y
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

Now, we can create a database for nextcloud.

mysql -u root -p
...
CREATE DATABASE nextcloud;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextclouduser'@'localhost' IDENTIFIED BY 'YOURPASSWORD';
FLUSH PRIVILEGES;
exit;

Now, we install Apache web server, and we start it and enable for autostart after boot:

dnf install httpd -y
systemctl start httpd.service
systemctl enable httpd.service

And set up firewall fow port http/80 and ssh/20 only:

systemctl status httpd
firewall-cmd --list-all
firewall-cmd --zone=public --permanent --remove-service=dhcpv6-client
firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --reload

Now point your browser to this server and look, if you see a Apache test page.

Now we can install php. Nextcloud (at this time is version 18.0.1) and support PHP (7.1, 7.2 or 7.3). So I use remi repositories and install php 7.3:

dnf -y install dnf-utils http://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf module list php
dnf module reset php
dnf module enable php:remi-7.3
dnf info php
dnf install php php-gd php-mbstring php-intl php-pecl-apcu php-mysqlnd php-pecl-imagick.x86_64 php-ldap php-pecl-zip.x86_64 php-process.x86_64
php -v
php --ini |grep Loaded
sed -i "s/post_max_size = 8M/post_max_size = 500M/" /etc/php.ini
sed -i "s/upload_max_filesize = 2M/upload_max_filesize = 500M/" /etc/php.ini
sed -i "s/memory_limit = 128M/memory_limit = 512M/" /etc/php.ini
systemctl start php-fpm.service
systemctl enable php-fpm.service

And now, we can install nextcloud:

mkdir -p /var/www/html/nextcloud/data
cd /var/www/html/nextcloud/
mount /dev/mapper/nextcloud-data /var/www/html/nextcloud/data/
wget https://download.nextcloud.com/server/releases/nextcloud-18.0.1.zip
unzip nextcloud-18.0.1.zip
rm nextcloud-18.0.1.zip
mv nextcloud/* .
mv nextcloud/.htaccess .
mv nextcloud/.user.ini .
rmdir nextcloud/
mkdir /var/www/html/nextcloud/data
chown -R apache:apache /var/www/html/nextcloud/
find /var/www/html/nextcloud/ -type d -exec chmod 750 {} \; 
find /var/www/html/nextcloud/ -type f -exec chmod 640 {} \;

Now create configuration file for nextcloud in httpd:

vim /etc/httpd/conf.d/nextcloud.conf
<VirtualHost *:80>
  DocumentRoot /var/www/html/nextcloud/
  ServerName  your.server.com

  <Directory /var/www/html/nextcloud/>
    Require all granted
    AllowOverride All
    Options FollowSymLinks MultiViews

    <IfModule mod_dav.c>
      Dav off
    </IfModule>

  </Directory>
</VirtualHost>
apachectl graceful

Refer to nextcloud admin manual, you can run into permissions problems. Run these commands as root to adjust permissions:

semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/data(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/config(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/apps(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.htaccess'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.user.ini'
restorecon -Rv '/var/www/html/nextcloud/'

If you see error “-bash: semanage: command not found”, install packages:

dnf provides /usr/sbin/semanage
dnf install policycoreutils-python-utils-2.9-3.el8_1.1.noarch

Now, we can check via built-in php scripts, in what state we are:

cd /var/www/html/nextcloud/
sudo -u apache php occ -h
sudo -u apache php occ -V
sudo -u apache php occ status

And finally, we can access our nextcloud and set up administrators password via our web: http://you-ip/

If you see default httpd welcome page, disable all lines in: /etc/httpd/conf.d/welcome.conf
Now you must complete the installation via web interface. Set Administrator’s password and locate to MariaDB with used credentials:

Database user: nextclouduser
Database password: YOURPASSWORD
Database name: nextcloud
host: localhost

In settings of nextcloud, go to section Administration > Overview. You can see some problems. If so, try to fix it. I had three problems. No apcu memory cache configured. So add at nextcloud config.php:

'memcache.local' => '\OC\Memcache\APCu',

Then I must edit som php variables, to set properly opcache: edit and adjust:

vim /etc/php.d/10-opcache.ini

Then I must edit httpd setting, because .htaccess wont working. So change apache config:

vim /etc/httpd/conf/httpd.conf

section: Directory "/var/www/html"
AllowOverride None
change to: 
AllowOverride All

And gracefuly restart apache:

apachectl graceful

Next, I find out, that my nextcloud instance cannot connect to internet and checks for update. I think, that this is on selinux (enforcing mode). So run check and find out, what is happening:

sealert -a /var/log/audit/audit.log

And the result:

SELinux is preventing /usr/sbin/php-fpm from name_connect access on the tcp_socket port 80
Additional Information:
Source Context                system_u:system_r:httpd_t:s0
Source Path                   /usr/sbin/php-fpm
Port                          80
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Enforcing
---------
If you believe that php-fpm should be allowed name_connect access on the port 80 tcp_socket by default.
If you want to allow httpd to can network connect
Then you must tell SELinux about this by enabling the 'httpd_can_network_connect' boolean.

So I allow httpd to can network connect via:

setsebool -P httpd_can_network_connect 1

And that is complete. If you wont secure http (https), try to find out another post on this page.

Have fun

Total Page Visits: 83364 - Today Page Visits: 68

Ftp access to linux server and apache listing of this content

In this post we create an ftp server aand grant access for user to linux server, based on Centos 7, and allow listing of this content on specific url via Apache web server (eventually for testing speed download via web and upload via ftp).

At the beginng, we install secure ftp server:

yum -y install vsftpd

And if we want secure ftp server, then we need to modify some variables in main configuration file. And check other variables, if set by below example:

vim /etc/vsftpd/vsftpd.conf

anonymous_enable=NO #disable anonymous access
local_enable=YES 
write_enable=YES 
chroot_local_user=YES #chroot user to their home folder
allow_writeable_chroot=YES

Now we allow ftp access in firewall:

firewall-cmd --permanent --add-service=ftp --zone=public 
firewall-cmd --reload

chown ftpuser:ftpuser -R /var/www/jjjj.sk/html/upload
semanage fcontext -a -t public_content_rw_t /var/www/jjjj.sk/html/upload
restorecon -Rvv /var/www/jjjj.sk/html/upload/
setsebool -P ftpd_full_access 1
a este zmenit home folder:
ftpuser:x:1002:1002::/var/www/jjjj.sk/html/upload:/bin/bash

Total Page Visits: 83364 - Today Page Visits: 68

Install WordPress on Centos-8-stream with apache (httpd)

I started on clean centos-8 server, created from netinstall cd. It is minimal instalation. So, lets begun. Check the version, to be installed:

dnf info httpd
Name         : httpd
 Version      : 2.4.37
 Release      : 11.module_el8.0.0+172+85fc1f40

So, let install it and allow http port on firewalld. And start apache server itself.

dnf install httpd
firewall-cmd --add-service=http --permanent
firewall-cmd --reload
systemctl start httpd.service
systemctl enable httpd.service

Now, you can point you web browser to IP on this server and you should see the welcome page of apache web server on centos.

Now create a directory, where we place our content and simple web page to test, if its working.

mkdir -p /var/www/vhosts/com.example.www
vim /var/www/vhosts/com.example.www/index.html
<html>
  <head>
    <title>Welcome to www.example.com!</title>
  </head>
  <body>
    <h1>Success!  The www.example.com virtual host is working!</h1>
  </body>
</html>

And now, create for this page own configuration in httpd:

vim /etc/httpd/conf.d/com.example.www.conf
<VirtualHost *:80>
    ServerAdmin admin@example.com
    DocumentRoot "/var/www/vhosts/com.example.www"
    ServerName www.example.com

ErrorLog /var/log/httpd/com.example.www-error_log
CustomLog /var/log/httpd/com.example.www-access_log common
</VirtualHost>

And now, gracefully restart your web server and point your browser to you domain: www.example.com (I edit my /etc/hosts to point this domain at my internal IP).

apachectl graceful

If you test page is working, lets begin with more thinks. We must install additional packages (software) for wordpress. Its mysql server and php. As mysql server, I use mariadb. Then create an initial configuration for mysql and create database for wordpress. I set no password for mysql.

dnf install mariadb-server mariadb
systemctl start mariadb
systemctl enable mariadb
mysql_secure_installation
   Set root password? [Y/n] n
   Remove anonymous users? [Y/n] y
   Disallow root login remotely? [Y/n] y
   Remove test database and access to it? [Y/n] y
   Reload privilege tables now? [Y/n] y

mysql -u root -p
   CREATE DATABASE wordpress;
   CREATE USER wordpressuser@localhost IDENTIFIED BY 'BESTpassword';
   GRANT ALL PRIVILEGES ON wordpress.* TO wordpressuser@localhost IDENTIFIED BY 'BESTpassword';
   FLUSH PRIVILEGES;
   exit;

When we find, which version of php will be standard installed, I decided to use another package sources and install newer php version 7.3

dnf info php
 Available Packages
 Name         : php
 Version      : 7.2.11

dnf install http://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf update
dnf install php73
dnf install php73-php-fpm.x86_64 php73-php-mysqlnd.x86_64
systemctl start php73-php-fpm.service
systemctl enable php73-php-fpm.service
ln -s /usr/bin/php73 /usr/bin/php
php -v
   PHP 7.3.10 (cli) (built: Sep 24 2019 09:20:18) ( NTS )

Now, create simple test php page, to view php by apache if its working.

vim /var/www/vhosts/com.example.www/foo.php
<?php
  phpinfo();
?>

Restart apache web server and point your browser to php:

systemctl restart httpd.service
www.example.com/foo.php

And now you can see informationa page about php on system.

Now we can download wordpress and unpack it.

cd ~ 
wget http://wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz
rsync -avP wordpress/ /var/www/vhosts/com.example.www/
chown -R apache:apache /var/www/vhosts/

Now, we edit configuration and add directory variables about default loding index.php. And remove test files – foo.php, index.html.

rm /var/www/vhosts/com.example.www/foo.php
rm /var/www/vhosts/com.example.www/index.html
vim /etc/httpd/conf.d/com.example.www.conf
<Directory /var/www/vhosts/com.example.www>
DirectoryIndex index.php
</Directory>

And restart apache web server

systemctl restart httpd.service

Now we can continue with setting our wordpress via web browser and our www.example.com page (click refresh in your web browser). Follow the instructions and fill your variables (database name, user, password…).

My installation step 2 tells me, that it cannot write config.php in our content directory. So, I can manually creaty config.php, or find out, what happens. Install selinux troubleshoot packages and run command sealert, which tell us what happend.

dnf install setroubleshoot
sealert -a /var/log/audit/audit.log

I can see this messages:

SELinux is preventing /opt/remi/php73/root/usr/sbin/php-fpm from write access on the directory com.example.www.
If you want to allow php-fpm to have write access on the com.example.www directory
Then you need to change the label on 'com.example.www'
Do
# semanage fcontext -a -t httpd_sys_rw_content_t 'com.example.www'
# restorecon -v 'com.example.www'
Additional Information:
Source Context                system_u:system_r:httpd_t:s0
Target Context                unconfined_u:object_r:httpd_sys_content_t:s0
Target Objects                com.example.www [ dir ]

So I do, what it want. I adapt permissions, that apache/php can write into this diretory.

semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/vhosts/com.example.www(/.*)?'
restorecon -Rv /var/www/vhosts/com.example.www/

Now I can continue with installation. And everything works fine. Have a nice day.

Total Page Visits: 83364 - Today Page Visits: 68

Hardening iptables from “ACCEPT all” to “DROP all”

Now I write some rules, for hardening iptables. From default policy “accept” everything to “drop” everything except something I want to accept. This setup was made on Server Ubuntu 18.04.2 LTS.

This post is related to and made from sites:

https://help.ubuntu.com/community/IptablesHowTo

https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-using-iptables-on-ubuntu-14-0

By default, we can see, that everything is allowed:

iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination

So we start with allowing established sessions to receive traffic:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

-A INPUT: The -A flag appends a rule to the end of a chain. This is the portion of the command that tells iptables that we wish to add a new rule, that we want that rule added to the end of the chain, and that the chain we want to operate on is the INPUT chain.

And now, we can allow specific port or service, which we want to allow:

iptables -A INPUT -p tcp --dport ssh -j ACCEPT
iptables -A INPUT -p tcp --dport http -j ACCEPT
iptables -A INPUT -p tcp --dport https -j ACCEPT

And now, we block everything else commint to us:

iptables -A INPUT -j DROP

Now we can see our input chain in firewall:

iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:http
ACCEPT tcp -- anywhere anywhere tcp dpt:https
DROP all -- anywhere anywhere

Now we must add some rule for loopback. because we block it now. If we add it right now with above command, we add it at the end of chain (after drop all). So all traffic will be blocked. We must add it at the begining of this chain:

iptables -I INPUT 1 -i lo -j ACCEPT

-I INPUT 1: The -I flag tells iptables to insert a rule. This is different than the -A flag which appends a rule to the end. The -I flag takes a chain and the rule position where you want to insert the new rule.

-i lo: This component of the rule matches if the interface that the packet is using is the “lo” interface. The “lo” interface is another name for the loopback device. This means that any packet using that interface to communicate (packets generated on our server, for our server) should be accepted.

And now we can see it:

iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:http
ACCEPT tcp -- anywhere anywhere tcp dpt:https
DROP all -- anywhere anywhere

The first and the last lines looks very similar, so use the variable -v (verbose) os -S (list rules). See

iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
287 46814 ACCEPT all -- any any anywhere anywhere ctstate RELATED,ESTABLISHED
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:ssh
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:http
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:https
211 45230 DROP all -- any any anywhere anywhere
iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -j DROP

Now we have five rules to ACCEPT packets, which we want. The we have the sixth rule for DROP all another packets.

The policy DROP everything can be done by two ways. We have the first way (Default policy of chain is ACCEPT everything. Our five rules catch certain packets and at the end we have the sixth rule to DROP all packet which catch all other remain packets). In case of breaking firewall, or accidentally flush our rules, we still can connect to our server (by default chain policy ACCEPT).

The second way is set default chain policy to DROP, and set our five rules first. So if packets are catch by one of this rules, is ACCEPTed. Then it is DROPPEd by default. There is a possibility, that if we flush our firewall rules, we never reach our server from network because the default chain policy is DROP. So first, we need the rules like above mentioned except the DROP rule. And then, at the end, change the default chain policy by command:

iptables -P INPUT DROP

And now look at this way of firewall:

iptables -S
-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

So we can see, that we DROP all packet, we want and ACCEPT packets we want. It can be done by this two ways. So pick one, which you want. I prefer the second way, because I have another access to server (via console-keyboard connected directly to server). So if something go wrong, I am still be able to connect it.

So if you choose the first way, you must add others rules before the DROP rule, because it will be matched by this rule. Like the loopback rule, you must insert it somewhere before the DROP rules. See the lines:

iptables -L --line-numbers
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- anywhere anywhere
2 ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
3 ACCEPT tcp -- anywhere anywhere tcp dpt:http
4 ACCEPT tcp -- anywhere anywhere tcp dpt:https
5 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
6 DROP all -- anywhere anywhere

And now we can add another rule somewhere in the middle:

iptables -I INPUT 6 -p tcp --dport 5666 -j ACCEPT

And we see it:

iptables -L --line-numbers
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- anywhere anywhere
2 ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
3 ACCEPT tcp -- anywhere anywhere tcp dpt:http
4 ACCEPT tcp -- anywhere anywhere tcp dpt:https
5 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
6 ACCEPT tcp -- anywhere anywhere tcp dpt:nrpe
7 DROP all -- anywhere anywhere

For save this rules and set it persistant after reboot, I use package:

apt-get install iptables-persistent

During installation you will be asked for some questions, like save this rules for permanent use and load next boot. If you haven’t yet, never mind. You can do it later with this:

iptables-save -c > /etc/iptables/rules.v4

Total Page Visits: 83364 - Today Page Visits: 68

How to use Apache as Reverse Proxy on Centos 7 with selinux

Introduction

In addition to being a “basic” web server, and providing static and dynamic content to end-users, Apache httpd (as well as most other web servers) can also act as a reverse proxy server, also-known-as a “gateway” server.

In such scenarios, httpd itself does not generate or host the data, but rather the content is obtained by one or several backend servers, which normally have no direct connection to the external network. As httpd receives a request from a client, the request itself is proxied to one of these backend servers, which then handles the request, generates the content and then sends this content back to httpd, which then generates the actual HTTP response back to the client.

There are numerous reasons for such an implementation, but generally the typical rationales are due to security, high-availability, load-balancing and centralized authentication/authorization.

It is critical in these implementations that the layout, design and architecture of the backend infrastructure (those servers which actually handle the requests) are insulated and protected from the outside; as far as the client is concerned, the reverse proxy server is the sole source of all content.

More is here.

Typical implemetation is below:

In this tutorial, we will set up Apache as a basic reverse proxy using the mod_proxy extension to redirect incoming connections to one or several backend servers running on the same network. This Apache Proxy Server also creates and manages security (ssl engine, https). Conection to the backend servers from this Proxy Server is not encrypted (only http). Next, we will use https (ssl certificates from Let’s Encrypt for ours conections from outside world, but not to backend.

Installation

For a minimum HTTP server instalation install apache itself:

yum install httpd -y

Make sure, that the “/etc/hosts” file contain references for the loopback address and the hostname

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
192.168.3.3 edge-proxy-e edge-proxy-e.gonscak.sk

Turn on the HTTP server, and make sure it starts automatically on reboot. Next, add http port to the firewalld.

systemctl start httpd.service
systemctl enable httpd.service 
firewall-cmd --add-service=http --permanent
firewall-cmd --reload

Now, we can test our apache test web page on http address. This page is there fer testing and informational purposes:

http://edge-proxy-e.gonscak.sk

If you see the test page above, then your server is now correctly installed.

Example – Reverse Proxying a Single Backend Server

Create a first configuration file for our test backend server (I assume, that you already have one).

vim /etc/httpd/conf.d/test-vhost.conf

<VirtualHost *:80>
    ServerName edge-proxy-e.gonscak.sk
    ProxyPreserveHost On
    ProxyPass / http://media.gonscak.sk/
    ProxyPassReverse / http://media.gonscak.sk/
</VirtualHost>

There are three directives here:

  • ProxyPreserveHost makes Apache pass the original Host header to the backend server. This is useful, as it makes the backend server aware of the address used to access the application.
  • ProxyPass is the main proxy configuration directive. In this case, it specifies that everything under the root URL (/) should be mapped to the backend server at the given address. For example, if Apache gets a request for /example, it will connect to http://media.gonscak.sk/example and return the response to the original client.
  • ProxyPassReverse should have the same configuration as ProxyPass. It tells Apache to modify the response headers from backend server. This makes sure that if the backend server returns a location redirect header, the client’s browser will be redirected to the proxy address and not the backend server address, which would not work as intended.

Now, we can test out configuration with the first command below. It runs a configuration file syntax test and report OK or error. And with second command we gracefully restarts Apache httpd daemon. If the daemon is not running, it is not started. Currently open connections are not aborted:

apachectl configtest
apachectl graceful

And now, if everything is OK, we can open out web page now (http://192.168.3.3). We now not see the default page of apache, but the content of backend server media.gonscak.sk. We are not connected directly to the media.gonscak.sk, but only to the “edge” server with Apache.

Enabling SSL support, set certificates from LetsEcnrypt

First, we must install package mod_ssl for Apache to support SSL:

yum install mod_ssl.x86_64

Now, we must open port 443 for Apache in firewall:

firewall-cmd --add-service=https --permanent
firewall-cmd --reload

Now, we create o text file, where we set up some directives for vhost. And then we can simple change som SSL directives for all vhosts in Apache. I use some Mozilla recommendations via https://mozilla.github.io/server-side-tls/ssl-config-generator:

    SSLEngine on
    	SSLCertificateFile /etc/pki/tls/certs/newclient.crt
    	SSLCertificateKeyFile /etc/pki/tls/private/newclient.key
    	SSLCACertificateFile /etc/pki/tls/certs/ca.crt
    Header always set Strict-Transport-Security "max-age=15768000"

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256

SSLHonorCipherOrder     on
SSLCompression          off

Next, I create an empty directory for DocumentRoot. There will be no content:

mkdir -p /var/www/vhosts/sk.gonscak.media

I edit config file for “/etc/httpd/conf.d/test-vhost.conf” and add virtualhost for ssl. And add link for log files.

<VirtualHost *:80>
    ServerAdmin webmaster@gonscak.sk
    ServerName edge-proxy-e.gonscak.sk
    AddDefaultCharset UTF-8
    RedirectPermanent / https://edge-proxy-e.gonscak.sk/
</VirtualHost>

<VirtualHost *:443>
    ServerAdmin webmaster@gonscak.sk
    DocumentRoot "/var/www/vhosts/sk.gonscak.media"
    AddDefaultCharset UTF-8
    ServerName edge-proxy-e.gonscak.sk

    ErrorLog /var/log/httpd/sk.gonscak.media-error_log
    CustomLog /var/log/httpd/sk.gonscak.media-access_log common
    Include	/etc/httpd/conf.d/modern-ssl-template.txt

  <IfModule mod_proxy.c>
   ProxyRequests Off
   ProxyPass /.well-known/ !
   ProxyPass / http://media.gonscak.sk/
   ProxyPassReverse / http://media.gonscak.sk/
   SSLProxyEngine Off
   ProxyPreserveHost Off
  </IfModule>
</VirtualHost>

Now, I hide some information, which world can get from our Apache server. Add this directives to Apache configuration. Detailes can be read here.

vim /etc/httpd/conf/httpd.conf
ServerSignature Off
ServerTokens Prod

Some nice explanations of Proxy and WordPress behind it is here: https://community.pivotal.io/s/article/Purpose-of-the-X-Forwarded-Proto-HTTP-Header

 

Selinux problem

If we have enabled selinux (check like this):

# sestatus

SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 31

And we see some problems in our error log and the page is not loaded:

[Tue Aug 25 12:25:37.344072 2020] [proxy_http:error] [client xxx:xxx] AH01114: HTTP: failed to make connection to backend: 192.168.88.5

We can see this error at log:

sealert -a /var/log/audit/audit.log

Just allow selinux policy to Apache to can network connect via setsebool:

setsebool -P httpd_can_network_connect on

And that is.

Total Page Visits: 83364 - Today Page Visits: 68

Encrypted LVM partition on software raid-1 with mdadm

At another post https://www.gonscak.sk/?p=201 I posted how to create raid1 software raid with mdadm in linux. Now I tried to add a crypted filesystem to this.

First, check, that we have working software raid:

sudo mdadm --misc --detail /dev/md0

/dev/md0:
           Version : 1.2
     Creation Time : Wed Aug 22 09:34:23 2018
        Raid Level : raid1
        Array Size : 1953381440 (1862.89 GiB 2000.26 GB)
     Used Dev Size : 1953381440 (1862.89 GiB 2000.26 GB)
      Raid Devices : 2
     Total Devices : 2
       Persistence : Superblock is persistent
     Intent Bitmap : Internal
       Update Time : Thu Aug 23 14:18:50 2018
             State : active 
    Active Devices : 2
   Working Devices : 2
    Failed Devices : 0
     Spare Devices : 0
Consistency Policy : bitmap
              Name : gw36:0  (local to host gw36)
              UUID : ded4f30e:1cfb20cb:c10b843e:df19a8ff
            Events : 3481
    Number   Major   Minor   RaidDevice State
       0       8       17        0      active sync   /dev/sdb1
       1       8       33        1      active sync   /dev/sdc1

Now, we synced drives and clean. It is time to encrypt.  If we have not loaded modules for encryption, load it:q

modprobe dm-crypt

Now create the volume with passphrase:

sudo cryptsetup --cipher=aes-xts-plain --verify-passphrase --key-size=512 luksFormat /dev/md0

And we can open it:

sudo cryptsetup  luksOpen /dev/md0 cryptdisk

Now we can create as many times a physical volume, volume group and logical volume.

sudo pvcreate /dev/mapper/cryptdisk
sudo vgcreate raid1 /dev/mapper/cryptdisk
sudo lvcreate --size 500G --name lv-home raid1

sudo pvs
  PV                     VG        Fmt  Attr PSize    PFree
  /dev/mapper/cryptdisk  raid1     lvm2 a--    <1,82t 1,33t
sudo vgs
  VG        #PV #LV #SN Attr   VSize    VFree
  raid1       1   1   0 wz--n-   <1,82t 1,33t
sudo lvs
  LV      VG        Attr       LSize
  lv-home raid1     -wi-ao---- 500,00g            

Next, we create a filesystem on this logical volume:

sudo mkfs.ext4 /dev/mapper/raid1-lv--home

And we can mount it:

sudo mount /dev/mapper/raid1-lv--home crypt-home/

Now we have an encrypted partition (disk) for our home directory.

Total Page Visits: 83364 - Today Page Visits: 68

How to install Nextcloud v 13 on Centos 7 with php 7

At first, please update your centos. Every command I used, is used as root user 😉

yum -y update

Installing database server MariaDB

Next, we install and create empty database for our nextcloud. Then we start it and enable for autostart after boot.
If you wish, you can skip installations of MariaDB and you can use built-in SQLite. Then you can continue with installing apache web server.

yum -y install mariadb mariadb-server
...
systemctl start mariadb
systemctl enable mariadb

Now, we run post installation script to finish setting up mariaDB server:

mysql_secure_installation
...
Enter current password for root (enter for none): ENTER
Set root password? [Y/n] Y
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

Now, we can create a database for nextcloud.

mysql -u root -p
...
CREATE DATABASE nextcloud;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextclouduser'@'localhost' IDENTIFIED BY 'YOURPASSWORD';
FLUSH PRIVILEGES;
exit;

Installing Apache Web Server with ssl (letsencrypt)

Now, we install Apache web server, and we start it and enable for autostart after boot:

yum install httpd -y
systemctl start httpd.service
systemctl enable httpd.service

Now, we install ssl for apache and allow https and httpd (for redirect) service for firewall:

yum -y install epel-release
yum -y install httpd mod_ssl
...
firewall-cmd --zone=public --permanent --add-service=https
firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --reload
systemctl restart httpd.service
systemctl status httpd

Now we can access our server via http://our.server.sk or self-signed certificate on https://our.server.sk

If we want signed certificate from letsencrypt, we can do it with next commands. Certboot will ask some questions, so answer them.

yum -y install python-certbot-apache
certbot --apache -d our.server.sk

If we are good, we can see:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem.
...

Then, we must edit our ssl.conf or our  virtual-host to see this certificates. And we can test our page with this.

https://www.ssllabs.com/ssltest/analyze.html?d=our.server.sk&latest

Install PHP 7

The creators of nextcloud recommends at minimal PHP 7.0.
Now we must add some additional repositories for php v. 7:

yum install https://$(rpm -E '%{?centos:centos}%{!?centos:rhel}%{rhel}').iuscommunity.org/ius-release.rpm
yum install yum-plugin-replace
yum repolist # show enabled repositories
yum repolist disabled #show disabled repositories

And we can install php 7.0:

yum install php70u php70u-dom php70u-mbstring php70u-gd php70u-pdo php70u-json php70u-xml php70u-zip php70u-curl php70u-mcrypt php70u-pear setroubleshoot-server bzip2 php70u-mysqlnd.x86_64 php70u-ldap.x86_64 unzip php70u-pecl-apcu.x86_64 mod_php70u.x86_64 php70u-opcache.x86_64 php70u-pecl-memcached.x86_64 php70u-process.x86_64

Check in:

php --ini |grep Loaded
Loaded Configuration File:         /etc/php.ini
php -v
PHP 7.0.27 (cli) (built: Apr 15 2017 07:09:11) ( NTS )
Copyright (c) 1997-2017 The PHP Group

In my case, I will use nextcloud as my backup device, so I increase the default upload limit to 200MB.

sed -i "s/post_max_size = 8M/post_max_size = 200M/" /etc/php.ini
sed -i "s/upload_max_filesize = 2M/upload_max_filesize = 200M/" /etc/php.ini

Restart web server:

systemctl restart httpd

Installing Nextcloud

At first, I install wget tool for download and unzip:

 yum -y install wget unzip

Now we can download nextcloud (at this time the latest version is 11.0.3). And extract it from archive to final destination. Then we change ownership of this directory:

wget https://download.nextcloud.com/server/releases/nextcloud-13.0.0.zip
...
unzip nextcloud_konfs/nextcloud-13.0.0.zip -d /var/www/html/
...
chown -R apache:apache /var/www/html/nextcloud/

If you have enabled SELinux, refer to nextcloud admin manual, you can run into permissions problems. Run these commands as root to adjust permissions:

semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/data(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/config(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/apps(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.htaccess'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.user.ini'
restorecon -Rv '/var/www/html/nextcloud/'

And finally, we can access our nextcloud and set up administrators password via our web: https://you-ip/nextcloud
Now you must complete the installation via web interface. Set Administrator’s password and locate to MariaDB with used credentials:

Database user: nextclouduser
Database password: YOURPASSWORD
Database name: nextcloud
host: localhost

In my case, I must create a DATA folder under out nextcloud, mount nfs backend for this data and set permissions.

mkdir /var/www/html/nextcloud/data
chown apache:apache data/ -R
setsebool -P httpd_use_nfs 1
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/data(/.*)?'
restorecon -Rv '/var/www/html/nextcloud/'

Now create an nextcloud configuration file fort apache:

vim /etc/httpd/conf.d/nextcloud.conf
<Directory /var/www/html/nextcloud/>
 Options +FollowSymlinks
 AllowOverride All

<IfModule mod_dav.c>
 Dav off
 </IfModule>

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/$
RewriteRule ^/$ /index.php/login
 SetEnv HOME /var/www/html/nextcloud
 SetEnv HTTP_HOME /var/www/html/nextcloud
</Directory>

#####################################################
<VirtualHost _default_:80>
ServerName our.server.sk RewriteEngine On RewriteCond %{REQUEST_URI} ^/$ RewriteRule ^/$ /index.php/login LogLevel warn RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI}[END,NE,R=permanent] </VirtualHost> #################################################### <VirtualHost _default_:443> DocumentRoot "/var/www/html/nextcloud" ServerName our.server.sk RewriteEngine On RewriteCond %{REQUEST_URI} ^/$ RewriteRule ^/$ /index.php/login ErrorLog logs/ssl_error_log TransferLog logs/ssl_access_log LogLevel warn SSLEngine on SSLProtocol all -SSLv2 SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA SSLCertificateFile /var/lib/acme/live/our.server.sk/cert SSLCertificateKeyFile /var/lib/acme/live/our.server.sk/privkey SSLCertificateChainFile /var/lib/acme/live/our.server.sk/chain </VirtualHost>

For nicer access, I created a permanent rewrite rule for my  Nextcloud root folder.

Now restart apache and add permisions for apache, to sen emails and work with LDAP:

systemctl restart httpd.service
setsebool -P httpd_can_sendmail on
setsebool -P httpd_can_connect_ldap on

Enable updates via the web interface

To enable updates via the web interface, you may need this to enable writing to the directories:

setsebool httpd_unified on

When the update is completed, disable write access:

setsebool -P httpd_unified off
Total Page Visits: 83364 - Today Page Visits: 68

How to resize Physical volume and shrink disk partition

I Installed proxmox environment on Intel 240GB SSD. Installation take the whole disk for lvm. So I need to reduce the used space and create a new partition for drbd.
This is my disk. You can see, that the disk is full allocated with 171G free.

root@pve1:/# gdisk -l /dev/sda
Disk /dev/sda: 468862128 sectors, 223.6 GiB
Number Start (sector) End (sector) Size Code Name
 1      34          2047        1007.0  KiB EF02
 2      2048        262143      127.0   MiB EF00
 3      262144      111411199   53.0    GiB 8E00 Linux LVM
root@pve1:/# pvs
 PV VG Fmt Attr PSize PFree
 /dev/sda3 pve lvm2 a-- 223.44g 171.44g
root@pve1:/# vgs
 VG #PV #LV #SN Attr VSize VFree
 pve 1 3 0 wz--n- 223.44g 171.44g
root@pve1:/# lvs
 LV VG Attr LSize Pool Origin Data% Move Log Copy% Convert
 data pve -wi-ao--- 40.00g
 root pve -wi-ao--- 10.00g
 swap pve -wi-ao--- 2.00g

So, we list our logical volumes with segments on physical volume /dev/sda3:

root@pve1:/# pvs -v --segments /dev/sda3
 Using physical volume(s) on command line
 PV VG Fmt Attr PSize PFree Start SSize LV Start Type PE Ranges
 /dev/sda3 pve lvm2 a-- 223.44g 171.44g 0 512 swap 0 linear /dev/sda3:0-511
 /dev/sda3 pve lvm2 a-- 223.44g 171.44g 512 2560 root 0 linear /dev/sda3:512-3071
 /dev/sda3 pve lvm2 a-- 223.44g 171.44g 3072 10240 data 0 linear /dev/sda3:3072-13311
 /dev/sda3 pve lvm2 a-- 223.44g 171.44g 13312 43889 0 free

We can see, the size of PV is 223,44G and we have free 171,44G. So, we must shrink this physical volume about 171,44G. So compute the space for size of this physical volume: 223,44 – 171,44 = 52G. So, our PV must have at least 52G. Next, we resize this pv:

root@pve1:/# pvresize --setphysicalvolumesize 52G /dev/sda3
 /dev/sda3: cannot resize to 13311 extents as 13312 are allocated.
 0 physical volume(s) resized / 1 physical volume(s) not resized
root@pve1:/# pvresize --setphysicalvolumesize 52.1G /dev/sda3
 Physical volume "/dev/sda3" changed
 1 physical volume(s) resized / 0 physical volume(s) not resized

As we can see, we cannost shrink exact to this space. So we add 100M and use the 52,1G size. Now we can see:

root@pve1:/# pvs -v --segments /dev/sda3
 Using physical volume(s) on command line
 PV VG Fmt Attr PSize PFree Start SSize LV Start Type PE Ranges
 /dev/sda3 pve lvm2 a-- 52.10g 100.00m 0 512 swap 0 linear /dev/sda3:0-511
 /dev/sda3 pve lvm2 a-- 52.10g 100.00m 512 2560 root 0 linear /dev/sda3:512-3071
 /dev/sda3 pve lvm2 a-- 52.10g 100.00m 3072 10240 data 0 linear /dev/sda3:3072-13311
 /dev/sda3 pve lvm2 a-- 52.10g 100.00m 13312 25 0 free

At this point, we must work on the lowest layer of disk, so we must delete this partition and create a new one. The new partition must start on the same sector as previous and the last sector must be after last segment of physical volume. I use gdisk, because my disk have GPT partition table:

root@pve1:/# gdisk /dev/sda
Command (? for help): p
Disk /dev/sda: 468862128 sectors, 223.6 GiB
Logical sector size: 512 bytes
First usable sector is 34, last usable sector is 468862094
Number Start (sector) End (sector) Size Code Name
 1 34 2047 1007.0 KiB EF02
 2 2048 262143 127.0 MiB EF00
 3 262144 468862094 223.4 GiB 8E00
Command (? for help): d
Partition number (1-3): 3
Command (? for help): n
Partition number (3-128, default 3):
First sector (262144-468862094, default = 262144) or {+-}size{KMGTP}:
Last sector (262144-468862094, default = 468862094) or {+-}size{KMGTP}: +53G
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 8E00
Changed type of partition to 'Linux LVM'
Command (? for help): p
Disk /dev/sda: 468862128 sectors, 223.6 GiB
Total free space is 357450895 sectors (170.4 GiB)
Number Start (sector) End (sector) Size         Code Name
 1     34             2047         1007.0 KiB   EF02
 2     2048           262143       127.0 MiB    EF00
 3     262144         111411199    53.0 GiB     8E00 Linux LVM
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.

Now, we must reboot our computer to use new partition table. And after reboot, use this command to resize physical volume on partition /dev/sda3

root@pve1:/# pvresize /dev/sda3
 Physical volume "/dev/sda3" changed
 1 physical volume(s) resized / 0 physical volume(s) not resized
root@pve1:/# pvs
 PV VG Fmt Attr PSize PFree
 /dev/sda3 pve lvm2 a-- 53.00g 1020.00m

Now, if we can use all of free space for the logical volume “data”, we can resize it to whole free space, like this:

root@pve1:/# lvresize /dev/pve/data -l +100%FREE
 Extending logical volume data to 41.00 GiB
 Logical volume data successfully resized
 root@pve1:/# lvs
 LV VG Attr LSize Pool Origin Data% Move Log Copy% Convert
 data pve -wi-ao--- 41.00g
 root pve -wi-ao--- 10.00g
 swap pve -wi-ao--- 2.00g
root@pve1:/# pvs
 PV VG Fmt Attr PSize PFree
 /dev/sda3 pve lvm2 a-- 53.00g 0

Now, we can create a new partition at the end of disk:

gdisk /dev/sda
Command (? for help): p
Disk /dev/sda: 468862128 sectors, 223.6 GiB
Logical sector size: 512 bytes
Number Start (sector) End (sector) Size Code Name
 1 34 2047 1007.0 KiB EF02
 2 2048 262143 127.0 MiB EF00
 3 262144 111411199 53.0 GiB 8E00 Linux LVM
Command (? for help): n
Partition number (4-128, default 4):
First sector (111411200-468862094, default = 111411200) or {+-}size{KMGTP}:
Last sector (111411200-468862094, default = 468862094) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.
root@pve1:~# gdisk -l /dev/sda
Number Start (sector) End (sector) Size         Code Name
 1     34             2047         1007.0 KiB   EF02
 2     2048           262143       127.0 MiB    EF00
 3     262144         111411199    53.0 GiB     8E00 Linux LVM
 4     111411200      468862094    170.4 GiB    8300 Linux filesystem

And if we list details about physical volume, we can see, that there is no free space:

root@pve1:~# pvs -v --segments /dev/sda3
 Using physical volume(s) on command line
 PV VG Fmt Attr PSize PFree Start SSize LV Start Type PE Ranges
 /dev/sda3 pve lvm2 a-- 53.00g 0 0 512 swap 0 linear /dev/sda3:0-511
 /dev/sda3 pve lvm2 a-- 53.00g 0 512 2560 root 0 linear /dev/sda3:512-3071
 /dev/sda3 pve lvm2 a-- 53.00g 0 3072 10495 data 0 linear /dev/sda3:3072-13566

And what is drbd, you can see in another post on my page. Have a fun.

Total Page Visits: 83364 - Today Page Visits: 68

How to create software raid 10 with mdadm

RAID 10, also called as RAID 1+0 is a stripe of mirrors. It require  four disks at least. It stripes data across mirrored pairs. So, as long as one disk in each mirrored pair is functional, data can be retrieved. If two disks in the same mirrored pair fail, all data will be lost, because there is no parity.

Raid 10 provides redundancy and performance despite of 50% capacity of disks.
Note on why to use different manufacturers disks: Disks will fail, this is not a matter of a “if” but a “when”. Disks of the same manufacturer and the same model have similar properties, and so, higher chances of failing together under the same conditions and time of use. The suggestion so is to use disks from different manufacturers, different models and, in special, that do not belong to the same batch (consider buying from different stores if you are buying disks of the same manufacturer and model). This is not uncommon that a second disk fail happen during a resotre after a disk replacement when disks of the same batch are used. You certainly don’t want this to happen to you.
So we have four disk fo this: /dev/sdc, /dev/sdd, /dev/sde, /dev/sdf. At first, we check, if there is any previous md superblock. So we examine this disks:

 mdadm -E /dev/sd[c-f]
/dev/sdc:
 MBR Magic : aa55
/dev/sdd:
 MBR Magic : aa55
/dev/sde:
 MBR Magic : aa55
/dev/sdf:
 MBR Magic : aa55

Now, we must clear this mbr (512b):

dd if=/dev/zero of=/dev/sdc bs=512 count=1
512 bytes copied, 0.000379187 s, 1.4 MB/s
dd if=/dev/zero of=/dev/sdd bs=512 count=1
512 bytes copied, 0.000251414 s, 2.0 MB/s
dd if=/dev/zero of=/dev/sde bs=512 count=1
512 bytes copied, 0.000487665 s, 1.0 MB/s
dd if=/dev/zero of=/dev/sdf bs=512 count=1
512 bytes copied, 0.000436107 s, 1.2 MB/s

And now, we can see, that there is no superblock:

mdadm -E /dev/sd[c-f]
mdadm: No md superblock detected on /dev/sdc.
mdadm: No md superblock detected on /dev/sdd.
mdadm: No md superblock detected on /dev/sde.
mdadm: No md superblock detected on /dev/sdf.

Now, we must create a partitions with the same size. Disks from different manufacturers (or even different models of the “same” capacity from the same manufacturer) don’t necessarily have the exact same disk size. And in future, we can replace failed disk with another disk (maybe a bigger), but we must create partition with the same size.
So, list the disks size:

fdisk -l /dev/sd[c-f]
Disk /dev/sdc: 465.8 GiB, 500107862016 bytes, 976773168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdd: 465.8 GiB, 500107862016 bytes, 976773168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sde: 465.8 GiB, 500107862016 bytes, 976773168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/sdf: 465.8 GiB, 500107862016 bytes, 976773168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

We can create partitions with fdisk command. Create a new primary partition with the same sectors:

fdisk -l /dev/sd[c-f]
Disk /dev/sdc: 465.8 GiB, 500107862016 bytes, 976773168 sectors
/dev/sdc1 2048 976773167 976771120 465.8G 83 Linux
Disk /dev/sdd: 465.8 GiB, 500107862016 bytes, 976773168 sectors
/dev/sdd1 2048 976773167 976771120 465.8G 83 Linux
Disk /dev/sde: 465.8 GiB, 500107862016 bytes, 976773168 sectors
/dev/sde1 2048 976773167 976771120 465.8G 83 Linux
Disk /dev/sdf: 465.8 GiB, 500107862016 bytes, 976773168 sectors
/dev/sdf1 2048 976773167 976771120 465.8G 83 Linux

For sure, check, if there is no magic block in partitions:

mdadm -E /dev/sd[c-f]1
mdadm: No md superblock detected on /dev/sdc1.
/dev/sdd1:
 MBR Magic : aa55
Partition[0] : 1836016416 sectors at 1936269394 (type 4f)
Partition[1] : 544437093 sectors at 1917848077 (type 73)
Partition[2] : 544175136 sectors at 1818575915 (type 2b)
Partition[3] : 54974 sectors at 2844524554 (type 61)
mdadm: No md superblock detected on /dev/sde1.
mdadm: No md superblock detected on /dev/sdf1.

So, clear this superblock:

dd if=/dev/zero of=/dev/sdd1 bs=512 count=1
512 bytes copied, 0.000261033 s, 2.0 MB/s

And check for the last time:

mdadm -E /dev/sd[c-f]1
mdadm: No md superblock detected on /dev/sdc1.
mdadm: No md superblock detected on /dev/sdd1.
mdadm: No md superblock detected on /dev/sde1.
mdadm: No md superblock detected on /dev/sdf1.

And finally we create a raid array:

mdadm --create /dev/md1 --level=10 --raid-devices=4 /dev/sd[c-f]1
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md1 started.

Check the status of initial synchronization:

cat /proc/mdstat
Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10]
md1 : active raid10 sdf1[3] sde1[2] sdd1[1] sdc1[0]
 976508928 blocks super 1.2 512K chunks 2 near-copies [4/4] [UUUU]
 [>....................] resync = 0.2% (2810176/976508928) finish=138.5min speed=117090K/sec
 bitmap: 8/8 pages [32KB], 65536KB chunk

 

Total Page Visits: 83364 - Today Page Visits: 68

disk cloning with dd

How to create a disk or usb image, and compress it on the fly? And how to restore it?
I have own operating system on USB key. To create a full-backup and then possible restore to another device, I use linux command dd (dd – convert and copy a file).
Now, we must determine, on which patch we have s source disk. I my case, it is

sudo fdisk -l /dev/sdb
Disk /dev/sdb: 29,5 GiB

First, I install additional  software for monitoring and best compressing on more cores

sudo apt-get install pigz pv

Then, I create a full copy of the usb key. Without compression it takes 30GB, with compression, it take only 3GB. With command “pv” we can watch progress. Pigz compress the source image with multiple threads and cores. With parameter -c it writes all processed output to stdout. So  with operand “>” we write this pigz output to a file:

sudo dd if=/dev/sdb | pv | pigz -c > /home/vasil/Documents/corsair-work.dd.gz

If we had som bad blocks on source disk, and we want to clone it anyway, we can use another conv options. Like:

conv=sync,noerror

This means:

  • noerror – This makes use dd continue even after a read error is encountered;
  • sync – This option has sense especially when used together with noerror.

In such a case the noerror option will make dd continue running even if it a sector cannot be read successfully, and the sync option will make so that the amount of data failed to be read its replaced by NULs, so that the length of the data is preserved even if the actual data is lost (since it’s not possible to read it).

Then, I remove the source usb key and insert new one. It also has a path /dev/sdb. Now, I restore it with this command:

pigz -cdk Documents/corsair-work.dd.gz |pv| sudo dd of=/dev/sdb bs=4M

Parameter -c also write output to stdout and program dd writes it to disk. Parameter -k menas, that keep original file after decompress. And parameter -d means decompress.
Now, we can boot system with new usb key. And this image is identical as the source.
I hope, that this help someone. Have a nice day.

Total Page Visits: 83364 - Today Page Visits: 68

Bareos on Centos 7 – powerful backup tool

Today I met with backup problem. I nee to find and set up solution for backup and possible restore of files in windows or linux. I heard about bacula, but after som searching and reading, I choose a new fork of bacula – bareos.

Installing Bareos itself

So I install it on new, clean vm centos 7. At first define a hostname:

hostnamectl set-hostname bareos-ba

Next, add a bareos repository:

cd /etc/yum.repos.d/
wget http://download.bareos.org/bareos/release/latest/CentOS_7/bareos.repo
yum install bareos -y

Next, we can use MariaDB-server for backend od bareos:

yum install mariadb-server -y
systemctl start mariadb.service
systemctl enable mariadb.servic

Now, we create and mount a file-storage, when bareos will save the data:

fdisk /dev/vda
...
mkfs.xfs /dev/vda1
mkdir /var/backups
mount /dev/vda1 /var/backups/
chown bareos:bareos -R /var/backups/
df -h
...
Filesystem                  Size  Used Avail Use% Mounted on
/dev/vda1                    32G   33M   32G   1% /var/backups

Edit /etc/fstab to make this mount permanent.
Now, we can create a new bareos database with pre-defined scripts:

[root@bareos-ba]#/usr/lib/bareos/scripts/create_bareos_database
Creating mysql database
Creating of bareos database succeeded.
[root@bareos-ba]# /usr/lib/bareos/scripts/make_bareos_tables
Making mysql tables
Creation of Bareos MySQL tables succeeded.
[root@bareos-ba]# /usr/lib/bareos/scripts/grant_bareos_privileges
Granting mysql tables
Privileges for user bareos granted ON database bareos.

Now, we can check our default configuration with:

su bareos -s /bin/sh -c "/usr/sbin/bareos-dir -t"
su bareos -s /bin/sh -c "/usr/sbin/bareos-sd -t"
bareos-fd -t

If you are using firewall, for bareos server open this ports:

firewall-cmd --zone=public --add-port=9101/tcp --permanent
firewall-cmd --zone=public --add-port=9102/tcp --permanent
firewall-cmd --zone=public --add-port=9103/tcp --permanent
#http only if you want web-gui for baores
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --reload
firewall-cmd --list-all
#public (active)
# - services: http ssh
# - ports: 5666/tcp 9103/tcp 9101/tcp 161/udp 9102/tcp

This step is only for bareos WebUI. If you don’t need this, skip it.

yum install bareos-webui -y
setsebool -P httpd_can_network_connect on
systemctl start httpd.service
systemctl enable httpd.service

Edit conf file and set FQDN for this host:

vim /etc/bareos-webui/directors.ini
- diraddress = "bareos-ba.example.com"

Copy example admin console config:

cp /etc/bareos/bareos-dir.d/console/admin.conf.example /etc/bareos/bareos-dir.d/console/admin.conf
chown bareos:bareos /etc/bareos/bareos-dir.d/console/admin.conf

Setting up a storage for bareos director

At first, we must add our previously created and mounted disk to our bareos-storage daemon and then add it to bareos-director daemon for using it and working.

cp /etc/bareos/bareos-sd.d/device/FileStorage.conf /etc/bareos/bareos-sd.d/device/backups.conf
chown bareos:bareos /etc/bareos/bareos-sd.d/device/backups.conf
vim /etc/bareos/bareos-sd.d/device/backups.conf
 - change archive device and the name:
Archive Device = /var/backups
Name = Backups
cp /etc/bareos/bareos-dir.d/storage/File.conf /etc/bareos/bareos-dir.d/storage/backups.conf
chown bareos:bareos /etc/bareos/bareos-dir.d/storage/backups.conf
vim /etc/bareos/bareos-dir.d/storage/backups.conf
 - change Name and Device. Name must be the same as above:
Name = Backups
Device = Backups

Now we edit job definitions:

vim /etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf
 - change Storage variable to ours above mentioned:
Storage = Backups

Now again check bareos config files for error:

su bareos -s /bin/sh -c "/usr/sbin/bareos-dir -t"
su bareos -s /bin/sh -c "/usr/sbin/bareos-sd -t"
bareos-fd -t

and restart (start) bareos:

service bareos-dir restart
service bareos-sd restart
service bareos-fd restart
systemctl enable bareos-dir.service
systemctl enable bareos-sd.service
systemctl enable bareos-fd.service

Using bconsole and WEBui

Our webui is on address bellow. Default login nad pass is: admin/admin

http://bareos-ba.globesy.sk/bareos-webui/

Our bareos console is avalaible via command bconsole:

[root@bareos-ba ~]# bconsole
Connecting to Director localhost:9101
1000 OK: bareos-dir Version: 16.2.4 (01 July 2016)
Enter a period to cancel a command.
*

bconsole is marked at the beginning with asterisk *
Some useful commands:

list storages
Automatically selected Catalog: MyCatalog
Using Catalog "MyCatalog"
+-----------+---------+-------------+
| StorageId | Name    | AutoChanger |
+-----------+---------+-------------+
|         1 | File    |           0 |
|         2 | Backups |           0 |
+-----------+---------+-------------+
list pools
show jobdefs
show filesets
status dir
status client=bareos-fd

Now we can start our first job – Selftest. So, run bconsole and continue:

bconsole
*run
- select job resource 3: backup-bareos-fs
- yes => Job queued. JobId=1
*wait jobid=1
*messages
quit

In messages we can see, that bareos backup almost 44MB of files. In our fileset of this Selftest, we can see bareos backup folder /usb/sbin:

cat /etc/bareos/bareos-dir.d/fileset/SelfTest.conf

Now, we can restore this files. By default job of restore, it will be restored to /tmp/bareos-restores:

 cat /etc/bareos/bareos-dir.d/job/RestoreFiles.conf

Run bconsole:

*restore all client=bareos-fd
- select 5 for most recent backup
- done
- yes
Job queued. JobId=2
*wait jobid=2
*messages
..

We can see our restored files in /tmp/bareos-restores/.
 

Total Page Visits: 83364 - Today Page Visits: 68

How to install nextcloud on centos 7 minimal

At first, please update your centos. Every command I use, is used as root 😉

yum -y update

Installing database server MariaDB

Next, we install and create empty database for our nextcloud. Then we start it and enable for autostart after boot.
If you wish, you can skip installations of MariaDB and you can use built-in SQLite. Then you can continue with installing apache web server.

yum -y install mariadb mariadb-server
...
systemctl start mariadb
systemctl enable mariadb

Now, we run post installation script to finish setting up mariaDB server:

mysql_secure_installation
...
Enter current password for root (enter for none): ENTER
Set root password? [Y/n] Y
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

Now, we can create a database for nextcloud.

mysql -u root -p
...
CREATE DATABASE nextcloud;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextclouduser'@'localhost' IDENTIFIED BY 'YOURPASSWORD';
FLUSH PRIVILEGES;
exit;

Installing Apache Web Server with ssl (letsencrypt)

Now, we install Apache web server, and we start it and enable for autostart after boot:

yum install httpd -y
systemctl start httpd.service
systemctl enable httpd.service

Now, we install ssl for apache and allow https service for firewall:

yum -y install epel-release
yum -y install httpd mod_ssl
...
firewall-cmd --zone=public --permanent --add-service=https
firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --reload
systemctl restart httpd.service
systemctl status httpd

Now we can access our server via https://out.server.sk
If we want signed certificate from letsencrypt, we can do it with next commands. Certboot will ask some questions, so answer them.

yum -y install python-certbot-apache
certbot --apache -d example.com

If we are good, we can see:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem.
...

And we can test our page with this:

https://www.ssllabs.com/ssltest/analyze.html?d=example.com&latest

Install PHP 7

As creators of nextcloud recommends at minimal PHP 5.4, I use php 7.
PHP 5.4 has been end-of-life since September 2015 and is no longer supported by the PHP team. RHEL 7 still ships with PHP 5.4, and Red Hat supports it. Nextcloud also supports PHP 5.4, so upgrading is not required. However, it is highly recommended to upgrade to PHP 5.5+ for best security and performance.
Now we must add some additional repositories:

rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

And we can install php 7.2:

yum install mod_php72w.x86_64 php72w-common.x86_64 php72w-gd.x86_64 php72w-intl.x86_64 php72w-mysql.x86_64 php72w-xml.x86_64 php72w-mbstring.x86_64 php72w-cli.x86_64 php72w-process.x86_64

Check in:

php --ini |grep Loaded
Loaded Configuration File:         /etc/php.ini
php -v
PHP 7.2.22 (cli) (built: Sep 11 2019 18:11:52) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

In my case, I will use nextcloud as my backup device, so I increase the default upload limit to 200MB.

sed -i "s/post_max_size = 8M/post_max_size = 200M/" /etc/php.ini
sed -i "s/upload_max_filesize = 2M/upload_max_filesize = 200M/" /etc/php.ini
sed -i "s/memory_limit = 128M/memory_limit = 512M/" /etc/php.ini

Restart web server:

systemctl restart httpd

Installing Nextcloud

At first, I install wget tool for download and unzip:

 yum -y install wget unzip

Now we can download nextcloud (at this time the latest version is 16.0.4). And extract it from archive to final destination. Then we change ownership of this directory:

wget https://download.nextcloud.com/server/releases/nextcloud-16.0.4.zip
...
unzip nextcloud-16.0.4.zip -d /var/www/html/
...
chown -R apache:apache /var/www/html/nextcloud/

Check, if you have enabled SELinux by command sestatus:

sestatus 

SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      31

Refer to nextcloud admin manual, you can run into permissions problems. Run these commands as root to adjust permissions:

semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/data(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/config(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/apps(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.htaccess'
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/.user.ini'
restorecon -Rv '/var/www/html/nextcloud/'

If you see error “-bash: semanage: command not found”, install packages:

yum provides /usr/sbin/semanage
yum install policycoreutils-python-2.5-33.el7.x86_64

And finally, we can access our nextcloud and set up administrators password via our web: https://you-ip/nextcloud
Now you must complete the installation via web interface. Set Administrator’s password and locate to MariaDB with used credentials:

Database user: nextclouduser
Database password: YOURPASSWORD
Database name: nextcloud
host: localhost

In my case, I must create a DATA folder under out nextcloud and set permissions:

mkdir /var/www/html/nextcloud/data
chown apache:apache /var/www/html/nextcloud/data -R
semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html/nextcloud/data(/.*)?'
restorecon -Rv '/var/www/html/nextcloud/'

For easier access, I created a permanent redirect for my IP/domain Nextcloud root folder. This redirect allow you to open page

https://your-ip

and redirect you to:

https://your-ip/nextcloud

You must edit httpd.conf file and add this line into directory /var/www/html:

vim /etc/httpd/conf/httpd.conf
...
RedirectMatch ^/$ https://your-ip/nextcloud
...
systemctl restart httpd.service

If we see an error like “Your data directory and files are probably accessible from the Internet. The .htaccess file is not working. ” try edit and change variable

vim /etc/httpd/conf/httpd.conf
....
<Directory "/var/www/html">
    AllowOverride All
    Require all granted
    Options Indexes FollowSymLinks
</Directory>

Enable updates via the web interface

To enable updates via the web interface, you may need this to enable writing to the directories:

setsebool httpd_unified on

When the update is completed, disable write access:

setsebool -P httpd_unified off

Disallow write access to the whole web directory

For security reasons it’s suggested to disable write access to all folders in /var/www/ (default):

setsebool -P  httpd_unified  off

A way to enable enhanced security with own configuration file

vim  /etc/httpd/conf.d/owncloud.conf
...
Alias /nextcloud "/var/www/html/nextcloud/"
<Directory /var/www/html/nextcloud/>
  Options +FollowSymlinks
  AllowOverride All
 <IfModule mod_dav.c>
  Dav off
 </IfModule>
 SetEnv HOME /var/www/html/nextcloud
 SetEnv HTTP_HOME /var/www/html/nextcloud
</Directory>
Total Page Visits: 83364 - Today Page Visits: 68