Learning Crossplane: Creating a GKE Cluster from Scratch (Crossplane 1.1)

What is Crossplane?

Crossplane is a tool that allows you to use Kubernetes to build and maintain infrastructure. The intent is to serve as a centralized cross-platform control plane for your infrastructure. (You can do a lot more than build infrastructure, and there is a famous example of using it to order pizza to prove the point.) It encourages you to create opinionated definitions for your infrastructure, that can than be applied in any environment that you have configured it to work with.

Like Terraform, you declare what your infrastructure should be in a set of files, and when you apply the changes, the tool makes the environment match your declaration. With both tools, because you define your infrastructure as code, you get all the wonderful advantages of repeatability, consistency, ability to document as much or as little and where ever you like, etc.

With Terraform, if the infrastructure changes outside of Terraform, it remains that way until the next Terraform apply. With Crossplane always running and checking the state of your system, if someone changes the infrastructure outside of the Crossplane declaration, Crossplane will quickly change it back.

Crossplane is implemented as Kubernetes Custom Resource Definitions (CRDs), so using Crossplane is simple for teams that already are familiar with applying YAML files in Kubernetes.

Why this tutorial?

As I write this, Crossplane is at version 1.1. Much that has been written about crossplane, including some compelling examples to learn from, are from versions before 0.13, when Crossplane made many breaking changes to its architecture and API. This made it hard to follow online examples and impossible to implement as written – and I found no clear documentation on how to convert pre-0.13 to post-0.13 CrossPlane.

For example, I was excited to run through InfraCloud’s tutorial that teaches you how to deploy a Google Kubernetes cluster with Crossplane. They do a great job explaining the concepts, then teaching you how to implement them. Unfortunately, their tutorial was written for Crossplane 0.4.1, and Crossplane is a completely different product now. Almost nothing in the tutorial works anymore.

So, thanks to an example in the Crossplane GitHub, a lot of reading, and some good luck, I was able to build it. Here is how.

First, lets review what we’re trying to do. This guide will will walk you through the steps of using Crossplane to create a Kubernetes cluster in Google Cloud Platform, along with the requisite networking. Crossplane runs in Kubernetes, so for this tutorial, I assume you’ll install Kubernetes and Crossplane on your computer, although you could just as easily install it in a Kubernetes cluster in a cloud provider or use a hosted Crossplane provider, like Upbound Cloud.

We will use Crossplane to build a network and cluster in GCP

1. Setup

A. Setup Kubernetes to host CrossPlane

As noted above, Crossplane runs in Kubernetes. So you need a kubernetes cluster to host it. I chose Minikube running in WSL2 on Windows. You should be able to use any Kubernetes installation on any platform, and there is lots of good information available on how to set up Kubernetes for your particular platform, so I won’t try to replicate it. If you happen to use WSL2 on Windows, here are some links I found helpful:

B. Install Crossplane

Crossplane provides excellent instructions on how to install it. Follow the instructions up to and including Install Crossplane CLI. In summary, you need to:

  • Install Helm if you haven’t already
  • Confirm Crossplane is running
  • Install the Crossplane CLI
  • STOP HERE. The rest of the instructions are specific to AWS, while we are using GCP.

C. Create a GCP Project if you have not yet

For this example, we’re using Crossplane to build a cluster in GCP. So you need a project in Google Cloud. At this step you don’t need a network or infrastructure set up in GCP other than your account and a project. Instructions for setting up a Google Cloud Project are here: https://cloud.google.com/getting-started. Be sure to enable billing for the project too.

For this tutorial, we are going to assume the name of the project is crossplane-test.

D. Install the Google Cloud SDK if you have not yet

Interacting with the Google API is much easier if you have the Google Cloud SDK.

At this point, if everything is set up properly, you should be able to run some gcloud commands to confirm everything is ok:

First, log in to google:

gcloud auth login

Follow the link it provides, log in to your Google account, accept the terms and conditions, copy the authorization code, and paste it in at the prompt from gcloud auth login.

Next, list your projects:

gcloud projects list

Or, if you have a lot of projects, you can list just the one you created:

gcloud projects list --filter='name="crossplane-test"'

Take a close look at your list of projects. If the NAME does not match the PROJECT_ID, note the PROJECT_ID.

2. Bootstrapping

We have Crossplane installed and running. Now we need to do the basic configuration to connect to GCP. Crossplane provides a script for performing this process, but it is fairly easy to do manually, and doing so will help you understand how Crossplane works. Crossplane’s script also enables APIs that are not necessary for this tutorial (and likely misses APIs you will use if you start using Crossplane “for real.”) So let’s skip the script and do it by hand.

Assuming you have the project created and billing configured, you need to do the following:

A. Set up some environment variables

These variables are for strings we will use a lot, or which you will likely use different values than I did. If you set them now, you should be able to copy/paste future commands with little or no editing.

# set this to the PROJECT_ID
export GCP_PROJECT=crossplane-test
# set this to whatever you want the service account name to be
export SA=crossplane-svc-acct  
# don't change this     
export GCP_SVC_ACCT="$SA@$GCP_PROJECT.iam.gserviceaccount.com"  
# set this to the path where you want to store Crossplane's authentication key file.
# If you just use a filename (like here), it will be stored in your home directory
# Otherwise, you can specify a path and filename
export KEY_FILE="$SA-keyfile.json"         
# set this to the namespace in your **Crossplane Kubernetes Cluster** where will store
# the information for the Crossplane GCP provider to connect to GCP.
# This can be the namespace Crossplane is running in (typically crossplane-system)
# but it doesn't have to be.  For this tutorial, use it.
export PROVIDER_SECRET_NAMESPACE=crossplane-system
    

