Powershell Remoting for Non-Domain Test Machines

NOTE – This isn’t particularly secure, but it works.  It’s a bit better than configuring WinRM in unencrypted mode though.

Got some non-domain joined Windows machines and you want to get WinRM running in a hurry so you can do some stuff remotely?  Do this.

On the server (the thing you are remoting to);

Invoke-WebRequest -Uri https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1
.\ConfigureRemotingForAnsible.ps1
winrm quickconfig

That script is taken from Ansible, and configures a host with a self-signed SSL cert for use with WinRM.  The final line then configures up the WinRM listeners and firewall rules.

Then, on the client (the thing you’re remoting from);

# enter local admin creds here
$creds = get-credential  

$so = New-PSSessionOption -SkipCACheck -SkipCNCheck
Invoke-Command -Computername YOURSERVERHERE -UseSSL -SessionOption $so -Credential $creds -ScriptBlock { get-childitem env: }

You should see a dump of the local environment variables on the target machine, indicating that the invoke worked.  You can now do whatever Powershell remoting stuff you want to do.

Note, this doesn’t actually check the CA cert provided, so you can be MITM’ed and have your credentials captured.  For better security you should use a properly signed certificate on the server and trust it on the client correctly, but this will work fine for a home setup where you’re in control of all the layers (network, client and server).

Good luck.

Netflow Collector on Splunk – Interesting Bug

The Splunk Add-on for Netflow appears to have a bug.  If you run through the configure.sh script accept all the defaults, it refuses to ingest any Netflow data.

This is because its script deletes all ASCII netflow data that’s older than -1 day old.

You can easily fix this by either rerunning configure.sh again and typing in every value, or edit /opt/splunk/etc/apps/Splunk_TA_flowfix/bin/flowfix.sh and change the following line;

# Cleanup files older than -1
find /opt/splunk/etc/apps/Splunk_TA_flowfix/nfdump-ascii -type f -mtime +-1 -exec rm -f {} \;

Change the +-1 to +1.  This tells the script to clean up all ASCII netflow data older than 1 day (ie, not everything older than some time in the future).

Splunk integration with Docker

I’ve changed over my log aggregation system from ElasticStack to Splunk Free over the past few days.  The primary driver for this is that I use Splunk at work, and since Splunk Free allows 500Mb/day of ingestion, that’s plenty for all my home stuff.  So, using Splunk at home means I gain valuable experience at using Splunk professionally.

What we’ll be talking about here is how you integrate your Docker logging into Splunk.

Configure an HTTP Event Collector

Firstly, you’ll need to enable the Splunk HTTP Event Collector.  In the Splunk UI, click Settings -> Data Inputs -> HTTP Event Collector -> Global Settings.

Click Enabled alongside ‘All Tokens’, and enable SSL.  This will enable the HTTP Event Collector on port 8088 (the default), using the Splunk default certificate.  This isn’t enormously secure (you should use your own cert), but this’ll do for now.

Now, in the HTTP Event Collector window, click New Token and add a token.  Give it whatever details you like, and set the source type to json_no_timestamp.  I’d suggest you send the results to a new index, for now.

Continue the wizard, and you’ll get an access token.  Keep that, you’ll need it.

Configure Docker Default Log Driver

You now need to configure the default logging method used by Docker.  NOTE – Doing this will break the docker logs command, but you can find everything in Splunk anyway.  More on that soon.

You will need to override the startup command for dockerd to include some additional options.  You can do this on CentOS7 by creating a /etc/systemd/system/docker.service.d/docker-settings.conf with the following contents;

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --log-driver=splunk --log-opt splunk-token=PUTYOURTOKENHERE --log-opt splunk-url=https://PUTYOURSPLUNKHOSTHERE:8088 --log-opt tag={{.ImageName}}/{{.Name}}/{{.ID}} --log-opt splunk-insecureskipverify=1

The options should be fairly evident.  The tag= option configures the tag that is attached to the JSON objects outputted by Docker, so it contains the image name, container name, and unique ID for the container.  By default it’ll be just the unique ID, which frankly isn’t very useful post-mortem.  The last option allows the use of the Splunk SSL certificate.  Get rid of this option when you use a proper certificate.

Getting the driver in place

Now you’ve done that, you should be able to restart the Docker host, then reprovision all the containers to change their logging options.  In my case, this is a simple docker-compose down followed by docker-compose up, after a reboot.

