Setup Package Cache Server on Ubuntu 18.04

If you’re like me and have several Debian/Ubuntu machines on your network there’s going to come a time when you need to upgrade them. Doing so, will use up a lot of bandwidth while every machine will likely be downloading the same packages. This may or may not upset your significant other who’s binge-watching Gilmore Girls on Netflix.

Since you’ve slowed the Internet down to a crawl, this it might  be a good excuse to leave the computer and get outside for some fresh air. HA, who am I kidding, we got stuff to do. Let’s setup a cache!

Here, I’ll be using Ubuntu 18.04 LTS and setting up apt-cacher-ng.

While we could setup Squid to function in the same way, and cache way more than just debian/ubuntu packages, using apt-cacher-ng is a quick win and requires hardly any configuration to get going. Maybew I’ll cover how to setup Squid in a future post.

First, we’ll make sure everything is up to date, then install apt-cacher-ng.

sudo apt-get update
sudo apt-get dist-upgrade -y
sudo apt-get install apt-cacher-ng -y

Let’s go over a few config options. We won’t go over every single one, just the ones that might be relevant. Open /etc/apt-cacher-ng/acng.conf using your favorite text editor and let’s start.

CacheDir. This is where acng will actually do its caching and store packages as they’re downloaded. You may want to change this if you’d like to save packages to a different partition with more space.

CacheDir: /var/cache/apt-cacher-ng

Port. Here, you can change the TCP port apt-cacher will listen on. Note, this should be higher than 1024. Otherwise, you would need to run acng as root.

Port: 3142

BindAddress. If you have a multi-honed server with several IP addresses, you might want acng to only listen on one. Just provide the IP here to do so. By default, it will listen on all interfaces (0.0.0.0).

BindAddress

ReportPage. If you’d like to see some misc statistics about the caching of packages (hit/miss ratio, space usage, etc…) set the page name here. To disable it, just comment out this line.

ReportPage: acng-report.html

Here’s how the page looks from my server:

ExThreshold. The number of days before deleting unreferenced files. You may want to tweak this to cache packages for longer periods of time.

ExTreshhold: 4

MaxDlSpeed. Here, you can limit how much bandwidth acng will use up. Very handy in some environments. Units are KiB/s.

MaxDlSpeed: 250

There are several other options layed out in the config file, feel free to read more on them and tweak as needed. For now, let’s move onto setting up our hosts to point at our new cache server for packages.

Log into one of your Ubuntu/Debian machines and create a new file at /etc/apt/apt.conf.d/21acng. Replace SERVER_IP with the IP address of the cache server you setup. If you specified a BindAddress above, use that one instead.

echo '"Acquire::HTTP::proxy "http://SERVER_IP:3142";' | sudo tee /etc/apt/apt.conf.d/21acng

Now, let’s try it out! From this client machine, run:

sudo apt-get update

Back on the caching server, you can see what’s happening by tailing the log file.

sudo tail -f /var/log/apt-cacher-ng/apt-cacher.log
.......
............
1532529680|O|227|10.137.5.1|ppa.launchpad.net/openjdk-r/ppa/ubuntu/dists/bionic/InRelease
1532529680|O|228|10.137.5.1|packages.cloud.google.com/apt/dists/cloud-sdk-bionic/InRelease
1532529680|O|217|10.137.5.1|dl.google.com/linux/chrome/deb/dists/stable/Release
.........
..............

Don’t forget, you can access the statistics page by opening a web browser to http://SERVER_IP:3142/acng-report.html

The home lab

After reading about others home lab environments, I was inspired to write about mine. It’s nothing too fancy but maybe somebody out there will find it interesting.

I’ve flipped-flopped several times between various operating systems and hypervisors trying to find the solution that best fits my needs. I’ve tried everything from pure Debian (kvm/libvirt, lxc), and Ubuntu (kvm/libvirt, lxd), to FreeBSD 11 (jails, bhyve), and SmartOS (zones, kvm).

At this point, I’ve settled on SmartOS, using project-fifo for my management layer and scheduler. I briefly tried out Joyent’s Triton, but it requires an entire machine be dedicated to running the head node with (at a minimum) 64GB of DRAM. Further, while it does provide an operations portal for configuring the system, the end-user portal requires you to sign-up for a support contract.