B. Enable the compute, Kubernetes (container), and networking APIs:

These are the APIs used for this example. When you start doing different actions in GCP with Crossplane, you may need to add additional APIs.

gcloud services enable --project $GCP_PROJECT \
  compute.googleapis.com \
  container.googleapis.com \
  servicenetworking.googleapis.com

C. Create a service account for Crossplane to use to do its work and save it in Crossplane’s Kubernetes

This service account will need to be able to do everything you want Crossplane to be able to do. As with any account used by a tool that helps automate your infrastructure management, if you run Crossplane in a production environment, you should give the account the least privileges necessary and monitor the use of the account.

gcloud iam service-accounts create --project $GCP_PROJECT $SA

Once the account is created, you need to download a JSON key for the account so Crossplane can authenticate to perform the tasks we ask it to do:

gcloud iam service-accounts keys create --iam-account $GCP_SVC_ACCT--project $GCP_PROJECT $KEY_FILE

Now, we need to store that key in a secret in Crossplane’s Kubernetes namespace so that Crossplane can use the key to authenticate to GCP. There are a handful of ways to do this, the easiest is probably to create a yaml file and apply it.

cat > authentication.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: gcp-account-creds
  namespace: ${PROVIDER_SECRET_NAMESPACE}
type: Opaque
data:
  credentials: $(base64 crossplane-gcp-provider-key.json | tr -d "\n")
EOF

Apply the yaml to create the secret:

kubectl apply -f

D. Grant the service account the appropriate permissions to do its work

Just like with the APIs, as you do more with Crossplane, you likely will need to add additional permissions to the service account.

gcloud projects add-iam-policy-binding $GCP_PROJECT --member "serviceAccount:$GCP_SVC_ACCT" --role="roles/iam.serviceAccountUser"
gcloud projects add-iam-policy-binding $GCP_PROJECT --member "serviceAccount:$GCP_SVC_ACCT" --role="roles/container.admin"
gcloud projects add-iam-policy-binding $GCP_PROJECT --member "serviceAccount:$GCP_SVC_ACCT" --role="roles/compute.networkAdmin"

E. Set up the GCP provider

Finally, we need to configure the Crossplane GCP provider to tell it where to get the authentication information we created in step C.

NOTE: While this can be combined with the authentication.yaml in Step C, I prefer to keep them separate because the authentication.yaml file contains sensitive information you don’t want to leave on your computer or, worse, add to source control. The provider.yaml file, on the other hand, does make sense to keep. While this is just a tutorial, and the risk if these credentials are exposed is presumably low, I prefer to set a good precedent so one doesn’t accidentally commit sensitive information to GitHub or some other repository.

cat provider.yaml <<EOF
apiVersion: gcp.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
  name: crossplane-provider-gcp
spec:
  projectID: ${GCP_PROJECT}
  credentials:
    source: Secret
    secretRef:
      namespace: ${PROVIDER_SECRET_NAMESPACE}
      name: gcp-account-creds
      key: credentials
EOF

Once again, apply the yaml file:

kubectl apply -f provider.yaml

F. Check your setup

Lets double check things to make sure we are ready to proceed to creating our network and cluster.

Check 1: Confirm the GCP provider is installed and healthy.

If you run kubectl get Providers, you should see the Provider you installed in the last step. For example:

kubectl get Providers
NAME                      INSTALLED   HEALTHY   PACKAGE                           AGE
crossplane-provider-gcp   True        True      crossplane/provider-gcp:v0.16.0   11d

Check 2: Look at the ProviderConfig to make sure it contains what we created.

When we defined the Provider, Crossplane defined a ProviderConfig for us that provides details about the provider. We can check them to make sure we didn’t mistype anything.

Running kubectl describe ProviderConfig default will show more information than we need right now. At this point, just make sure it returns expected values for the Secret’s key, name, and namespace.

kubectl describe ProviderConfig default
<<<<<<<<<< lines removed for brevity >>>>>>>>>>
Spec:
  Credentials:
    Secret Ref:
      Key:        creds
      Name:       gcp-creds
      Namespace:  crossplane-system
    Source:       Secret
<<<<<<<<<< lines removed for brevity >>>>>>>>>>

3. Set up Networking

Finally, we can start to play with Crossplane!

Before we build a cluster, we first need to define the network the cluster will use. We need a network and a subnet. Using your favorite editor, create a file named networking.yaml that contains the following (you may want to change some values in this to match you personal preferences):

apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
  # set this to whatever you want
  name: crossplane-built-nw
spec:
  forProvider:
    autoCreateSubnetworks: false
    description: 'This is a network built by crossplane'
    routingConfig: 
      routingMode: 'REGIONAL'
---
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Subnetwork
metadata:
  name: crossplane-test-subnet
spec:
  forProvider:
    ipCidrRange: '192.168.0.0/21'
    networkRef: 
      # make sure this matches the network name you defined in Network
      name: crossplane-built-nw
    # pick a the closest region to you, although for a tutorial, it won't matter much which
    region: us-east1

Again, use kubectl apply -f networking.yaml to apply the file

Now, confirm the network and subnet exist by running kubectl get Networks and kubectl get subnetworks. These commands should show the network and subnet you just created, and both READY and SYNCED should show as true. If SYNCED is False, Crossplane may not be successfully authenticating with your GCP instance. (NOTE: once you fix the problem, it can take a minute or so before Crossplane will show SYNCED as True). If SYNCED is True, you should see the network in the GCP Console too.

4. Clean up our Authentication files

We still have some files with sensitive information in them lying around. Get in the habit of cleaning up any files with authentication information in them immediately. (Even better, don’t create local copies of files with sensitive information, because you might forget to remove them, or not truly remove them. But we did for this tutorial, so we need to clean them up.)