The docker logs command will be broken now, but you can instead use Splunk to replicate the functionality, like this;

index=docker host=dockerhost | spath tag | search tag="*mycontainer*" | table _time,line

That will drop out the logs from the last 60 minutes for the container mycontainer running on the host dockerhost.

You can then start doing wizardry like this;

index=docker | spath tag | search tag="nginx*" 
| rex field=line "^(?<remote_addr>\S+) - (?<remote_user>\S+) \[(?<time_local>.+)\] \"(?<request>.+)\" (?<status>\d+) (?<body_bytes>\d+) \"(?<http_referer>.+)\" \"(?<http_user_agent>).+\" \"(?<http_x_forwarded_for>).+\"$"
| rex field=request "^(?<request_method>\S+) (?<request_url>\S+) (?<request_protocol>\S+)$"
| table _time,tag,remote_addr,request_url

To dynamically parse NGINX container logs outputted by Docker, split up the fields, and then list them by time, remote IP, and the URL requested.

I’m sure there’s better ways of doing this (such as parsing the logs at index time instead of at search time), but this way works pretty well and should function as a decent starting point.

NGINX Rate Limiting for Unsecured Apps

Some applications don’t properly support IP blackholing in the case of failed login attempts.  There’s a few ways to handle that, but one nice way is to make use of nginx in the front of the application to apply rate limiting.

I’m considering using nginx as a reverse proxy for your application here as out of scope for this article.  It’s a good idea to get used to using it to front your applications and control access to them.

Rate Limiting in NGINX

We’ll be making use of the ngx_http_limit_req module.  Simply put, you create a zone using limit_req_zone, then define allowed locations that will use the zone using limit_req.

The mental abstraction you can use for the zone is a bucket.  The zone definition describes a data table which will hold IP addresses (in this case), and how many requests they’ve made.  The requests (which are water in the bucket in this analogy) flow out a ‘hole’ in the bucket at a fixed rate.  Therefore, if requests come in faster than the rate, they will ‘fill’ the bucket.

The ‘size’ of the bucket is determined by the parameters you’ve set on limit_req for the allowed burst size.  So a large burst size enables a lot of requests to be made in a time period that exceeds the recharge rate, but it’ll fill the bucket up eventually.  They then slowly recharge at the described rate.

IMPORTANT – If you do not use the nodelay option in limit_req, what happens is that nginx delays incoming requests to force them to match the rate – irrespective of bursts.  In this article, we’ll use nodelay, because we want to flat out return errors when the burst size is exceeded.

Configuring Rate Limiting

In the http context of your nginx.conf, insert a zone definition like this;

limit_req_zone $binary_remote_addr zone=myzone:10m rate=1r/m;

This defines a new zone named myzone which will be populated with the binary forms of remote addresses of clients of size 10Mb.  This will hold a large number of addresses, so it should be fine.  It will recharge limits at a rate of one per minute (which is very slow, but this is intentional, as you’ll see).

Then, let’s assume your app has a login page that you know is at /app/login, and the rest of the app is under /.  You could write some locations like this;

location = /app/login {
    limit_req zone=myzone burst=10 nodelay;

    # whatever you do to get nginx to forward to your app here
}

location / {
    # whatever you do to get nginx to forward to your app here
}

That way, calls to /app/login will be rate limited, but the rest of your app will not.

In the above example, calls to /app/login from a single IP will be rate limited such that they can make a burst of 10 calls without limits, but then are limited to an average rate of one per minute.

For something that’s a login page, this should be sufficient to allow legitimate logins (and likely with a mistyped password or two), but it’ll put a big tarpit on dictionary attacks and the like.

Netflow with ELK Stack and OpenWRT

Now we’re getting into some pretty serious magic.  This post will outline how to put together OpenWRT and ELK Stack to collect network utilization statistics with Netflow.  From there, we can use Kibana to generate visualizations of traffic data and flows and whatever else you want to leverage with the power of Elasticsearch.

I’m using a virtualized router instance running OpenWRT 15.05.1 (Chaos Calmer) on KVM with the Generic x86 build.  Using a hardware router is still doable, but you’ll need to be careful about CPU utilization of the Netflow exporter.  Setting this up will require a number of components, which we’ll go through now.

You will need an OpenWRT box of some description, and an ELK Stack already configured and running.

