Chrony NTP Server on CentOS 7.3

Illustration

I already wrote about setting pool NTP server using good old ntpd daemon. However, CentOS comes with another daemon installed by default - Chrony. Let me guide you through the setup of Chrony NTP server on CentOS 7.3 for the purpose of joining the pool.

While NTP server needs a good (single-core) CPU and fast network, it cares nothing about the RAM. That makes it ideal for even the smallest cloud instances. Both $5 Linode (1024 MB RAM) and $2.50 Vultr (512 MB RAM) will work wonderfully. Just don’t set your pool speed over 100 Mbps to avoid any extra bandwidth charges.

Immediately after CentOS has been installed, it’s best to update system to the latest and greatest:

yum -y upgrade

As Chrony is installed by default on CentOS, there are no new packages to install. However, the firewall still must allow for external requests:

firewall-cmd --permanent --add-service ntp
firewall-cmd --reload

Just installing NTP is no good if we don’t have any servers to synchronize with. To setup those, editing /etc/chrony.conf is needed. Depending which data center you select for the server, you will want to have 4 to 7 servers from the stratum one list physically close to your location while obeying any restrictions noted (especially if server is open access or not). For the virtual machine located in Miami, the following servers would work (do not forget to remove servers already in file):

server ntp-s1.cise.ufl.edu iburst
server time-a.bbnx.net iburst
server time-b.bbnx.net iburst
server time-a.timefreq.bldrdoc.gov iburst
server time-c.timefreq.bldrdoc.gov iburst
…

Of course, a server won’t be the server until we add the following line to /etc/chrony.conf so that external connections are accepted while control is limited to local machine only:

…
allow all
bindcmdaddress 127.0.0.1
bindcmdaddress ::1

Now finally, with all configured, we can restart our server:

systemctl restart chronyd

To verify the server is working, the first step is to see all sources the server synchronizes with. As soon as one of them has asterisk next to its name, all is good.

chronyc sources

A bit more realistic check will be actually requesting the time from remote computer:

ntpdate -q ^^<ip>^^

Once server works for a while, it can be added to the NTP.org pool. Since the server has both IPv4 and IPv6 address (I surely hope you added IPv6 ;)), it will be necessary to add it twice. At the start it will be only monitored but, as its score increases, soon you will start seeing traffic from all over the world.

PS: It would be wise to keep 1 Mpbs as defined speed until you see how much traffic you’re actually getting. Once the server has been handling requests at basic speed for a while you can think about increasing the number.

IPv6 Privacy Extensions in XigmaNAS

Illustration

When you look at IPv6 address XigmaNAS assigns to your interface, you’ll notice the last 64 bits are always the same. FreeBSD (a baseline OS for both XigmaNAS and FreeNAS) generates them based on your interface MAC address (aka EUI-64). While this might be perfectly fine for the purpose of global IPv6 connectivity, it does leak your MAC address to the Internet.

While support for privacy extension is present, unlike some other operating systems, XigmaNAS doesn’t have it turned on by default. However, changing this is very easy. Just go to System, Advanced, rc.conf and add ipv6_privacy=YES, followed by reboot.

You’ll notice your interface now has two global IPv6 addresses. One is still MAC-based (you can recognize it by ff:fe in the middle of last 64 bits) while the other has last 64 bits completely randomized. For all outgoing connections XigmaNAS will now use that randomized IP. Furthermore, XigmaNAS will generate a completely new IPv6 address every 24 hours and gradually deprecate the old one.

While this doesn’t do anything to hide your Internet activity (remember, your /64 prefix is assigned by ISP), it does make correlation of your activity by ad companies just a wee bit harder.

PS: You can also obtain the exactly same results by setting two sysctl.conf variables:

net.inet6.ipv6.use_tempaddr=1
net.inet6.ipv6.prefer_tempaddr=1

