Friday, April 19, 2019

Vault and Consul as a personal credential store


The combination of Vault and Consul has been proclaimed to be the answer to every secrets management. In core production environments, consul on its own has been used as a KV store, service discovery and even a backend datastore. Vault on its own is more of a secret management system that can be used with different backends of which consul is one of them.

A production environment can combine these two tools (vault as a secure store for passwords, token, certificates, consul as its backend store) for a complete secret management solution.

If this can be used in production, i thought i could adapt this as my own personal password manager and i intend to show how i achieved that in this post.

I have got a repo in GitHub with all you will need for this set up.

https://github.com/sksegha/vault-consul-docker

Here is screen recording



The repo creates 2 docker containers - One vault and one consul. Vault is the actual secret management tool  while consul is the backend storage. 

The repo also contains a script that helps set up the "cluster"; outputting the encryption keys to a keys.txt file. The consul data is also saved in the data directory. As long as this data directory exist then your secrets are safe.

If you decide that you want to blow away the installation + data, the cleanup script will do that for you.



Saturday, April 13, 2019

Grokking Bitbucket Logs

Bitbucket has a non-standard log format so many collectors would not be able to poll its logs into a time-series database like influxdb.

This is where Grok patterning comes in.

But before I show you the grok pattern for bitbucket logs, i will like to show you the difference between the customary nginx log format and bitbucket logs.

Bitbucket has a couple of logs, atlassian-bitbucket-access.log, atlassian-bitbucket.log, atlassian-bitbucket-audit.log and couple of others; but for the sake of this post we will compare nginx's access.log and atlassian's atlassian-bitbucket-access.log.

This is assuming you are using nginx as a load balancer for your bitbucket server installation, either for proxy redirection or some form of TLS termination. Whatever your reason is, nginx will front your service and hand over communication to the bitbucket service running on the backend. So we have 2 access points, one  - Nginx, the other Atlassian bitbucket Apache.

Here are some excerpts

NGINX Logs