OpenWRT Setup

You’ll need to install softflowd, which is as easy as;

opkg update
opkg install softflowd

Then edit /etc/config/softflowd and set the destination for flows to go to something like;

option host_port 'netflow.localdomain:9995'

Start up the Softflow exporter with /etc/init.d/softflowd start and it should be working.

Note, default config will be using Netflow version 5.  Let that stand for now.  Also, leave the default interface on br-lan – that way it’ll catch flows for all traffic reaching the router.

Logstash Configuration

If you’re using the ELK Stack Docker project like me, you’ll need to set up the Docker container to also listen on port 9995 UDP.  At any rate, you need to edit your logstash.conf so that you have the following input receiver;

# Netflow receiver
input {
  udp {
    port => 9995
    type => netflow
    codec => netflow
  }
}

This is an extremely simple receiver which takes in Netflow data on port 9995, sets the type to netflow and then processes it with the built-in Netflow codec.

In your output transmitter, you’ll then want something like this example;

output {
        if ( [type] == "netflow" ) {
                elasticsearch {
                        hosts => "elasticsearch:9200"
                        index => "logstash-netflow-%{host}-%{+YYYY.MM.dd}"
                }
        } else {
                elasticsearch {
                        hosts => "elasticsearch:9200"
                        index => "logstash-%{type}-%{+YYYY.MM.dd}"
                }
        }
}

What this does is pretty straightforward.  Everything gets sent to the Elasticsearch engine at elasticsearch:9200.  But, messages with the type of netflow get pushed into an index that has the IP address that the flow was collected from in it (this will probably be your router).

Restart Logstash and you should start getting flows in within a few minutes.

Kibana Setup

From there, just go into Kibana and add a new index pattern for logstash-netflow-*.  You can then visualize / search all your Netflow data to your heart’s content.

Nice!

Customizing OwnCloud using Docker

I’m messing around with OwnCloud at the moment, a solution to provide cloud-like access to files and folders through a webapp using your own local storage.  As is my want, I’m doing it in Docker.

There’s a minor catch though – the official OwnCloud Docker image does not include smbclient, which is required to provide access to Samba shares.

Here’s how to take care of that.