Why? SmartOS provides many benefits including running completely from DRAM. This is nice because I don’t have devote entire disk(s) for running the OS. The OS boots from a simple USB key where it loads its configuration and imports the ZFS datasets responsible for running your VM’s (zones/kvm). I won’t dive into the many benefits of ZFS here, but if you’re like me and value your data, you’ll appreciate it.

Because it boots from a USB key and the config is a single file, upgrades are extremely easy, just insert another USB key with an updated image and reboot. Boom.

So let’s get into the physical gear.

Servers:
* 1x Dell R720xd = 64GB RAM, 24x 300G 10K SAS + 1 SSD L2ARC
* 2x Dell R610 = 64GB RAM, 6x 250G SSD

Firewall/Router (not pictured):
* PCEngines APU2 = 2GB RAM, 30GB mSATA, 3 intel (igb) gigabit ports

Switch:
* Quanta LB4M 48port GB switch +2 10GB ports

Networking:
* Intel x520-da1 10Gbe (ixgbe) NIC on seach server
* Broadcom quad port 1gbe nic (bnx)
* Each server has two links to the switch. The first is a 1gbe link for the admin_nic and the second is a 10gbe for the trunk_nic which connects my zones/VMs to their respective VLANS.

I’ll layout the various VM’s I have running and why in a later post, or I’ll simply update this one at a later date.

Nginx and Letsencrypt on SmartOS

acme_tiny is a nice, small utility for creating and renewing your doman SSL/TLS certificates.

It’s less than 200 lines of bash and just works. Here’s how to set it up and have your certificate automatically renewed once a month.

First, let’s get things installed; nginx and the acme-tiny client and setup the necessary directories.

pkgin in nginx py36-acme-tiny
mkdir -p /opt/local/etc/acme /opt/local/www/acme

Add the following stanza towards the top of your nginx config and reload nginx.

vi /opt/local/etc/nginx/nginx.conf
  location ^~ /.well-known/acme-challenge/ {
     alias /opt/local/www/acme/;
     try_files $uri =404;
  }
nginx -s reload

Now, let’s setup the necessary keys so we can request a new certificate for our site.

cd /opt/local/etc/acme
openssl genrsa 4096 > account.key
openssl genrsa 4096 > domain.key
openssl req -new -sha256 -key domain.key -subj "/CN=shaner.life" > domain.csr

Now we can create the initial request for our domain.

acme_tiny   --account-key /opt/local/etc/acme/account.key \
            --csr /opt/local/etc/acme/domain.csr \
            --acme-dir /opt/local/www/acme \
            > /opt/local/etc/acme/signed.crt
curl -s 'https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem' > /opt/local/etc/acme/intermediate.pem
cat /opt/local/etc/acme/signed.crt /opt/local/etc/acme/intermediate.pem > /opt/local/etc/acme/fullchain.pem

If you get an error complaining about the expired terms of service pdf don’t fret, we just need to update the acme script with the name of the updated version.

vi /opt/local/bin/acme_tiny
 # search for 'pdf' and replace the url with the one from the error you recieved during your (failed) certificate request.

If you got an error complaining about not being able to access the acme-challenge token, double check your nginx config and be sure to restart nginx.

One last step before we can setup nginx with our new certs. We need to create a diffie-helman key. This will take a while, go grab some coffee and when you get back it should be done.

openssl dhparam 4096 > /opt/local/etc/nginx/dhparam.pem

Now that we have our dh key, certificate and private key. Let’s setup nginx to use them. Here are the relevant SSL bits that can be added to your server stanza in _opt/local/etc/nginx.conf_ .

server_tokens off;
ssl_certificate /opt/local/etc/acme/fullchain.pem;
ssl_certificate_key /opt/local/etc/acme/domain.key;
ssl_dhparam dhparam.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5';

Let’s automate this thing. Here, we’re creating a script to be called by a cron job for automatic renewal of our certificate. We do this on a somewhat frequent schedule as lets-encrypt only issues certificates that are valid for 3 months.