172.16.68.113 - - [13/Apr/2019:11:43:24 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 401 5 "-" "git/2.17.1"
172.16.68.113 - user [13/Apr/2019:11:43:24 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 200 2656 "-" "git/2.17.1"
172.16.68.113 - - [13/Apr/2019:11:43:26 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 401 5 "-" "git/2.17.1"
172.16.68.113 - user [13/Apr/2019:11:43:26 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 200 2656 "-" "git/2.17.1"
172.16.68.133 - - [13/Apr/2019:11:43:31 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 401 5 "-" "git/2.17.1"
172.16.68.133 - user [13/Apr/2019:11:43:31 -0500] "GET /scm/dt/repo.git/info/refs?service=git-upload-pack HTTP/1.1" 200 2656 "-" "git/2.17.1"

Bitbucket Logs

172.16.68.113,127.0.0.1 | https | i@1Q4STNAx702x1743109x3 | - | 2019-04-13 11:42:49,034 | "GET /scm/dt/repo.git/info/refs HTTP/1.0" | "" "git/2.17.1" | - | - | - | - | - | - |
172.16.68.113,127.0.0.1 | https | o@1Q4STNAx702x1743109x3 | - | 2019-04-13 11:42:49,036 | "GET /scm/dt/repo.git/info/refs HTTP/1.0" | "" "git/2.17.1" | 401 | 0 | 0 | - | 2 | - |
172.16.68.113,0:0:0:0:0:0:0:1 | https | i@1Q4STNAx702x1743110x3 | - | 2019-04-13 11:42:49,038 | "GET /scm/dt/repo.git/info/refs HTTP/1.0" | "" "git/2.17.1" | - | - | - | - | - | - |
172.16.68.113,0:0:0:0:0:0:0:1 | https | o@1Q4STNAx702x1743110x3 | cicd | 2019-04-13 11:42:49,127 | "GET /scm/dt/repo.git/info/refs HTTP/1.0" | "" "git/2.17.1" | 200 | 0 | 2644 | cache:hit, refs | 89 | - |

These are log entries from the same session from both the frontend NGINX and backend BITBUCKET process.

If you want to read more on interpreting atlassian access logs, you can this read up here and if you wonder why a session is giving so much 401s, read up here

Collecting NGINX logs is easy enough, if you run a Telegraf agent on the host, you can configure telegraf with the input below to monitor the NGINX process and parse its access logs


[[inputs.nginx]]
  ## An array of Nginx stub_status URI to gather stats.
  urls = ["https://localhost:80/nginx_status"]

# # Stream and parse log file(s).
[[inputs.logparser]]
  files = ["/var/log/nginx/access.log"]
  ## Read file from beginning.
  from_beginning = false
  [inputs.logparser.grok]
    patterns = ["%{COMBINED_LOG_FORMAT}"]
    measurement = "nginx_access_log"
However, bitbucket logs do not follow the conventional combined log format as we see above, we need some conversion of these log strings to what influxdb might be able to use.

This is where grok patterns come in.

The Grok pattern for bitbucket based on the log strings above could look like this

%{IP:clientIP},%{IP:localIP} \| %{WORD:protocol:tag} \| %{DATA:requestID} \| %{USERNAME:username} \| %{TIMESTAMP_ISO8601:timestamp} \| "%{DATA:action} %{DATA:resource} %{DATA:http_version}" \| "" "%{DATA:request_details}" \| %{NUMBER:resp_code} \| %{NUMBER:bytes_read} \| %{NUMBER:bytes_written} \| %{DATA:labels} \| %{NUMBER:resp_time} \| %{DATA:session_id} \|

I have tried to follow this format

%{<capture_syntax>[:<semantic_name>][:<modifier>]}  (the almighty Grok format)

The semantic_name for each capture syntax closely follow atlassian's recommendation and whats obtainable in the combined_log format. This is to allow for comparison between NGINX logs and Atlassian logs.

Creating an input block to collect these logs should be pretty easy from here

Add this block to telegraf config

[[inputs.logparser]]
  files = ["/var/atlassian/application-data/stash/log/atlassian-bitbucket-access.log"]
  from_beginning = false
  [inputs.logparser.grok]
   patterns = ["%{HTTP}"]
   measurement = "bitbucket_access_log"
   custom_patterns = '''
     HTTP %{IP:clientIP},%{IP:localIP} \| %{WORD:protocol:tag} \| %{DATA:requestID} \| %{USERNAME:username} \| %{TIMESTAMP_ISO8601:timestamp} \| "%{DATA:action} %{DATA:resource} %{DATA:http_version}" \| "" "%{DATA:request_details}" \| %{NUMBER:resp_code} \| %{NUMBER:bytes_read} \| %{NUMBER:bytes_written} \| %{DATA:labels} \| %{NUMBER:resp_time} \| %{DATA:session_id} \|
   '''

You could also collect atlassian audit logs with this block in your telegraf configuration

[[inputs.logparser]]
  files = ["/var/atlassian/application-data/stash/log/audit/atlassian-bitbucket-audit.log"]
  from_beginning = false
  [inputs.logparser.grok]
   patterns = ["%{AUDIT_LOG}"]
   measurement = "stash_audit_log"
   custom_patterns = '''
     AUDIT_LOG %{IP:clientIP},%{IP:local} \| %{WORD:eventType} \| %{USERNAME:user:tag} \| %{INT:msSinceJan11970} \| %{USERNAME:eventDetails} \| \{\"authentication-method\":\"%{WORD:authenticationMethod}\"\,\"error\"\:\"%{DATA:authError}\"\} \| %{DATA:requestID} \| %{DATA:sessionID}
   '''

Friday, April 12, 2019

Bitbucket and TIG (Telegraf InfluxDB and Grafana)



Bitbucket is our Git repository management solution designed for professional teams. It gives you a central place to manage git repositories, collaborate on your source code and guide you through the development flow. (ref: https://confluence.atlassian.com/confeval/development-tools-evaluator-resources/bitbucket/bitbucket-what-is-bitbucket)

Other competing tools used for the same purpose are Github, Gitlab and Mercurial.

It is basically a combination of a Git Server and a web interface written in Java built with Apache. You also have the option of using a local postgres installation or an external one. Git also needs to be installed on the server.

But rather than talking about bitbucket, i really just wanted to talk about gathering operational stats on performance of bitbucket installations. One thing i did run into was the scarcity of public information about how best to collect these stats but after many trial and error, i found an approach that actually works.

Since bitbucket is basically a java application, as part of other system/host level metrics; i needed to collect JVM metrics as well. In this case, here is list of metrics we will be hoping to collect


  • System/ Host Metrics - cpu, disk, diskio, kernel, memory, network, network stats etc.
  • JVM metrics - java memory, java class loading, java threading etc.
  • nginx stats - installations could have a frontend loadbalancer like nginx. Mostly just connection metrics.
  • postgres - DB performance metrics/stats.
So how do we collect all of this information?

Telegraf
Telegraf is a plugin-driven server agent for collecting and reporting metrics. It also allows you define where you want to send these metrics to and in this case we will be sending all to influxdb.

So assuming metrics is collected, what do i do with it?

Influxdb 
Influxdb is a time-series database.

Ok metrics is collected by telegraf, warehoused by influxdb; how do i see what this all looks like?

Grafana
Grafana is a data visualization and monitoring tool which is also capable of sending notification about alert thresholds.

So what should i do with all these

1. Install Telegraf on the bitbucket host
Refer to documentation here

2. SetUp destination Influxdb server 
Refer to documentation here
Also for the Influxdb, you will need to create a database, also create a user with write permissions for that database 
Refer to documentation here

3. Set up a Grafana server 
Refer to documentation here

The 3 steps above have enough documentation out there if you need extra help, but one part that seems clouded in mystery is enabling Jolokia plugin for bitbucket. As mentioned earlier bitbucket itself is nothing more than a java application built with Apache in most cases running behind a load balancer like Nginx. If you do use a monitoring tool like prometheus, you could easily poll metrics from the JVM using by enabling the Prometheus plugin and have your prometheus server do the rest of the job. However with telegraf, the story is different. Telegraf can not poll data from the JVM directly hence it needs an agent like Jolokia to attach itself to the running JVM ( i think this is the term called symbiosis in biology) and then collect such data. 

The Jolokia plugin can be enabled from the bitbucket UI, however you also need to enable JMX monitoring in bitbucket to actually make it all come together. 

This is a documentation from Atlassian ( it might be old too, so some steps are cumbersome and might be unnecessary)

As far as enabling JMX is concerned, here is all you need 




























So if this is done correctly, then the process for bitbucket should look like this



1
atlbitb+  8966  176 14.5 19871572 7155652 ?    Sl   14:39 615:44 /opt/atlassian/bitbucket/5.7.1/jre/bin/java -classpath /opt/atlassian/bitbucket/5.7.1/app -Datlassian.standalone=BITBUCKET -Dbitbucket.home=/var/atlassian/application-data/stash -Dbitbucket.install=/opt/atlassian/bitbucket/5.7.1 -Dcom.sun.management.jmxremote.port=3333 -Dcom.sun.management.jmxremote.rmi.port=45625 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.password.file=/var/atlassian/application-data/stash/shared/config/jmx.access -Xms4g -Xmx4g -XX:+UseG1GC -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/var/atlassian/application-data/stash/tmp -Djava.library.path=/opt/atlassian/bitbucket/5.7.1/lib/native;/var/atlassian/application-data/stash/lib/native -Xloggc:/var/atlassian/application-data/stash/logs/2019-04-12_14-39-51-gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=5M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/atlassian/application-data/stash/logs/heap.log com.atlassian.bitbucket.internal.launcher.BitbucketServerLauncher start
That excerpt does show that we have a couple flags turned on for bitbucket which also include turning on JMX monitoring.

So here is where it gets interesting.

Telegraf configuration is determined by a file in /etc/telegraf/ named telegraf.conf. Lets go ahead and configure the said file to collect all the metrics we are interested in.

Edit telegraf.conf with your favorite editor (vi, vim or nano), it should end up looking like this

telegraf.conf

There are bits of the configuration above i would like to explain in a little more detail, the jolokia piece of it and also the log parsing bit of it. Maybe i will make some other post about matching log lines in bitbucket and then exporting said logs to a time series database like influxdb.

Anyways, now we have telegraf configured. One the telegraf agent on the box is restarted, series should start polling in the influxdb.

On the influxdb



> show measurements
name: measurements
name
----
bitbucket.atlassian
bitbucket.jvm_class_loading
bitbucket.jvm_memory
bitbucket.jvm_operatingsystem
bitbucket.jvm_runtime
bitbucket.jvm_thread
bitbucket.thread_pools
bitbucket.webhooks
bitbucket_access_log
cpu
disk
diskio
java_class_loading
java_garbage_collector
java_last_garbage_collection
java_memory
java_memory_pool
java_runtime
java_threading
kernel
linux_sysctl_fs
mem
net
netstat
nginx
nginx_access_log
postgresql
processes
stash_access_log
stash_audit_log
swap
system
This does show that we are in business. From this point on, setting up visualization for the data in influx should come easy.

Here is a visualization in grafana











each panel consist of queries into the series in influxdb. e.g the panel JVM Uptime if from this query


SELECT last("Uptime") FROM "java_runtime" WHERE ("host" =~ /^$host$/) AND $timeFilter GROUP BY time($interval) fill(null)

I apologize if this post skipped some details. Hopefully i can answer some questions.

Thursday, May 3, 2018

Setting up Python Environments for Ansible

There is always a need for a different version of Ansible that what is installed on ur running OS. Initially to avoid this mind loops, i have run Ansible from docker containers. I basically just build a docker image with my preferred version of Ansible and other packages i might need and run my Ansible playbooks from within the docker container. 

While Docker is great tool, there are some times that managing the various docker images and container can become a pain. One other method that can be used is creating virtual environments for specific versions of Ansible.

The steps would typically involve


  • installing pip
  • install python virtualenv module
  • install you preferred sensible version 
  • create Python virtual environment.
All of these steps have been combined in the script below


#!/bin/bash

ANSIBLE_VERSIONS=( "2.2.0.0" "2.2.1.0" "2.2.2.0" "2.2.3.0" "2.3.0.0" "2.3.1.0" "2.4.0.0")

VIRTUALENV_PATH="/home/$USER"

if [[ $(uname) == "Linux" ]]; then
  # Ubuntu
  if [ -f /etc/debian_version ]; then
    codename="$(lsb_release -c | awk '{print $2}')"
    sudo apt-get update
    sudo apt-get -y install build-essential libffi-dev libssl-dev python-dev \
    python-minimal python-pip python-setuptools python-virtualenv
  fi

  # AMAZON LINUX
  if [ -f /etc/system-release ]; then
    codename="$(cat /etc/system-release | awk '{print $1}')"
    sudo yum -y install gmp-devel libffi-devel openssl-devel python-crypto \
    python-devel python-pip python-setuptools python-virtualenv \
    sudo yum -y group install "Development Tools"
  fi
fi

  # RHEL
  if [ -f /etc/redhat-release ]; then
    codename="$(cat /etc/redhat-release | awk '{print $1}')"
    if [[ $codename == "Fedora" ]]; then
      sudo dnf -y install gmp-devel libffi-devel openssl-devel python-crypto \
      python-devel python-dnf python-pip python-setuptools python-virtualenv \
      redhat-rpm-config && \
      sudo dnf -y group install "C Development Tools and Libraries"
      elif [[ $codename == "CentOS" ]]; then
      sudo yum -y install gmp-devel libffi-devel openssl-devel python-crypto \
      python-devel python-pip python-setuptools python-virtualenv \
      redhat-rpm-config && \
      sudo yum -y group install "Development Tools"
    fi
  fi


if  ! python -c "import virtualenv" &> /dev/null;
then
    sudo pip install virtualenv;
    # Allow users to use virtualenv in their homedirs
    sudo chmod -R o+rX /usr/local/lib/python2.7/dist-packages/virtualenv*
fi


# Setup Ansible Virtual Environments
for ANSVER in "${ANSIBLE_VERSIONS[@]}"
do
  if [ ! -d "$VIRTUALENV_PATH/ansible-$ANSVER" ]; then
    virtualenv $VIRTUALENV_PATH/ansible-$ANSVER
    source $VIRTUALENV_PATH/ansible-$ANSVER/bin/activate
    pip install ansible==$ANSVER ansible-lint
    deactivate
  fi
done

printf "\nRun this to activate virtualenv:\n\n\tsource ~/ansible-(version)/bin/activate\n\n"

I hope this helps

Wednesday, April 18, 2018

Scripting Consul Backups

Consul is a hashicorp tool for discovering and configuring services in your infrastructure. Among its many uses is Key Value Store. As a key value store, it can be used to dynamically store passwords, ssh keys, encryption keys.

You can read more on consul on the hashicorp website.

The aim of this write up is to show how you could backup your consul data in S3 if you do not have an enterprise version which would normally come with a consul backup agent.

Here is a sample of my script


#!/bin/bash

BAK_DEST=/tmp/consul/backup

#Polling associated AWS variables

REGION=$(/usr/bin/curl --silent http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)

INSTANCE_ID=$(/usr/bin/curl --silent http://169.254.169.254/latest/meta-data/instance-id)

#S3 bucket is published in AWS Parameter store. You might decide to hardcode this.
S3_BUCKET=$(/usr/local/bin/aws ssm get-parameter --name "/keystore/$REGION/consul_s3_destination" --region $REGION | jq -r .Parameter.Value)

#hostname is a tag on the EC2 instance so my server can easily poll that information.
HOSTNAME=$(/usr/local/bin/aws ec2 describe-tags --region=$REGION --filter "Name=resource-id,Values=$INSTANCE_ID" "Name=key, Values=Name" --output=text | cut -f5)


#number of days to keep archives
KEEP_DAYS=2

#script variables
BAK_DATE=`date +%F`
BAK_DATETIME=`date +%F-%H%M`
BAK_FOLDER=${BAK_DEST}
BAK_DB=${BAK_DEST}/${HOSTNAME}-${BAK_DATETIME}

#CREATE folder where backup database is to be place
#echo 'Creating consul back up ' ${BAK_FOLDER}
#mkdir ${BAK_FOLDER}

#PERFORM Consul backup
echo 'Creating archive file ' ${BAK_DB}'.tar.gz Please wait ......'
/usr/local/bin/consul snapshot save ${BAK_DB}.snap
tar czPf ${BAK_DB}.tar.gz ${BAK_DB}.snap


#Moving backups to AWS. This uses AWS CLI to copy snapshots to S3
echo 'Copying consul backups to S3'
/usr/local/bin/aws s3 cp ${BAK_DB}.snap s3://${S3_BUCKET}/dailybackup/${HOSTNAME}-${BAK_DATETIME}.snap


# DELETE FILES OLDER THAN 2 days
echo 'Deleting backup older than '${KEEP_DAYS}' days'
find ${BAK_FOLDER} -type f -mtime +${KEEP_DAYS} -name '*.gz' -execdir rm -- {} \;
find ${BAK_FOLDER} -type f -mtime +${KEEP_DAYS} -name '*.snap' -execdir rm -- {} \;


A few items of interest; the script works on the premise that your consul server has the permission to read from AWS parameter store; also write to S3. Servers should also have AWS CLI installed.

I also created a local destination for consul backups on the host /tmp/consul/backup

The hostname is a tag in AWS and hence the server must also be able to describe ec2 instances to pull it tag information. 

The script uses the command "consul snapshot save" to take a snapshot and save in the local destination followed by using was AWS CLI to copy the snapshot to a predefined S3 destination. The S3 destination is published in parameter store which the EC2 instance would also grab. 

Tuesday, April 17, 2018

Buiding a Vault-Consul Cluster in AWS

Vault and Consul are hashicorp tools that can be powerfully combined to store key values. These values can very from passwords, encryption keys, ssh keys etc.

You can read up on consul HERE

You can read up on vault HERE

One of my biggest challenges at getting this setup right was identifying the right architecture while providing the right combination of resiliency and HA.

Vault can be used all by itself, installed to use a file backend. It will write the keys to this path specified in the config. The downside to this is, when you lose the host... you lose all your keys. Also a file backend does not provide for High Availability.

Some other backend include etcd, mongoDB, dynamoDB, PostgreSQL. Some of these backends provide HA while some do not.

I decided to go with a consul backend for the following reasons.


  • Consul is a Hashicorp product just like Vault. On house to solve all my problems. This might be a good choice if you were to be buying enterprise support since in most cases hashicorp offers a Vault and Consul Package of support. 
  • HA persistent data. Just like dynamoDB, etcd, google cloud storage; consul storage backends provide HA.
Technical Implementation

Number of servers - 5
Number of servers running CONSUL - 3 servers running consul server agent.
Number of server running VAULT - 2 servers running consul client agent, and then vault agent. 

In my case, my environment is deployed in AWS but bare in mind the concepts are similar even if you were deploying this in a traditional datacenter or another cloud provider environment.


If you need to know how to configure consul, here is a good documentation from digital ocean.

One of the important highlight is the configuration of the consul servers in this set up. 


{
  "node_name" : "consul-1",
  "bind_addr": "10.43.51.118",
  "advertise_addr": "10.43.51.118",
  "server" : true,
  "data_dir": "/var/consul",
  "log_level" : "INFO",
  "client_addr" : "0.0.0.0",
  "bootstrap_expect": 3,
  "disable_remote_exec": true,
  "disable_update_check": true,
  "leave_on_terminate": true,
  "retry_join": [
     "provider=aws tag_key=consul-role tag_value=server"
  ]
}


node-name = hostname it registers in the quorum with
bind_addr/advertise_addr = IP address of node
bootstrap_expect = Number of servers to expect to form quorum
retry_join = This is an interesting concept in automation. They are basically tags when running this setup in AWS. It ensures that all servers that come up in AWS with the tag attempt to form a quorum. 

"retry-join accepts a unified interface using the go-discover library for doing automatic cluster joining using cloud metadata. To use retry-join with a supported cloud provider, specify the configuration on the command line or configuration file as a key=value key=value ... string."

For the consul clients, the configuration will look like this 


{
  "node_name" : "vault-0",
  "bind_addr": "10.43.51.67",
  "advertise_addr": "10.43.51.67",
  "server" : false,
  "data_dir": "/var/consul",
  "log_level" : "INFO",
  "client_addr" : "0.0.0.0",
  "disable_remote_exec": true,
  "disable_update_check": true,
  "leave_on_terminate": true,
  "retry_join": [
     "provider=aws tag_key=consul-role tag_value=server"
  ]
}

note that the "server" item is set to false. This is because the consul running on the vault nodes are only clients so technically do not hold your keys but act as a communication forwarder to the quorum of consul servers. 

The vault config on both vault nodes however would look like this;

backend "consul" {
address = "127.0.0.1:8500"
path = "vault/"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}

as you will see above, it uses consul as backend; sending traffic to local host port 8500. (which is the consul client running on this host)

For a production environment, you might want to use a tool like terraform to deploy your infrastructure. I would build custom AMIs for consul and vault and any other additional configuration will be deployed with an sensible playbook on startup. The vault and consul configuration were baked into the packer AMI; while the retry_join values were set using terraform. 

Here is an excerpt of my terraform config 

resource "aws_instance" "consul" {
  count                      = "${var.consul_count}"
  ami                        = "${data.aws_ami.consul_ami.id}"
  instance_type              = "${var.consul_instance_type}"
  key_name                   = "${var.ssh_keyname}"
  subnet_id                  = "${element(local.subnet_ids, count.index)}"
  iam_instance_profile       = "${aws_iam_instance_profile.ec2.id}"
  vpc_security_group_ids     = ["${module.consul.this_security_group_id}",   "${module.ec2_utility.this_security_group_id}"]
  
 root_block_device {
               volume_type = "gp2"
               volume_size = 20
              }
  tags = "${merge (local.tags_server, map ("Name", "consul-${count.index}"))}"
 }

resource "aws_instance" "vault" {
  count                      = "${var.vault_count}"
  ami                        = "${data.aws_ami.vault_ami.id}"
  instance_type              = "${var.vault_instance_type}"
  key_name                   = "${var.ssh_keyname}"
  subnet_id                  = "${element(local.subnet_ids, count.index)}"
  iam_instance_profile       = "${aws_iam_instance_profile.ec2.id}"
  vpc_security_group_ids     = ["${module.vault.this_security_group_id}", "${module.ec2_utility.this_security_group_id}", "${module.consul.this_security_group_id}"]

  lifecycle {
    ignore_changes = ["ebs_block_device"]
  }

  root_block_device {
             volume_type = "gp2"
             volume_size = 20
             }
 tags                        = "${merge (local.tags_client, map ("Name", "vault-${count.index}"))}"
}
NOTE: you will need to be very conversant with terraform to understand what i have above since some parts of the config are missing. 

As part of an effort to provide some resiliency for the environment; i have a lambda function that takes a snapshot of the consul EBS volumes once a day. I also have a cronjob that runs a script that would use the consul application to backup consul data. i.e. consul snapshot save.
You might not need all this if you run consul enterprise because i understand it has a consul enterprise has a backup agent that comes with it. 

Maybe one day i will write something about terraform and packer. I find them impressively useful for automation and some form of configuration management. 

Let me know if you find this useful and  if you have any other questions. 



Tuesday, January 30, 2018

Automating backups of your AWS EBS volumes using Lambda and Cloudwatch

I found this write up below very insightful when you try to create schedule snapshots of EBS volumes in AWS.

https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/TakeScheduledSnapshot.html

The problem i found however was that the Snapshots created were without description and it provided no means for clean up.

A better approach will be to use lambda function to make API calls to take EBS of these volumes and then another Lambda function to clean up older snapshots. This is what i ended up doing.

In Summary, this is what we are trying to achieve

- Filter information about the volumes we want to take snapshots of
- Take the snapshots of these volumes
- Label and tag these snapshots with names of their parent volume
- Delete Snapshots that past the specified retention period
- Write logs to cloud watch

IAM Role Assignment
First we need to create a IAM Role that has permission to do the following
- retrieve information about EC2 instances
- take snapshots of these EC2 instance
- Create Tags for the snapshots
- delete snapshots
- Make log entries to CloudWatch

In your AWS Management console, select IAM  > Roles  > Create Role
Under "AWS service"; select Lambda. Click on "Next: Permissions".
Skip the Next Page, we will create a policy later and attach to this Role.
Name you roles, and also a quick description.



In your AWS Management console, select IAM  > Policies > Create policy

This policy would include the tasks listed in the IAM role assignment; paste the JSON below in the JSON editor.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:*"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": "ec2:Describe*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateSnapshot",
                "ec2:DeleteSnapshot",
                "ec2:CreateTags",
                "ec2:ModifySnapshotAttribute",
                "ec2:ResetSnapshotAttribute"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

select the policy you just created and attach it to the role you created earlier 








Now that we have our permissions in place, we will go create out Lambda function

Select Lambda in the list of AWS services , click on "Create a Function"
Type in the name of your function, for runtime; select python 2.7.
for Role, select existing role and choose the IAM role you created earlier from the list









Click on create on function and the next screen takes you to the functions main page.
We will be using both library for the lambda function; paste the following in the function section.

import boto3
import collections
import datetime

def lambda_handler(event, context):
    ec2 = boto3.client('ec2')
    
    # Get list of regions
    regions = ec2.describe_regions().get('Regions',[] )

    # Iterate over regions
    for region in regions:
        print "Checking region %s " % region['RegionName']
        reg=region['RegionName']

        # Connect to region
        ec2 = boto3.client('ec2', region_name=reg)
    
        # Get all in-use volumes in all regions  
        result = ec2.describe_volumes( Filters=[{'Name': 'tag:service', 'Values': ['keypads']}])
        
        for volume in result['Volumes']:
            print "Backing up %s in %s" % (volume['VolumeId'], volume['AvailabilityZone'])
        
            # Create snapshot
            result = ec2.create_snapshot(VolumeId=volume['VolumeId'],Description='Created by Lambda backup function ebs_snapshot_consul')
        
            # Get snapshot resource 
            ec2resource = boto3.resource('ec2', region_name=reg)
            snapshot = ec2resource.Snapshot(result['SnapshotId'])
        
            volumename = 'N/A'
        
            # Find the following tags for volume if it exists
            if 'Tags' in volume:
                for tags in volume['Tags']:
                    if tags["Key"] == 'Name':
                        volumename = tags["Value"]
                    if tags["Key"] == 'service':
                        servicename = tags["Value"]
        
            # Add volume name to snapshot for easier identification
            snapshot.create_tags(Tags=[{'Key': 'Name','Value': volumename},{'Key': 'service','Value': servicename}])





The code above scans through all regions and filters volume with that are tagged with "service"=="keypads"; as seen below 

result = ec2.describe_volumes( Filters=[{'Name': 'tag:service', 'Values': ['encryption']}])

Also it tags the eventual snapshot with the name of the parent volume and parent service tag, as seen below 

# Find the tags for volume if it exists
  if 'Tags' in volume:
      for tags in volume['Tags']:
          if tags["Key"] == 'Name':
              volumename = tags["Value"]
          if tags["Key"] == 'service':
              servicename = tags["Value"]

Note that you can use other tags for filtering your volume, and you can leave out filters if you want to take a snapshot of all volumes.

For your function, adjust some basic settings

- set timeout value to 2mins ( enough time for your code to run)
- Memory set to 128MB

go ahead and save, then test your function 
        
Log output will look like this if the run was a success


Checking region ap-south-1 
Checking region eu-west-3 
Checking region eu-west-2 
Checking region eu-west-1 
Checking region ap-northeast-2 
Checking region ap-northeast-1 
Checking region sa-east-1 
Checking region ca-central-1 
Checking region ap-southeast-1 
Checking region ap-southeast-2 
Checking region eu-central-1 
Checking region us-east-1 
Checking region us-east-2 
Checking region us-west-1 
Checking region us-west-2 
END RequestId: d610f18d-05c9-11e8-a062-d72177d9ae3e
REPORT RequestId: d610f18d-05c9-11e8-a062-d72177d9ae3 Duration: 13258.99 ms Billed Duration: 13300 ms Memory Size: 128 MB Max Memory Used: 63 Mns().get('Regions',[] )
Now our snapshot creation function is complete. However, we need to create a schedule around our function; this is where triggers come into play. On the list of trigger on the left hand side; click on cloud watch events. 
Create a new rule, enter the rule name, enter rule description, choose the CRON expression. 







Click Add, and save your function.
And you are all done with snapshot creation. 
Now we need to create another function for clean up of older snapshots.

Follow the same process for creating Lambda function as above, here is what the lambda_function.py will look like 

import boto3
from botocore.exceptions import ClientError

from datetime import datetime,timedelta

def delete_snapshot(snapshot_id, reg):
    print "Deleting snapshot %s " % (snapshot_id)
    try:  
        ec2resource = boto3.resource('ec2', region_name=reg)
        snapshot = ec2resource.Snapshot(snapshot_id)
        snapshot.delete()
         except ClientError as e:
        print "Caught exception: %s" % e
        
    return
    
def lambda_handler(event, context):
    
    # Get current timestamp in UTC
    now = datetime.now()

    # AWS Account ID    
    account_id = '1234567890'
    
    # Define retention period in days
    retention_days = 3
    
    # Create EC2 client
    ec2 = boto3.client('ec2')
    
    # Get list of regions
    regions = ec2.describe_regions().get('Regions',[] )

    # Iterate over regions
    for region in regions:
        print "Checking region %s " % region['RegionName']
        reg=region['RegionName']
        
        # Connect to region
        ec2 = boto3.client('ec2', region_name=reg)
        
        # Lets grab all snapshot id's by Tags
        result = ec2.describe_snapshots( Filters=[{'Name': 'tag:service', 'Values': ['keystore']}] )
    
        for snapshot in result['Snapshots']:
            print "Checking snapshot %s which was created on %s" % (snapshot['SnapshotId'],snapshot['StartTime'])
       
            # Remove timezone info from snapshot in order for comparison to work below
            snapshot_time = snapshot['StartTime'].replace(tzinfo=None)
        
            # Subtract snapshot time from now returns a timedelta 
            # Check if the timedelta is greater than retention days
            if (now - snapshot_time) > timedelta(retention_days):
                print "Snapshot is older than configured retention of %d days" % (retention_days)
                delete_snapshot(snapshot['SnapshotId'], reg)
            else:
                print "Snapshot is newer than configured retention of %d days so we keep it"(retention_days)

When you test your lambda function, the logs will look like this 
      
   Deleting snapshot snap-010444196f3597b6b 
    Checking region us-east-2 
    Checking region us-west-1 
    Checking region us-west-2 
    END RequestId: 5630a0fb-05e4-11e8-8aa1-3d6c72fb82d4
    REPORT RequestId: 5630a0fb-05e4-11e8-8aa1-3d6c72fb82d4 Duration: 17820.37 ms Billed Duration: 17900 ms  Memory Size: 128 MB Max Memory Used: 67 





Monday, January 29, 2018

New Season, New Me

So my old posts were basically papers i wrote while studying for my Masters degree in information systems management.

I finally completed my studies sometime in 2013; which i immediately changed fields and started working IT full time. My previous role was more like a Hybrid business systems analyst. I was acting as an intermediary between core system/software developers and the front end users of these products.

I have always wanted a full blown IT position which was why i took IT certifications (self paces learning) while in my old role. I bagged a CCNA certification even without touching a physical switch or router.

I got a chance in 2013 to jump ship; it was a fresh start for me. 6 years after my first degree, i was starting a Job as a "Storage engineer". It was a fertile learning ground for me as i put everything i got into it. I bagged 8 various certifications in storage in a space of 2 years and i soon became a SNIA certified Storage Network Expert.

With all my storage/server/network knowledge; i moved again and this time into Cloud Engineering working with AWS Automation tools. I also have a CISSP certification as well.

I love what i do.

I remembered i had this blog a long time ago, so i have decided it might be a good platform to share some of the interesting things i have been working on.

Watch out for more cool stuff.


Friday, September 20, 2013

Cost Volume Profit Analysis


Managers are required to make and meet future expectations of their business, which include revenues, cost and profit to help them plan and monitor operation.  Cost Volume profit analysis is done to identify levels of operating activity needed to avoid losses, achieve target level of profits, make future operational plans and monitor performance. It investigates changes in profits in response to changes in sales volume, cost and prices. Cost Volume Profit analysis is carried out to determine;
  • Products and service to emphasize in a business
  • Required sales volume to meet profit level targeted
  • Revenue level to avoid loss
  • Necessity to increase fixed costs
  • Acceptable level of risks by the business

The process of CVP analysis however starts from the basic profit equation

Profit = Total Revenue – Total cost

From the cost behavior, we establish that cost can be divided into fixed and variable cost; fixed cost remaining unchanged over a relevant range regardless of increase in activity; variable cost changes with the level of activity, so that

Profit = Total Rev – Total Vc – Total Fc
Per unit of item;
Profit = (P – V) Q – F
By this evaluation, we obtain a target profit, which a business would stay afloat. All other items can then be obtained.
The Contribution Margin Analysis seeks to identify the effect of volume on profit. It tells the manager how much revenue per unit of item sold can be applied toward a fixed cost s the excess after meeting fixed cost requirements is profit.
CM = P – V
Now that we have obtained the profitable levels of activity for the business, managers might also be interested in knowing their limits as regards dropping levels of activity in quantity and also in quality. In knowing such limits, there exist a certain point where the level of activity covers all fixed cost and variable costs (at which point, profits begin to rise). This point is called the break-even point.

Break Even Analysis provides an avenue to compare the expected or planned volume of activity with the break even point and so make a judgment about risk (Atill and McLaney, 2011). When a business fails to reach the BEP, steps must be taken to remedy the problem such as increase in sales, reduction in cost. These 2 items affect the BEP hence are important items in break-even analysis.

BEP = Fixed cost / (Sales Rev – Variable cost)

In summary, CVP analysis considers only unit level activity cost drivers that provides a framework for discussing planning issues. To enhance its usefulness, the CVP relationships are plotted on graphs that classify cost according to behavior (fixed or variable) and highlight the contribution margin from the break-even point as it moves forward to cover fixed costs. In developing multilevel contribution income statements, it is important to remember that cost classification schemes should be designed to fit the organization and users.         (Pandey and Bandyopadhyaya, nd). When the concept of cost volume profit analysis is used in banks, the first major challenge is categorizing cost into fixed and variable cost. However interest paid on deposits is a typical example of variable cost while other operating expenses could be categorized as fixed cost; and since CVP can be calculated based in terms of average units per item or dollar of sales volume, then it can be applied to banks. Infact in the bank where I worked, break-even points are determined as soon as branches start up operations. CVP can be used for pricing decision such that for a price sensitive group, fees can be increased on unprofitable accounts and as such customers depart, operating costs reduce and those than remain pay for their keep.

References
Atrill and Mclaney (2011). Accounting and Finance for Nonspecialist. 7th ed. Pearson Education Limited UK

Pandey and Badyopadhyaya (nd). Cost Volume Profit Analysis and bank performance; A case study of public sector banks. Available online from: http://www.scribd.com/doc/7161138/CVP-Analysis-and-Banks-Performance

Horngren, Charles T., George Foster, and Srikant M. Datar. (1997). Cost Accounting: A Managerial Emphasis. 9th ed. Upper Saddle River, NJ: Prentice Hall.