Delete authentication.yaml and $KEY_FILE. I’m using WSL, so I have shred -u available. On a Mac use rm -P and in Windows, delete the file and use PowerShell’s cipher command to securely delete the files.

shred -uz authentication.yaml $KEY_FILE

The data is still in the secret, so if you needed to get it again, you easily could.

5. Create a cluster and node pool

The yaml for creating a simple cluster and node pool is pretty extensive. So rather than creating it from scratch, I recommend downloading Crossplane’s example cluster yaml, and customizing that. Below are the items I chose to change, and why. (Do not try to apply what is written below; it is incomplete):

metadata:
  # name the cluster whatever you want
  name: example-cluster
spec:
  forProvider:
    addonsConfig:
      # if you are going to use this cluster, you may wish to enable httpLoadBalancing
      # depending on the types of services you'll deploy
      httpLoadBalancing:
        disabled: true
    # match the location of the network you created
    location: us-east1
    # set the network to match the network we created in Step 3
    network: "gke-test" 
    networkPolicy:
      enabled: true
      provider: CALICO
    podSecurityPolicyConfig:
      enabled: true
---
kind: NodePool
metadata:
  # name the nodepool whatever you want
  name: crossplane-np
spec:
    clusterRef:
      # specify the cluster name you used above
      name: example-cluster
    config:
      # you can use a smaller (or larger) machine if you wish
      machineType: n1-standard-1
      # delete this line. It conflicts with the machineType line above
      # (or leave it in and determine what happens)
      machineType: n1-standard-2
    # match the locations of the network we created in Step 3
    locations:
      - "us=-east1"

This will take a while to build. You can follow its status in the Cloud Console or by running kubectl get gkeclusters and waiting until the STATE is RUNNING.

At this point, you should have a cluster running in GKE. Congratulations!

6. Tests and Explorations

Here are some things you might want to explore to gain a deeper understanding of how Crossplane works.

  • What happens when you use kubectl edit to edit properties of the cluster?
  • If you left both machineType entries in the NodePool definition, what type of machineType are your nodes?

This tutorial only scratches the surface of Crossplane and its potential. The next step is to create custom Managed Resources and Composite Resources.

7. My Opinion

I’m excited to see how Crossplane further develops and how the roles of Crossplane and Terraform play out. Will they essentially be competitors, or will they coexist in environments, each playing similar but separate roles? It may make sense for centralized DevOps support teams to continue to use Terraform for building and managing overall shared infrastructure, while also building opinionated Crossplane Composite Resources for individual teams to use for the infrastructure they maintain.

I also look forward to the GCP support maturing; Crossplane currently has managed resources for only a handful of GCP objects. With Google offering Google Config Connectors, GCP shops already have a set of CRDs that can be used for GCP infrastructure. However, Config Connectors lack Crossplane’s relatively simple option for creating opinionated and composite CRDs.

Hopefully Crossplane updates will remain stable and be backwards-compatible, at least within major version releases. If Crossplane continues to release breaking architectural changes in point-releases like they did in 0.13, they will frustrate both new users and authors of tutorials like this one.

Crossplane is still a very young project, but is developing at a rapid pace. I expect we will see its adoption rise as more teams find value in the ability to easily create opinionated CRDs, with the option of making them apply across multiple platforms.

Raspberry Pi as a Splunk Universal Forwarder to Store-and-Foward Logs

I am a fan of Splunk, so I run it at home.  The problem is, I don’t want it running all the time, but I always want to collect logs.

Why don’t I just leave Splunk running? I run it as a virtual machine and it consumes memory and cpu, which I often need for other VMs. Also, the system that hosts my VMs consumes a fair amount of power, and as the owner of a partially solar-powered home, I try to conserve power consumption.  So I turn that system off when I’m not using it.

Therefore, I  need a low power way to continuously gather logs from any systems that are running, store the logs, and forward them to splunk when I choose to run it.

The Raspberry Pi is perfect for this sort of store-and-forward scenario.  The architecture is pretty simple.  I run syslog-ng on the pi to collect syslogs from all the systems and save them to disk. The pi also runs the Splunk Universal Forwarder to forward those logs to Splunk when Splunk is running. The Universal Forwarder keeps track of what it has forwarded and what it hasn’t, and only forwards what it needs. So, I can leave the pi up, running (but not consuming much power), and gathering logs, and only fire up my Splunk system when I need to. When I run Splunk, the Universal Forwarder sends along all the logs it gathered while Splunk was powered off and continues to forward logs until I power Splunk off again.

One of the key things I learned early on is the importance of keeping the logs on a separate drive so if they fill the drive, they don’t consume the operating system’s disk (which will crash the pi). I use a USB flash drive, and moved the full /var/log directory to it because any one of several logs could fill the drive.