cat > /opt/local/etc/acme/renew.sh <<EOF
#!/bin/bash
acme_tiny --account-key /opt/local/etc/acme/account.key --csr /opt/local/etc/acme/domain.csr --acme-dir /opt/local/www/acme > /opt/local/etc/acme/signed.crt
curl -s 'https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem' > /opt/local/etc/acme/intermediate.pem
cat /opt/local/etc/acme/signed.crt /opt/local/etc/acme/intermediate.pem > /opt/local/etc/acme/fullchain.pem
cp fullchain.pem /opt/local/etc/nginx/ssl/fullchain.pem 
nginx -s reload
EOF

Okay, with our script in place, let’s create the cron job to run once a month (on the first).

crontab -e 
0 0 1 * * /opt/local/etc/acme/renew.sh >/dev/null 2>&1

All set! Good Job 🙂

Install elasticsearch 5.x on SmartOS

Whenever possible, I like running software in containers instead of kvms. Aside from the obvious performance gains, server density increases significantly since you’re not kidnapping huge chunks of DRAM from the OS and holding it hostage.

I recently had a need to setup an elasticsearch 5.x cluster on a SmartOS machine. It was mostly straight-forward except for a couple gotchas. Here’s how you do it.

1. Create the zone/container. Here, we’re just using a stock ubuntu 16.04 LTS image. Note, the key here is the max_lwps field. Elasticsearch requires at least 2048.

vmadm create << EOF
{
  "brand": "lx",
  "image_uuid": "7b5981c4-1889-11e7-b4c5-3f3bdfc9b88b",
  "autoboot": true,
  "alias": "elastic1",
  "hostname": "es01",
  "dns_domain": "example.com",
  "resolvers": [
    "192.168.1.1",
    "8.8.8.8"
  ],
  "max_physical_memory": 8192,
  "max_swap": 4096,
  "quota": 60,
  "max_lwps": 2048,
  "nics": [
    {
      "nic_tag": "admin",
      "ip": "192.168.1.100",
      "netmask": "255.255.255.0",
      "gateway": "192.168.1.1",
      "primary": true
    }
  ]
}
EOF

2. Login to the container and install elasticsearch

zlogin `vmadm list | awk '/elastic/{print $1}'`
echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" &amp;gt; /etc/apt/sources.list.d/elastic-5.x.list
apt-get update ; apt-get install elasticsearch

3. Disable system_call_filter install during bootstrap phase

echo "bootstrap.system_call_filter: false" >> /etc/elasticsearch/elasticsearch.yml

4. Enable and fire it up!

systemctl enable elasticsearch ; systemctl start elasticsearch

5. Tail the logs to make sure everything started up OK.

tail -f /var/log/elasticsearch/elasticsearch.log

Bhyve pfSense 2.4 no console menu

I ran into an annoying issue today while trying to install pfsense 2.4.2 in a bhyve VM using the ISO installer. Everything went swimmingly until post-install when pfsense finished startup and never provided the expected pfSense console. All it would show is bootup complete.

I went through and confirmed /etc/ttys was configured properly and added console=comconsole to /boot/loader.conf. However, it still wouldn’t work. I’d get all the typical startup info but it still wouldn’t drop to the pfSense console.

To fix this, I ended up having to install pfSense using the memstick serial installer.

In case you aren’t already using vm-bhyve, here’s how it went down from start to finish:

1. Install and initial setup:

pkg install -y vm-bhyve grub2-bhyve
zfs create -o mountpoint=/bhyve zroot/bhyve
sysrc vm_enable="YES"
sysrc vm_dir="zfs:zroot/bhyve"
vm init

I manage network bridges myself, so I’ll just import them into vm-bhyve so it can use them.

vm switch import wan bridge0
vm switch import mgmt bridge1
fetch -o /tmp/pf-memstick-serial.img.gz https://nyifiles.pfsense.org/mirror/downloads/pfSense-CE-memstick-serial-2.4.2-RELEASE-amd64.img.gz
gunzip /tmp/pf-memstick-serial.img.gz

2. Create pfsense VM:

cd /bhyve/.templates
cat > pfsense.conf <<EOF
loader="bhyveload"
cpu=2
memory=512M
network0type="virtio-net"
network0switch="wan"
network1type="virtio-net"
network1switch="mgmt"
disk0type="virtio-blk"
disk0name="disk0.img"
EOF
vm create -t pfsense -s20G pf1