PPS: If you want to generate new address more (or less) often, check net.inet6.ip6.temppltime and net.inet6.ip6.tempvltime system variables.

[2018-06-05: This code has been added into XigmaNAS code base. Available as of 11.1.0.4 (revision 5606).]

[2018-07-22: NAS4Free has been renamed to XigmaNAS as of July 2018]

Setting IPv6 on Mikrotik

If your ISP offers IPv6 and you have Mikrotik router, it would be shame not to make use of it. My setup assumes you get /64 prefix from your ISP (Comcast in my case) via DHCPv6. Also assumed is empty IPv6 configuration.

First I like to disable default neighbor discovery interface. Blasting IPv6 router advertisements on all intefaces is not necessarily a good idea:

/ipv6 nd
set [ find default=yes ] disabled=yes

Next step is to setup DHCP client. Withing a few seconds, you should see the prefix being allocated:

/ipv6 dhcp-client
add add-default-route=yes interface=ether1 pool-name=^^general-pool6^^ request=prefix
:delay 5s
print
 Flags: D - dynamic, X - disabled, I - invalid
  #    INTERFACE             STATUS        REQUEST             PREFIX
  0    ether1                bound         prefix              ^^2601:600:9780:ee2c::/64^^, 3d14h41m41s

At this time I love to allocate address ending with ::1 to the router itself:

/ipv6 address
add address=::1 from-pool=^^general-pool6^^ interface=^^bridge1^^ advertise=yes

Now it should be possible to ping its address from external computer (in this example address would be 2601:600:9780:ee2c::1). If this doesn’t work, do check if you have link-local addresses. If none are present, reboot the router and they will be regenerated.

With router reachable, it is time to delegate IPv6 prefix to internal machines too. For this purpose, setup RA (router announcement) over the bridge. While default interval settings are just fine, I like to make them a bit shorter (20-60 seconds):

/ipv6 nd
add interface=^^bridge1^^ ra-interval=20s-60s

And that’s all. Now your computers behind the router will have direct IPv6 route to the Internet. Do not forget to setup both router firewall and firewall of individual devices. There is no NAT to save your butt here.

PS: Here is the basic IPv6 firewall allowing all connections out while allowing only established back in:

/ipv6 firewall filter
add chain=input action=drop connection-state=invalid comment="Drop (invalid)"
add chain=input action=accept connection-state=established,related comment="Accept (established, related)"
add chain=input action=accept in-interface=ether1 protocol=udp src-port=547 limit=10,20:packet comment="Accept DHCP (10/sec)"
add chain=input action=drop in-interface=ether1 protocol=udp src-port=547 comment="Drop DHCP (>10/sec)"
add chain=input action=accept in-interface=ether1 protocol=icmpv6 limit=10,20:packet comment="Accept external ICMP (10/sec)"
add chain=input action=drop in-interface=ether1 protocol=icmpv6 comment="Drop external ICMP (>10/sec)"
add chain=input action=accept in-interface=!ether1 protocol=icmpv6 comment="Accept internal ICMP"
add chain=input action=drop in-interface=ether1 comment="Drop external"
add chain=input action=reject comment="Reject everything else"
add chain=output action=accept comment="Accept all"
add chain=forward action=drop connection-state=invalid comment="Drop (invalid)"
add chain=forward action=accept connection-state=established,related comment="Accept (established, related)"
add chain=forward action=accept in-interface=ether1 protocol=icmpv6 limit=20,50:packet comment="Accept external ICMP (20/sec)"
add chain=forward action=drop in-interface=ether1 protocol=icmpv6 comment="Drop external ICMP (>20/sec)"
add chain=forward action=accept in-interface=!ether1 comment="Accept internal"
add chain=forward action=accept out-interface=ether1 comment="Accept outgoing"
add chain=forward action=drop in-interface=ether1 comment="Drop external"
add chain=forward action=reject comment="Reject everything else"

Reboot E-mail Via Google's SMTP