Here is the overall process:

  1. Build the core splunk system (the indexer & search head) and confirm it works ok. I’m not going to cover that here.
  2. Set your core splunk system to receive traffic from forwarders
  3. Install Raspbian on the Raspberry Pi (from raspberrypi.org)
  4. Using raspi-config, set the disk to use the full SD card, set the hostname, and set the timezone
  5. Set your router to give a fixed ip to the raspberry pi.
  6. Set up a USB stick to be the /var/log directory
    1. format the USB stick to ext3
    2. move the /var/log dir to the USB stick (instructions here are for moving /var, but the procedure is the same).
      Note, in the step to edit the fstab file, I used the following:
      UUID=uuid  /var/log  ext3   defaults   0   1
      (use blkid to determine the UUID)
  7. Install syslog-ng (sudo apt-get install syslog-ng)
  8. Configure a system to send syslogs to this system using udp port 514.
  9. Configure syslog-ng
    1. edit syslog-ng.conf (sudo vi /etc/syslog-ng/syslog-ng.conf)
    2. add the following lines to the appropriate sections to set syslog-ng to listen for syslogs on udp port 514 and save them to /var/log/udp514.log (or whatever you want to call your log file. syslog-ng can do a lot more if you wish, including create unique log files for every log that comes in)
       # source for syslog 514 traffic
       source s_udp514 { udp(port(514)); };
       # destination for udp 514 syslogs
       destination d_udp514 { file("/var/log/udp514.log"); };
       # All udp514 logs
       log { source(s_udp514); destination(d_udp514); };
    3. restart syslog-ng (sudo /etc/init.d/syslog-ng restart)
    4. watch that log to see it is getting data (tail -f /var/log/udp514.log)
  10. Install the Splunk Universal Forwarder
    1. download the forwarder
    2. install the forwarder (sudo tar xvzf forwarder-for-linux-arm-raspberry-pi_10.tgz -C /opt)
    3. configure splunk to run with user id splunk & start splunk
      sudo useradd splunk
      sudo groupadd splunk (the group may be created already)
      sudo chown splunk:splunk /opt/splunkforwarder/
      sudo -H -u splunk /opt/splunkforwarder/bin/splunk star
      t
    4. configure to run at boot
       sudo /opt/splunkforwarder/bin/splunk enable boot-start -user splunk
    5. make sure any logs you wish to forward are readable by splunk. Since logs are typically read-writefor the owner and read-only for the group, you can change the group.  You may choose to do a single file, eg
      sudo chgrp splunk /var/log/udp514.log
      or all the log files, eg
      sudo -R chgrp splunk /var/log/
  11. Reboot your pi and confirm splunk is running using the right id (splunk)
    sudo reboot -r now
    ps -ef | grep splunk
    You should  get a result similar to this, showing that splunk, not root, is running splunk (the first column is the user):

    splunk    2188     1 24 00:21 ?        00:00:08 splunkd -p 8089 start
    splunk    2189  2188  0 00:21 ?        00:00:00 [splunkd pid=2188] splunkd -p 8089 start [process-runner]
    pi        2262  2247  0 00:22 pts/0    00:00:00 grep --color=auto splunk
  12. Configure the Universal Forwarder
    1. since splunk now runs as the splunk id, change to that id and change to the splunk directory
      sudo su – splunk
      cd /opt/splunkforwarder/bin
    2. set the admin password to something unique (the default is “changeme”)
      ./splunk edit user admin -password <new password> -role admin -auth admin:changeme
    3. set the forwarder to forward (use your new password)
      ./splunk add forward-server <host>:<port> -auth admin:<password>
    4. set what to monitor and forward
      ./splunk add monitor /var/log/
  13. Log into your Splunk instance and check out your logs!
    1. If you have problems, check out this troubleshooting page: Troubleshooting Forwarding
    2. Validate the approach works by shutting down your core Splunk.  Notice that the next time you power it up, after a little delay logs will start filling in from during your outage.
  14. Configure your logs to rotate
    1. Edit /etc/logrotate.conf and add the following (this will rotate when the size hits 1G, the new log file it creates will be owned by splunk/splunk with 740 permissions, and we’ll keep up to 10 files)
      /var/log/udp514.log {
          size 1g 
          create 740 splunk splunk
          rotate 10
      }
    2. Note that Splunk won’t read the rotated logs, so it probably makes sense to zip them and keep fewer copies.

Since first writing this, I found these excellent instructions, complete with screenshots, for setting up a universal forwarder. They are great, although they don’t have the Raspberry Pi specific details, or how to run splunk as another user.

Book Review: Lauren Ipsum

I don’t normally post book reviews, but I am so impressed with Lauren Ipsum that I feel compelled to plug it. It is a great book for young readers – and the rest of us too!

Most importantly, it is a fun book. My 7-year old daughter and I are reading it together, and she loves it. (So do I!) As Lauren (aka Laurie) explores a handful of towns with a lizard named Xor whom she has befriended, the reader explores a broad range of fundamental computing principals. The book offers many layers of computing references. Just reading the book will introduce the reader to basic coding (in a simple and fun way), security, algorithms, the negative impact of jargon, etc. An older reader can refer to the chapter notes in the back, which provide interesting context for some of the ideas that are briefly mentioned but not really explored. And geeks will be amused by the endless subtle (or blatant) allusions in the names, places, and objects, such Hugh Ristic and Lauren herself (see Lorem Ipsum). These references are cleverly inserted in a way that will amuse those “in the know” without being distracting for others.

To enter several of the towns, Laurie needs to tell guards her name and password, but she isn’t “in the system.” So, she guesses those of others she has met along the way by exploiting the weaknesses she notices in the guards’ processes. It presents great lessons in password security, but also offers an opportunity to discuss hacking and ethics, should you choose to. Is it appropriate for Lauren to use someone else’s name and password? In the story’s context it is harmless and the ethics are barely addressed, but it is something my daughter and I have chatted about, and I intend to dig deeper into.

The story carries itself. Even if there were no ulterior lessons, this would be a great chapter book for 7-12 year olds. Given the additional depth the book offers, I expect we’ll continue to re-read it over the years and appreciate it in different ways. Definitely one for the home library.

As a bonus – the publishers are offering a “get one give one” program – if you buy a retail copy, they’ll donate a copy to a library, school, or other educational organization.

Thanks big sister, for giving my daughter Lauren Ipsum for Christmas! Great gift! Next maybe I’ll write about the Kano.

Application Configuration Management