3. Temporarily reconfigure the VM to use the memstick installer.
Basically, we just need to add another disk (the installer image) and make sure it’s first to boot.

cd /bhyve/pf1/
cp /tmp/
cp pf1.conf pf1.orig.conf
cat >pf1.conf<<EOF
loader="bhyveload"
cpu=2
memory=512M
network0_type="virtio-net"
network0_switch="wan"
network1_type="virtio-net"
network1_switch="mgmt"
disk0_type="virtio-blk"
disk0_name="/tmp/pf-memstick-serial.img"
disk1_type="virtio-blk"
disk1_name="disk0.img"
EOF
vm start pf1
vm console pf1

Walk through install process and when finished DON’T reboot. Simply disconnect from the console (~ + ctrl-d) and shut the vm down. If we let it reboot, it’ll just reboot back into the intaller since it’s still configured as the first disk.

vm stop pf1
mv /bhyve/pf1/pf1.orig.conf /bhyve/pf1/pf1.conf
vm start pf1

After it was all said and done, I checked what the memstick installer inserts into /boot/loader.conf to make it work. I’m guessing this is the key, and what I should’ve added to the loader config when I tried using the ISO installer initially.

boot_multicons="YES"
boot_serial="YES"
console="comconsole,vidconsole"
comconsole_speed="115200"

I didn’t try using the ISO installer and adding the above as I just wanted to get up and running, but it’d be interesting to see if it would do the trick.

Hope this helps some of you!

A security event pipeline using Bro, Kafka, and FreeBSD Jails

With the help of the Bro Kafka plug-in, we’ll configure Bro to stream JSON-formatted logs through Kafka and use python to subscribe and print events from the stream.

This tutorial uses FreeBSD 11.1-RELEASE. But can easily be adapted to Linux installations.

How do you monitor events from multiple Bro sensors throughout a network? Do you go to each one and search logs ad-hoc? Maybe fire up a tmux session with multiple synced panes and search them all at once?

With tools like filebeat (previously logstash-forwarder) we’ve been able to ship Bro logs off to remote systems without much effort for a number of years now. However, the way I see it, you’re left with two options.

1. Enable policy/tuning/json-logs.bro to produce JSON logs instead of the standard tab-delimited logs.

– No need to normalize/convert logs to JSON upstream.
– Easier to setup filebeat and tag with extra info.
– Can’t use bro-cut and other CLI tools to parse bro logs on the system.

2. Use the Kafka plug-in and ship logs through Apache Kafka.
– Logs are written to the host system as normal (tab-delimited), but are sent in JSON format to the specified Kafka topic(s).
– You can choose which logs are sent to Kafka (conn, dns, http, notice etc..)
– You can subscribe to a Kafka topic and receive logs from all sensors publishing to it as a single stream.
– You’ll need to manage a Kafka cluster.

Depending on your needs, both are decent options. However, for this tutorial, we’re going to setup and push logs into Kafka.

To start, we’ll want to get bro installed. Refer my previous tutorial on using Bro with Netmap to get up and running. Similar to compiling the netmap plug-in, we’ll need to compile the Kafka plug-in.

pkg install -y librdkafka
cd $BRO_SRC/aux/plugins/kafka
make && make install

The `make install` step isn’t needed if you’re building the plug-in for another system (matching FreeBSD version). You’ll find the compiled plug-in under $BRO_SRC/aux/plugins/kafka/build/BRO_KAFKA.tgz for this purpose.

Next, we’ll want to get Kafka up and runing. Here, we’ll use iocage to create a Kafka (+zookeeper) jail. Since Kafka runs on Java, we’ll want to have fdecfs and procfs available inside the jail as well. Replace ‘kafka’ in the last line here with whatever hostname you chose for your jail as Kafka will attempt to resolve it on start-up and generate an error if it’s unable to.

iocage create -r 11.1-RELEASE -n kafka ip4_addr="igb0|10.0.0.10/24" boot=on mount_fdescfs=1 mount_procfs=1
iocage console kafka
pkg install -y kafka zookeeper
echo "10.0.0.10  kafka" >> /etc/hosts