FROM owncloud:latest
RUN set -x; \
 apt-get update \
 && apt-get install -y smbclient \
 && rm -rf /var/lib/apt/lists/* \
 && rm -rf /var/cache/apt/archives/*

The above Dockerfile will use the current owncloud:latest image from Docker Hub, and then install smbclient into it.  You want to do the update, install and cleanup in one step so it gets saved as only one layer in the Docker filesystem, saving space.

You can then put that together with the official MySQL Docker Image and a few volumes to have a fully working OwnCloud setup with docker-compose.

version: '2'

services:
  mysql:
    image: mysql:latest
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=passwordgoeshere
    volumes:
      - ./data/mysql:/var/lib/mysql:rw,Z

  owncloud:
    hostname: owncloud.localdomain
    build: owncloud/
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=passwordgoeshere
    ports:
      - 8300:80
    volumes:
      - ./data/data:/var/www/html/data:rw,Z
      - ./data/config:/var/www/html/config:rw,Z
      - ./data/apps:/var/www/html/apps:rw,Z
    depends_on:
      - mysql

Create the directories that are mounted there, set the password to something sensible, and docker-compose up !

One thing though.  OwnCloud doesn’t have any built-in account lockout policy, so I wouldn’t go putting this as it is on the ‘Net just yet.  You’ll want something in front of it for security, like nginx.  You’ll also want HTTPS if you’re doing that.

More on that later.

How to convert an MP4 to a DVD and burn it on Linux

If you’re using Vagrant with VirtualBox on Windows, create a new directory, throw the source mp4 in it, then create a Vagrantfile like this;

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-16.04"

  config.vm.provider "virtualbox" do |vb|
  vb.customize ["storageattach", :id, "--storagectl", "IDE Controller", "--port", 0, "--device", 0, "--type", "dvddrive", "--passthrough", "on", "--medium", "host:X:"]
  end
end

Edit the host:X: to be the drive letter of your physical DVD drive.

Then bring up the VM with;

vagrant up
vagrant ssh
sudo -s -H

Now that’s done, do this.  You can start from here if you’re already on Linux or have some other means of getting a VM ready.  I assume you’re going to want to make a PAL DVD, and that your DVD is in /dev/sg0 (check with wodim --devices);

apt-get install dvdauthor mkisofs ffmpeg wodim
ffmpeg -i input.mp4 -target pal-dvd video.mpg
export VIDEO_FORMAT=PAL
dvdauthor -o dvd/ -t video.mpg
dvdauthor -o dvd/ -T
mkisofs -dvd-video -o dvd.iso dvd/
wodim -v dev=/dev/sg0 speed=8 -eject dvd.iso

All done.  Assuming everything went well, you have a freshly burned DVD, all using open source Linux software, with no horrible adware that tends to come with Windows DVD burning software.

You can then get rid of the VM with vagrant destroy.

Ansible with Vagrant on Windows

Since I’m converting all my builds and other things to use Ansible, the idea of using Ansible to customize a Vagrant box is very attractive.

I’ve chosen to use the ansible-local provisioner in this case, so that Ansible runs inside the Vagrant box.  I’ll do an example later where this isn’t the case.

Have a look at this gist for some info about how to do this.  Or read on.

Step 1 – the Vagrantfile

In a blank directory, edit a new Vagrantfile.  Make it look something like this;

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
 # The default ubuntu/xenial64 image has issues with vbguest additions
 config.vm.box = "bento/ubuntu-16.04"

 # Set memory for the default VM
 config.vm.provider "virtualbox" do |vb|
   vb.memory = "1024"
 end

 # Configure vbguest auto update options
 config.vbguest.auto_update = false
 config.vbguest.no_install = false
 config.vbguest.no_remote = true

 # Configure the hostname for the default machine
 config.vm.hostname = "ansible-example"

 # Mount this folder as RO in the guest, since it contains secure stuff
 config.vm.synced_folder "vagrant", "/vagrant", :mount_options => ["ro"]

 # And finally run the Ansible local provisioner
 config.vm.provision "ansible_local" do |ansible|
   ansible.provisioning_path = "/vagrant/provisioning"
   ansible.inventory_path = "inventory"
   ansible.playbook = "playbook.yml"
   ansible.limit = "all"
 end

end

There’s a few things going on here.  First up, we define the default box we’re going to use, the memory allocated to it, our auto-update options and the hostname.

Next up is we define a synced folder that will appear in the Vagrant box.  There is a default, which is for the folder the Vagrantfile is in to appear as /vagrant.  However, this is shared on VirtualBox with R/W access, which means that the box can modify your original files (including its own Vagrantfile).  Not necessarily bad, but I don’t like the idea of that very much.

Lastly, we define the Ansible provisioner.  This will simply run the playbook that’s in the vagrant/provisioning subfolder of the Vagrantfile against all hosts.

Step 2 – Create Playbook

Do the following to create the rest of the structure (from within the directory your Vagrantfile is in);

mkdir -p vagrant/provisioning

Now, you’ll need to create an ansible.cfg in that directory, like this;

[defaults]
host_key_checking = no

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes

The parameters are necessary to avoid Ansible having a freak-out about SSH keys and whatnot when deploying.  Of course, if you have just one host, you don’t need to worry about it.

Next, you need an inventory spec;

ansible-example ansible_connection=local

This forces deployments against the machine we’re deploying to use the local connection type.

And lastly, a really basic playbook to test it out;

---

- hosts: ansible-example
 tasks:
 - copy: content="IT WORKS!\n" dest=/home/vagrant/ansible_runs

...

Step 3 – Run it!

Now we’ve set up the most basic structure, bring it up!

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'bento/ubuntu-16.04'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'bento/ubuntu-16.04' is up to date...
==> default: Setting the name of the VM: ansible-example_default_1472187535117_41803
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
 default: Adapter 1: nat
==> default: Forwarding ports...
 default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
 default: SSH address: 127.0.0.1:2222
 default: SSH username: vagrant
 default: SSH auth method: private key
 default:
 default: Vagrant insecure key detected. Vagrant will automatically replace
 default: this with a newly generated keypair for better security.
 default:
 default: Inserting generated public key within guest...
 default: Removing insecure key from the guest if it's present...
 default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
 default: The guest additions on this VM do not match the installed version of
 default: VirtualBox! In most cases this is fine, but in rare cases it can
 default: prevent things such as shared folders from working properly. If you see
 default: shared folder errors, please make sure the guest additions within the
 default: virtual machine match the version of VirtualBox you have installed on
 default: your host and reload your VM.
 default:
 default: Guest Additions Version: 5.0.26
 default: VirtualBox Version: 5.1
==> default: Setting hostname...
==> default: Mounting shared folders...
 default: /vagrant => C:/cygwin64/home/username/ansible-example/vagrant
==> default: Running provisioner: ansible_local...
 default: Installing Ansible...
 default: Running ansible-playbook...

PLAY [ansible-example] ****************************************************************

TASK [setup] *******************************************************************
ok: [ansible-example]

TASK [copy] ********************************************************************
changed: [ansible-example]

PLAY RECAP *********************************************************************
ansible-example : ok=2 changed=1 unreachable=0 failed=0


$

And to prove that the playbook actually, really, did run;

$ vagrant ssh
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-31-generic x86_64)

 * Documentation: https://help.ubuntu.com
 * Management: https://landscape.canonical.com
 * Support: https://ubuntu.com/advantage
Last login: Fri Aug 26 05:01:58 2016 from 10.0.2.2
vagrant@ansible-example:~$ cat ansible_runs
IT WORKS!
vagrant@ansible-example:~$

You can then re-run the playbook any time you like with vagrant provision .

The main catch with running Ansible like this is that it actually installs Ansible on the Vagrant box.  You can get around this by running Ansible on your Vagrant host.  More on this later.

Vagrant on Cygwin/Virtualbox Quickstart

So, you want to try out Vagrant, and you’re using Windows with Cygwin?  Have I got something for you!

Preparing the Environment

Firstly, get Oracle VirtualBox installed.  I personally prefer VMware Workstation, but VirtualBox works better for this.  Also get the extensions while you’re at it.

Next, go and install Vagrant, and use the default settings.  Now we’re going to have to manually patch a file in the Vagrant source.  Go to /cygdrive/c/HashiCorp/Vagrant/embedded/gems/gems/vagrant-1.8.5/plugins/guests/linux/cap in Cygwin, and edit public_key.rb .  At line 57, make the code look like the bit that’s highlighted here;

if test -f ~/.ssh/authorized_keys; then
  grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp
  mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys
  chmod 0600 ~/.ssh/authorized_keys
fi

This won’t be necessary in a newer version of Vagrant, but it is required in 1.8.5 for some boxes to work.

Next up, bring up your Cygwin prompt, and do this.  This will remove the default VMware provider (if it’s installed), and put in a plugin that automatically updates VirtualBox Guest Additions (optional, but very useful)

vagrant plugin uninstall vagrant-vmware-workstation
vagrant plugin install vagrant-vbguest
vagrant version

It should spit out that you’re running an up-to-date Vagrant.  Great.

Bringing up your first Vagrant box

Now, I’m a CentOS fan, so we’ll be bringing up a CentOS box first.  From your Cygwin prompt, do this;

vagrant box add centos/7 --provider virtualbox
mkdir vagrant-test && cd vagrant-test
vagrant init centos/7
vagrant up
vagrant ssh

If everything’s been done correctly, you’ll find yourself in a shell on your new Vagrant box.  By default, the VM will be using NAT.  Poke around, and when done, exit and do;

vagrant destroy -f
cd ..
rm -rf vagrant-test

To clean everything up.  After cleanup, you’ll still be left with the centos/7 box cached, you can ditch that with vagrant box remove centos/7 .

All done!  You’ve got a working Vagrant environment on Windows, running under Cygwin against a VirtualBox provider.  Magic!

Bash on Windows – X Server!

It turns out that you can use Bash on Windows 10 to run X applications, including through ssh tunnels.  Here’s how.

First, go and install XMing.  I’d strongly suggest not allowing it to get access to your network, so it stays on localhost.  This is so that an attacker can’t draw stuff on your screen through your X server.

Run XMing, put it in your startup if you want.  You now have an X server.  Next up, you’ll need to fire up Bash on Windows, and run sudo apt-get install xauth.  Then edit your ~/.bashrc .  Right down the bottom, add the following;

export DISPLAY=localhost:0
xauth generate $DISPLAY

This causes your session to be configured so that you can use X applications and they’ll be pointed to your X server.  It also provides the correct X authentication tokens to make things like ssh work.

Now, log out and back in again.  Start up Bash for Windows, then you can run stuff.