For many years now, I’ve been frustrated by the lack of configuration management maturity in the applications I use. I’m particularly surprised (appalled) that security software nearly always falls short. The problem isn’t only with security software, it’s just that I would expect more from software designed to secure systems. Virtually all security software maintains audit logs, and most can ship those to a log server, but few do more than that. Pick a tool, and chances are you can configure it, but can’t roll back to a previous configuration, compare two configurations, or migrate a configuration changes to another system.

Information Security has three core tenets we refer to as CIA: Confidentiality, Integrity, and Availability. If you are an Infosec professional, you are responsible, directly or indirectly, for ensuring those qualities of the your systems. So what are the core tenets for configuration management? What should we insist that all configurable software support? I propose CAARR.

  • Comparability: I should be able to compare the configurations of two different points in time or of two different systems. Ideally, a tool should provide this functionality and offer context. That is, the tool should identify what the differences represent from the user interface perspective, rather than from the software developer’s or the data structure’s perspective. That is a tall order, however. At the very least, the configuration should be exportable as a human-readable flat file, so that configurations may be stored and compared. (Scooter Software’s Beyond Compare is exceptionally good for such comparisons. You can even customize how it performs the comparison – e.g., what it ignores. Microsoft’s free XML Notepad is a great tool for comparing these files if they are XML formatted.)
  • Accountability: The software needs to log who did what, and when. As I mentioned above, most security software does this, at least at a rudimentary level. Frequently, however, the logs lack sufficient detail, or only address specific types of changes or events. For example, Symantec Endpoint Protection v11 does a great job logging events on the end users computer, but the management console provides minimal logging of configuration changes. It identifies who made a change and when, but all they tell you about the change itself is what type of change it was, e.g., a policy changed. If the software doesn’t allow you to configure the level of logging detail, it should at least provide you with sufficient information to know what changed, if not explicitly what the change was.
  • Annotatability: Many tools lack support for annotations in the configuration or audit log. It is invaluable to be able document why a change was made, who requested or authorized it, when should the change be removed, etc. The annotations should exist right in the configuration itself, adjacent to the changed setting, and the annotations should be visible when are making changes within the tool. That way, when you or someone else is considering changing the configuration, they’ll be aware of previous decisions. For example,
    Cisco PIX and ASA firewalls provide a comments field to allow you to document each access control. It is up to you to enter comments every time you make a change – the tool does not enforce it nor remind you. Also, Cisco firewalls lack full annotation support – there are many items that can be created or changed (e.g., NATs) where there is no option for comments.
  • Rescindability: “Oops!” What do you do when you or a colleague utters that (or worse)? Roll back the changes, of course. Software configurations should be easy to roll back to a previous baseline. Ideally, you should be able to keep multiple configurations stored away, so you can roll back to any previous settings, not just the last one. Whether you identify baselines within the software or simply save configuration files, if you can’t undo changes easily, you’re in for a tough time.
  • Repeatability: Do you have multiple installations of this tool? Maybe you have a test installation so you can experiment before putting changes into production. Maybe you have the software installed in locations across the globe. You need to be able to configure a tool and repeat that configuration (or portions of that configuration) elsewhere. You should not have to keep detailed notes about what you’re doing, so you may manually perform those steps on other systems.

So why do most applications fail to meet these requirements? I don’t know, although I think it is partly because programming languages and tools don’t make it easy. You can find frameworks that support logging and frameworks that support configuration files. However, not all frameworks support all five CAARR components. In fact, I’m not sure any do. In a way, this is ironic, because any decent software version control tool (e.g., Subversion, ClearCase, AccuRev, Perforce, Team Foundation, etc.) provides all of these, so developers are (or should be) familiar with the concepts. Tools that store their changes in databases seem to be the least likely to support comparability and repeatability. I presume this is because with a database structure, the developer or framework will need to create the comparability and repeatability functions, and the more tables used to support the configuration, the more complex the coding will be. On the other hand, if the configuration is stored in a flat file, comparability and repeatability are are essentially assured, especially if the file is human-readable and understandable. If the tool is not developed from the onset with CAARR principles in mind, it probably will never be.

The next time you evaluate a software tool, include CAARR in your requirements. Just don’t be surprised if none of the applications you review score well, and you may be forced to compromise. Let the vendors know that the lack of CAARR hurt their candidacy, and hopefully this sad state will begin to improve.

Part 6 – Addendum

This is an addendum to a five part series:

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains
  6. Addendum

I’ll update this more later (or just replace it), but wanted to comment on the fact that there is an important difference between my sample rainbow tables and those in the Wikipedia article.  in my rainbow tables, the leftmost column is plaintext, and the rightmost is a hash.  In the wikipedia article, the rightmost column is also plaintext.  It is the same table, but they do one extra reduction (R3) to get the plaintext.  This means that you can’t look up your hashed value in the table – the first thing you MUST do is run your last reduction, and then look up the resultant plaintext.  Why would you do this?

Space.

In my example (and the wikipedia example), the plaintext and hashes are similar sizes (and small).  In reality, if you’re dealing with MD5 or SHA-1 hashes, your plaintext values are going to be MUCH shorter!  So, you can save a lot of space by doing one more reduction, and using the reduced values instead.  Of course, you increase the risk of collisions (and run extra calculations), but that is the price of admission.

Rainbow Tables – Part 5 (Chains and Rainbow Tables)

This is Part 5 of a five part series:

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains (you are here)
  1. Addendum

So – here we go!  Let’s look at rainbow tables!

Here is a sample lookup table for a very simplistic encryption algorithm (discussed in Part 2) that takes a number from 0 to 99 and hashes it into a 4 digit number:

p1 h3
3 3708
10 5850
25 4202
68 5520
89 5109