For development purposes, this will be the only node in the Kafka cluster so you shouldn’t need to change much. Go ahead and edit /usr/local/etc/kafka/server.properties and set the options below. Note, be sure to use whatever IP address you’ve configured for your jail:

delete.topic.enable=true
listeners=PLAINTEXT://10.0.0.10:9092
zookeeper.connect=10.0.0.10:2181

Now, lets enable all the things and fire up zookeeper and Kafka. There’s a small, first-time startup bug for kafka we’ll need to fix before starting Kafka. The init script attempts to chown a file that doesn’t exist (yet).

sysrc zookeeper_enable=YES
sysrc kafka_enable=YES
touch /var/log/kafka/kafkaServer.out
service zookeeper start; service kafka start

Make sure zookeeper and Kafka are running. Both 2181/tcp and 9092/tcp should be listening respectively. If they’re not, you can check the logs under /var/log/zookeeper and /var/log/kafka to see what’s going on.

root@kafka:~ # netstat -an | grep LISTEN
tcp4       0      0 172.16.0.68.9092       *.*                    LISTEN
tcp4       0      0 172.16.0.68.2181       *.*                    LISTEN

If everything looks good, go ahead and exit the jail, we’re done here for now.

Let’s tie the two together by configuring Bro to send logs to Kafka. Go ahead and log into your Bro system and add the following to local.bro file. On my system (installed from source) it’s located under /usr/local/bro/share/bro/site/.

root@kafka:~ # netstat -an | grep LISTEN
@load Bro/Kafka/logs-to-kafka.bro
redef Kafka::topic_name = "THREATLINE";
redef Kafka::tag_json = T;
redef Kafka::logs_to_send = set(Conn::LOG, DHCP::LOG, DNS::LOG, FTP::LOG, HTTP::LOG, SMTP::LOG, SSL::LOG, Notice::LOG, Software::LOG, Weird::LOG);
redef Kafka::kafka_conf = table(["metadata.broker.list"] = "10.0.0.10:9092");

From the above, you can see we’re sending the following logs to Kafka: conn, dhcp, dns, ftp, http, smtp, ssl, notice, software, and weird. There are a lot more logs available depending on which bro scripts you’ve enabled. Here, you’ll find more logs you can send. Be sure to use the IP address of the Kafka jail you created earlier in the above `metadata.broker.list` setting.

Have bro check our config before deploying it.

broctl check

If everything looks good, go ahead and deploy the new config. If you get any errors, double-check your config before running the next `deploy` command.

broctl deploy

Go ahead and generate some traffic for Bro to log. Bro will automatically create the topic if it doesn’t already exist.
Switch back to your Kafka system and run the below command to see if the topic you specified in the Bro config was created.

/usr/local/share/java/kafka/bin/kafka-topics.sh --list --zookeeper 172.16.0.68:2181
THREATLINE

If this doesn’t produce any output, the topic hasn’t been created yet and you’ll probably need to check that bro is running and logging traffic.

Alright, if you’ve made it this far you’re doing good. Let’s use a bit of python to connect to the Kafka topic and print the events to the screen.

First, we’ll install kafka-python While there are many python Kafka libraries out there now-a-days, this one seems to work pretty well. You can install using pip or your package manager.

pkg install py27-kafka-python
fetch https://gist.githubusercontent.com/shanerman/746f79771702bd2ff0a9eb23de0343d3/raw/43437b2b0cb319d755d036eb33e037fe5b1dfeab/print_bro_stream.py
python2.7 print_bro_stream.py

At this point, logs should start printing to your screen. If you’re not seeing anything, you may have to (again) generate some traffic for Bro to log.

Ok, so we have all our Bro sensors pushing various log data into a unified stream of events ready for consumption. Now what? Well the sky is the limit at this point. Here are a few ideas
– Have logstash subscribe to the Kafka topic and push events into Elasticsearch.
– Monitor `dns` events, check for evil domain names.
– Watch the `conn` events and look for compromised IP addresses.
– Watch `software` events and get an idea what software is running on your network.
– Monitor `ssl` events for bad SSL Certificates.
– etc…

Once you have a the Bro event in a python data structure, the sky really is the limit. In future posts, we’ll dive deeper into processing these events using python and do some alerting.