Mark Young

Software. Microcontrollers. Beer.

Up and Running With Bless and Enforced MFA - Part 2

Part 2 of deploying Bless will focus on enforcing MFA and using Lyft’s client.

The problem at this point is that anyone in the ops group can bounce without MFA enforced. We can set up the lyft client, which enforces MFA, but if we dont make other changes, there’s no enforcement of MFA. ie you can still call the netflix client and bounce right in.

The other issue is that Netflix’s bless holds true the idea of a bastion host. In-house, we have IPSec tunnels from our VPC’s to our internal network, so I dont need to bounce through a bastion. So I want anyone to be able to bounce, given that they have permissions (ops) and they used MFA. Lyft’s doesn’t enforce the bastion concept, which is nice.

Prerequisites

In the IAM console in AWS, enable an MFA device for your user.

Setting up the client

I wrote a wrapper to simplify this. Feel free to not like it, but I put this in my ~/.bash_aliases to make it easier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function bounce() {
  if [[ ! -d ~/.blessclient ]]; then
    pushd . >/dev/null
    git clone https://github.com/lyft/python-blessclient.git ~/.blessclient
    cd $_
    make
    popd >/dev/null
  fi
  INTERNAL_IP=$(ifconfig | grep inet | grep 192 | awk '{ print $2}')
  # This gets weird if you have wired and wifi hooked up (sometimes i do). It might allow the wrong IP and give you a crap error messsage.
  if [[ $(ifconfig | grep inet | grep 192 | awk '{ print $2}' | wc -l) -gt 1 ]] && [[ -z ${BLESSFIXEDIP} ]]; then
    echo "More than one internal ip found. Disable a network interface or provide it manually via env var 'BLESSFIXEDIP'"
  else
    FILE="$(mktemp)"
    rm -rf ${FILE}*
    ssh-keygen -f ${FILE} -N ""
    BLESS_IDENTITYFILE=${FILE} BLESSFIXEDIP=${BLESSFIXEDIP:-${INTERNAL_IP}} ~/.blessclient/blessclient.run --host $1 --nocache --config ~/.blessclient/blessclient.cfg --region EAST && ssh -i ${FILE} ec2-user@$1
  fi
}

Now you can bounce with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ bounce 10.0.3.7
Generating public/private rsa key pair.
Your identification has been saved in /var/folders/5j/n9trdvbn3qqdtkshkpvnrkhh0000gp/T/tmp.s2ZdeuJD.
Your public key has been saved in /var/folders/5j/n9trdvbn3qqdtkshkpvnrkhh0000gp/T/tmp.s2ZdeuJD.pub.
The key fingerprint is:
SHA256:CizQBI/ROY8QIeiRjt7rY65jPc/oJDL3MA markyoung@Admins-MacBook-Pro.local
The key's randomart image is:
+---[RSA 2048]----+
|B*+.             |
|++o              |
|=..              |
|.o.*.            |
|. o  S           |
|...+ . .         |
| E .o.           |
|oo= + .          |
|.=*              |
+----[SHA256]-----+
Enter your AWS MFA code: 477821
Requesting certificate for your public key (set BLESSQUIET=1 to suppress these messages)
Finished getting certificate.
Last login: Fri Jun 16 20:14:52 2017 from ip-192-168-1-124.ec2.internal

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.03-release-notes/
No packages needed for security; 2 packages available
Run "sudo yum update" to apply all updates.
[mark@server ~]$

Nice! But again, MFA is used, not enforced. Lets change that.

I hinted at my group policy in part 1 for ops that looked like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
resource "aws_iam_group_policy" "bless-client" {
    name = "bless-client_iam_policy"
    group = "${aws_iam_group.ops.id}"
    policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": [
          "${data.terraform_remote_state.bless_client_iam.bless-client-role-arn}"
        ]
      },
      {
        "Action": "kms:Encrypt",
        "Effect": "Allow",
        "Resource": [
          "${data.terraform_remote_state.kms.ops_bless_arn}"
        ],
        "Condition": {
          "StringEquals": {
            "kms:EncryptionContext:user_type": "user",
            "kms:EncryptionContext:from": "$${aws:username}"
          }
        }
      }
    ]
}
EOF
}

But in actuality, it should look more like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
resource "aws_iam_group_policy" "bless-client" {
    name = "assume-bless-role"
    group = "${aws_iam_group.ops.id}"
    policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": [
        "${data.terraform_remote_state.bless_client_iam.bless-client-role-arn}"
      ],
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "sts:AssumeRole",
      "Resource": [
        "${data.terraform_remote_state.bless_client_iam.bless-client-role-arn}"
      ],
      "Condition": {"BoolIfExists": {"aws:MultiFactorAuthPresent": false}}
    },
    {
      "Action": "kms:Encrypt",
      "Effect": "Allow",
      "Resource": [
        "${data.terraform_remote_state.kms.ops_bless_arn}"
      ],
      "Condition": {
        "StringEquals": {
          "kms:EncryptionContext:user_type": "user",
          "kms:EncryptionContext:from": "$${aws:username}"
        },
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    },
    {
      "Action": "kms:Encrypt",
      "Effect": "Deny",
      "Resource": [
        "${data.terraform_remote_state.kms.ops_bless_arn}"
      ],
      "Condition": {
        "StringEquals": {
          "kms:EncryptionContext:user_type": "user",
          "kms:EncryptionContext:from": "$${aws:username}"
        },
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": false
        }
      }
    }
  ]
}
EOF
}

What did we do? We basically said ops users can assume the bless-client role or call kms:Encrypt on that key, but only if they used MFA. If you try the original bless client now, it will fail.