It looks like a plain old lookup table with plaintext in the left column, and the hashed value in the right column.  But, that isn’t the case.  In fact, with the encryption algorithm we’re using for the example, the hashed value for 3 is 3955.  So what are we looking at?

As we discussed in Part 1, a basic lookup table would simply list every possible password (plaintext) and its corresponding encrypted value (hash).  If you know the encrypted value, you just look it up and see what the password is.  The problem with an ordinary lookup table is that it can quickly get huge.  A rainbow table provides a way to make it dramatically smaller.  A rainbow table does this through the use of chains and reduction functions.

The beauty of a rainbow table is that you don’t store the whole table. Instead, you store the left-most column and the right-most column, and you calculate the values in between as needed.  The table we saw above contains those two columns (leftmost and rightmost) from this rainbow table:

p1 h1=H(p1) p2=R1(h1)
h2=H(p2) p3=R2(h2)
h3=H(p3)
3 3955 55 4532 45 3708
10 0823 23 5603 56 5850
25 2059 59 3626 36 4202
68 3131 31 3790 37 5520
91 2554 54 3213 32 5109

Because we’ve used 10 entries to represent the 30 entries in table above, we’ve reduced the space by 66%!  Of course, there is a cost – we have more calculations to do. It is a tradeoff between size and speed.  We’ll look into that later; for now, lets just see how the rainbow table works by considering some examples.

Let’s say you have an encrypted value of 5520 that you want to decrypt.  Let’s look it up in our table.  Remember, we’ve only stored the lookup table, not the full rainbow table, so that is where we need to look:

p1 h3
3 3708
10 5850
25 4202
68 5520
89 5109

Yay!  We have a match!  If this were a simple lookup table, we would be done.  But it isn’t!  This lookup table represents the rainbow table above.  Because we’ve saved space, we have some calculations to do.  Time to pay the piper…  (This is where it gets interesting.)

Right now, all we know is that we have a hashed value of 5520, which is the right-most value in the chain that begins with 68:

p1 h1=H(p1) p2=R1(h1) h2=H(p2) p3=R2(h2) h3=H(p3)
68 3131 31 3790 37 5520

So, we take 68 and hash it to get 3131.  With hashes, we can always hash a plaintext value, to get its resulting hashed value, but we cannot go backwards (see Part 3). We can’t take a hashed value and get its plaintext value. (That’s what rainbow tables are for!)   Great – so we have 3131.  Now what do we do?

Here we meet the really clever part of rainbow tables.  Rainbow tables use something called reduction functions to “reduce” the hashed value (4 digits in our case) to a valid plaintext value (2 digits for us).  Reduction functions are really important, and are discussed in more detail in Part 4.  For now, let’s just learn enough about them to be able to walk through our examples.  A few important points:

  • A reduction function is NOT an inverse of a hash function. Hash functions don’t have an inverse.
  • But, a reduction function DOES consistently map a hashed value to some plaintext value.  But the plaintext value is meaningless. (Meaningless, but helpful for the rainbow table!)
  • For any given valid hash value (eg, 3131), the reduction function MUST generate a valid plaintext value.
  • Just like hashes have collisions, so do reduction functions. Because of this, rainbow tables use multiple reduction functions.  (More about this in Part 4.)

So, for our example, we apply our reduction function, R1, to the value we got from encrypting 68.  R1(3131) = 31.  Next, we hash 31:  H(31)=3790.  We pause, check if that is the value we started with (5520), notice that it is not, and repeat the steps.

That is, we apply our second reduction function, R2, to 3790.  R2(3790) = 37.  We hash 37, which results in H(37)=5520.  We pause, check if this is the value we started with (5520), and it is!  So we stop, knowing that 37 hashes into 5520.  So our plaintext is 37.

Boy, that was a lot of work, especially given that we had a match in our table!  Yes, that’s true.  Remember – we’re doing extra work to save space.

So let’s recap the algorithm we have seen so far:

1. Find the hashed value in the lookup table.
2. Take the plaintext value and hash it.
3. Does that hash match the hash we have?
   If so, stop. The value you just hashed is the value you're looking for.
4. If not, apply the reduction function to get a new plaintext value,
   and go back to step 2.

So what happens if the hashed value we have isn’t in the lookup table?  For example, what would we do if we started with the hashed value of  3626?  Step 1 in our algorithm obviously isn’t sufficient!

The solution is to apply the reduction function.  Since this is essentially walking backwards through the chains, we apply the last reduction function (R2).  What is R2(3626)?  Well, you don’t know (unless you’ve read Part 4), but that is okay.  Let R1 and R2 be “black boxes” for now and take my word for it – R2 (3626) is 36.  (Take a close look at what you’ve seen so far for R1 and R2 – you might be able to guess the algorithms.)  Hash that number, H(36)=4202, and try the algorithm again.  Looking back at the lookup table (not the full rainbow table), this time we find 4202.  We see that its corresponding value for p1 is 25.  Now we can go on to step 2: H(25)=2059.  Step3: is 2059 the number we’re looking for?  No, we looking for 3626, so on to step 4: R1(2059)=59.  Back to step 2: H(59)=3626.  Step 3: s 3626 the number we’re looking for?  Yes!  Therefore, 59 is its plaintext.

So, let’s rewrite the algorithm a little bit:

1. Find the hashed value in the lookup table.  If you find it, go to step 2.
  If not:
  1a. Starting with the last reduction function (e.g., R2), "reduce" the
      hashed value to get a new plaintext number. Every time you repeat
      step 1, you go to the next lowest reduction function (e.g., R2,
      then R1).
  1b. Hash the new plaintext number and repeat step 1 from he beginning
      with this new hash value.
2. Take the plaintext value and hash it.
3. Does that hash match the hash we have?
   If so, stop. The value you just hashed is the value you're looking for.