Setting up NTP server is easy. But actually monitoring that server is a bit more difficult. A bare minimum should be getting an e-mail after reboot. However, even that simple step requires a bit of setup.

First you need to install sendmail, its configuration compiler, and a few SASL authentication methods:

yum install -y sendmail sendmail-cf cyrus-sasl-plain cyrus-sasl-md5

Next step is preparing authentication database (do substitute e-mail and password):

mkdir -p -m 700 /etc/mail/authinfo
echo 'AuthInfo: "U:root" "I:^^relay@gmail.com^^" "P:^^password^^"' > /etc/mail/authinfo/mail
makemap hash /etc/mail/authinfo/mail &gt; /etc/mail/authinfo/mail

The last configuration step is adding the following lines into /etc/mail/sendmail.mc just ABOVE the first MAILER line:

…
define(`SMART_HOST',`[smtp.gmail.com]')dnl
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl
define(`confAUTH_OPTIONS', `A p')dnl
define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confCACERT', `/etc/pki/tls/certs/ca-bundle.trust.crt')dnl
TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
FEATURE(`authinfo',`hash -o /etc/mail/authinfo/gmail.db')dnl
…

With configuration out of the way, we can proceed with “compiling” that new configuration and restarting the daemon:

make -C /etc/mail
systemctl start sendmail

Finally, we are ready to test e-mail via command line:

echo "Subject: Test via sendmail from `hostname`" | sendmail -v ^^youremail@example.com^^

Assuming everything works, the only remaining task is adding cron task (crontab -e):

@reboot  echo -e "Subject: `hostname` status\n\nHost rebooted at `date -R`." | /usr/sbin/sendmail -v ^^youremail@example.com^^

Now every reboot will result in a e-mail message.

Random Slacking

It all started as a joke.

As few of us started using Slack it seemed oddly appropriate that #random channel should have a freshly squeezed random number every day. But there were some complaints about the quality. The first issue arose when 42 was randomly selected a few days in a row and it all went down hill from there culminating in a whole weekend without a random number. Unforgivable!

To replace such flawed human being a simple script was needed. It was clear from the get-go that script would be written in Bash. Not only my favorite but also supported on my personal servers and extremely easy to schedule via crontab.

Albeit single digit number had a previous occurrence, single-person decision was made that two-digit numbers look the best and should be used going forward. Due to the previous issue with number 42, it was also decided such number cannot appear too often. After all, you don’t answer the question of life, the universe, and everything more than once in a blue moon.

Too keep things on a low key, it was necessary to avoid any Slack bot interface. No, the message should always appear to come from a user. After a while chat.postMessage call was discovered enabling just that. This did require a (legacy) token and came at a cost of future extensibility but it also allowed a lot of faking so it all worked out.

In any case, here is the final script:

#!/bin/bash

TOKEN="xoxp-111111111111-222222222222-333333333333-abcdefabcdefabcdefabcdefabcdef"
CHANNEL="random"
USERNAME="myuser"

TAGLINE_FILE="/srv/taglines.txt"

NUMBER=$(( RANDOM % 89 + 10)) #random number 10-99
if (( $NUMBER == 42 )) ; then NUMBER=$(( RANDOM % 89 + 10)) ; fi  #about 0.01% chance to get 42 second time

TAGLINE=`shuf -n 1 $TAGLINE_FILE | cut -d'*' -f1`

TEXT="Random number of the day is ${NUMBER}.\\n${TAGLINE}"

curl -X POST \
     -H "Authorization: Bearer $TOKEN" \
     -H 'Content-type: application/json; charset=utf-8' \
     --data "{\"channel\":\"$CHANNEL\",\"text\":\"$TEXT\",\"as_user\":\"true\",\"username\":\"$USERNAME\"}" \
     https://slack.com/api/chat.postMessage

PS: No, illusion is not full, as there will be hints this is sent via API and not by human being. However, hints are small enough that not many will note.