4. If not, apply the reduction function to get a new plaintext value, and
   go back to step 2.

Essentially, step 1 backs you up column-by-column in the rainbow table until you find a hash and can match a row.  Then, steps 2-4 move you forward through a specific row to obtain the value you need.  (And if you don’t find a value in these steps, then your rainbow table doesn’t have the information you’re looking for.)

Try it on your own, perhaps using 2554.

So, there you go – a gentle introduction to Rainbow Tables.  Hopefully this will help make other descriptions (such as those at wikipedia and kuliukas) a bit easier.  I encourage playing around with the spreadsheet and walking through the process with other sample values.

Rainbow Tables – Part 4 (Reduction Functions)

This is Part 4 of a five part series:

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions (you are here)
  5. Rainbow Tables and Chains
  1. Addendum

Reduction functions are at the heart of how rainbow tables work.

To understand reduction functions, lets look at a set of two-digit values that are “encrypted” into four-digit hashes.  For example, using the sample hashing algorithm described in Part 3, the number 10 is encrypted into 0823:

 p     h
10  0823

Now, imagine if we had a special function that could somehow map a 4 digit hash to a 2 digit number.  Because hashes are irreversible, the two digit number is not the original password.  But, it is a two digit number, which means it is a valid password.  For example,  a really simple reduction function could take the last two digits of a four digit number.  That would map the hash 0823 into the plaintext 23:

   h    p
0823   23

That’s all a reduction function is – a function that consistently maps a hashed value into a valid plaintext value. It is worth repeating that the reduction funtion is NOT the inverse of the hash.  In the example above, 0823 “reduces” to 23, but 23 does not hash into 0823 (23 hashes into 5603).

It is also important that the results of the reduction function be valid plaintext. This is easy to do in our samples where we’re dealing with two and four digit numbers. But let’s say you are dealing with a system that allows passwords to be only alphanumerics, no special characters.  If your reduction function results in characters like !,@,#, or $, then you’ll be saying !@#$ because you’ll get invalid plaintext!

In part 3, we looked at the fact that all encryption algorithms can have collisions.  This is also true for reduction functions.  In fact, the situation tends to be worse for reduction functions, because chances are that your maximum password size is much shorter than the size of your hashed values.  In our simple example, we’re mapping four digit numbers into two digit numbers.  Clearly, we’ll frequently collide!

The collisions are a real problem for rainbow tables.  One strategy to reduce (but not eliminate) their impact is to use multiple reduction functions.  In Part 5, we talk about R1 and R2.  In those samples, R1 is our function above – the last two digits.  R2 is equally simple – it just takes the first two digits.

Let’s go look at rainbow tables in Part 5!

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains (next)

Rainbow Tables – Part 3 (Simple Hashes and Collisions)

This is Part 3 of a five part series:

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions (you are here)
  4. Reduction Functions
  5. Rainbow Tables and Chains
  1. Addendum

Geoff Kuenning, a computer science professor at Harvey Mudd College, has a great web page about hashes as a part of one of his classes.  Let’s look at two of the three simple hash functions he presents (we’ll use one of these for our rainbow table).

If you haven’t looked at the spreadsheet yet, now is probably a good time to do so.  It is broken into tabbed pages. The first four tabs are dedicated to Kuenning’s three hash functions.

When we look at the hash functions, one of the things we want to consider is the number of collisions that occur.  Part of the nature of hash functions is  that multiple input values can produce the same output result.  When this happens, it is known as a collision.  (If you remember high-school trigonometry, this is like the sin() and cos() functions.  sin(0) and sin(180 degrees) both equal zero.)

Note: My nomenclature differs slightly from Kuenning’s.  I use H() to indicate the hash function, h to indicate the hashed value, and p to indicated the unencrypted password (aka plaintext).  On this page only, I’ll make p and h orange so you remember that we’re starting with one and calculating the other.

Hash Function 1: The Division Method

The division method simply uses the modulo function:

     h = p mod m

where m is a prime number.  (m should be far from a power of 2, but for our purposes it doesn’t matter.)

here is a table of hash values for plaintext values from 0-19, with a m value of 13:

    p  h      p  h      p  h      p  h
    0  0      5  5     10 10     15  2
    1  1      6  6     11 11     16  3
    2  2      7  7     12 12     17  4
    3  3      8  8     13  0     18  5
    4  4      9  9     14  1     19  6

Lousy for encryption, don’t you agree?  But, it is a hash – you can’t tell what the plaintext value is if you only know the result.  And, there are collisions.

I’m going to skip the next function that Kuenning presents, the Knuth variant on division. It is easy to understand, and is included in the spreadsheet if you want to see how it compares with the other two.

Hash Function 2: The Multiplication Method

This hash function is a little more involved, but is still simple enough to implement easily in a spreadsheet.  Kuenning conveniently breaks it into three calculations.  This makes it easier to understand (and easier to program).

     s = p*A
     x = fractional part of s
     h = floor(m*x)

Below is a table of the results of this hash applied to the numbers 0-19, with A set to 6.213335, m set to Kuenning’s recommended value of (SQRT(5) – 1)/2, and x truncated to 4 digits (if s = 1.234567, x is then 2345)

p:  s:          x:     Hash:           p:  s:          x:     Hash:
0   0              0    0000           10  62.13335    1333    0823
1   6.213335    2133    1318           11  68.346685   3466    2142
2   12.42667    4266    2636           12  74.56002    5600    3460
3   18.640005   6400    3955           13  80.773355   7733    4779
4   24.85334    8533    5273           14  86.98669    9866    6097
5   31.066675    666    0411           15  93.200025   2000    1236
6   37.28001    2800    1730           16  99.41336    4133    2554
7   43.493345   4933    3048           17  105.626695  6266    3872
8   49.70668    7066    4367           18  111.84003   8400    5191
9   55.920015   9200    5685           19  118.053365   533    0329

A few points are worth noting:

  • We don’t see any collisions here.  However, the spreadsheet shows that there are collisions.
  • No matter what integer we use as the source, the resulting hash will always be a fixed length: 4 digits.  In other words, the hash will always be less than or equal to 9999.  (Actually, it will be less than 6180).
  • Because the hash is always an integer between 0 and 6180, we know that if we have a set of over 6180 numbers, we are guaranteed to have collisions.
  • Zero does not hash particularly well with this algorithm.

This is a great hash for creating a simple rainbow table to learn how they work.  Before we do so, however, let’s look at the notion of reduction functions in Part 4.

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions (next)
  5. Rainbow Tables and Chains

Rainbow Tables – Part 2 (Encryption and Hashes)

This is Part 2 of a five part series:

  1. Introduction
  2. Encryption and Hashes (you are here)
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains
  1. Addendum

As I mentioned in the first post of this series, rainbow tables are used to find a password if you know the encrypted password.

Passwords typically are (or should be) encrypted with a one-way encryption algorithm. This type of encryption is known as a hash.  With a hash, there is no algorithm that can be applied to the encrypted password to determine the unencrypted password.  There are only two ways to determine the unencrypted password:

  • keep trying passwords until you find the right one (brute force)
  • in advance, create a list of passwords and their encrypted results.  This is known as a lookup table.  Such a table can be huge, but is very simple to use and is fast.  Rainbow tables are a compromise.  They consume less space, but require more processing.  Compared with brute force, they still can be very fast (unless the password is poor and can be guessed immediately).

To understand rainbow tables, you need to be comfortable with hashes.  Most experienced computer users are at least somewhat familiar with MD-5 hashes, which are often used as a checksum to validate that a downloaded file was not corrupted in-flight. MD-5 is known to be vulnerable, but it makes a fine checksum.  SHA-1 is a more secure hash.

The rainbow table explanations I cited before (wikipedia and kuliukas) both use MD-5 for their example hashes.  While MD-5 is well-known and is easily available, it is difficult to calculate an MD-5 hash within Excel.  Because Excel makes a great platform for experimenting with data and tables to learn a concept, I found some simpler hash functions that I’ll use for the examples here.  These hashes would be terrible for real encryption, but they work well for creating a simple, understandable rainbow table in Excel.  We’ll look at those hash algorithms in Part 3.

If you’re not comfortable with hashes, these simple algorithms will also provide a gentle introduction to the concept.

The examples in the other articles use character strings for their passwords to hash.  To keep things simple, my examples will stick to encrypting numbers between 0 and 99.  After all, in a computer, characters are all represented by numbers anyway, so we’re just skipping the step of translating a character into ASCII or Unicode.

Let’s go look at these simple hashes in Part 3.

  1. Introduction
  2. Encryption and Hashes
  3. Simple Hashes and Collisions (next)
  4. Reduction Functions
  5. Rainbow Tables and Chains

Rainbow Tables – Part 1 (Introduction)

This is Part 1 of a five part series:

  1. Introduction (you are here)
  2. Encryption and Hashes
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains
  1. Addendum

In January, I took the SANS Security Essentials (401) class at SANS West.  In the class, we briefly covered the concept of rainbow tables. Rainbow tables are (superficially) just password lookup tables: if you know the encrypted  password, you can look it up in the table and get the unencrypted password.  (The word ‘superficially’  there is critical – rainbow tables are far more interesting than simple lookup tables.)

One of the evening sessions (after the boot camp session) featured Ed Skoudis talking about penetration testing.  He also talked about rainbow tables.  What really caught my attention was his discussion of chains to vastly reduce the size of the tables.  The chains provide a clever way to trade off table size at the cost of some processing time.  I just had to understand the details, and I didn’t have the patience to wait until some day in the future when I might take Ed’s Pen Testing class.  I cornered Ed at the bar on the last night, asked how he would recommend learning them, and he pointed me to Wikipedia.

The wikipedia article is great, but a bit tough to read for someone new to the concept.  Fortunately, the article refers to another article, How Rainbow Tables Work, which is a bit easier.  It also is a good article, and the two were all I needed to help me build some samples of my own in order to really understand the concepts.

In this series of posts, I will attempt to provide an even easier introduction to rainbow tables.  I hope that the posts will help you understand the other articles, and appreciate the beauty of the approach.

I’ve broken the discussion into six parts, so that you can skip any bits that are already familiar to you.  This introduction is Part 1. Part 2 briefly talks about encryption and introduces the concept of hashes. Part 3 looks at some really simple hashing algorithms, including the one used for the examples here. Part 4 goes into reduction functions.  Finally, Part 5 explains rainbow tables and chains.  If you understand that password encryption uses hashes and are familar with common hashes like MD5 and SHA-1, you can probably jump right to Part 5, and then read the other parts if you want to fill in any gaps.

These posts contain simple examples that you can work through yourself with a spreadsheet.  I have built one for you, which you can view using Google Docs or download to Excel.  I encourage you to play with the values in the spreadsheet  if you really want to get a deep understanding of how each part works.

If you’ve never used Google Docs before, it has the equivalent of Excel’s tabs.  At the bottom, you’ll see links that will take you to the different pages.  You also can select File – Export – .xls to export it into an Excel Spreadsheet, and you’ll find all the tabs there.

Enjoy!

  1. Introduction
  2. Encryption and Hashes (next)
  3. Simple Hashes and Collisions
  4. Reduction Functions
  5. Rainbow Tables and Chains