AWS News Blog

Just-in-time VPN access with an AWS IoT button

Voiced by Polly

Guest post by AWS Community Hero Teri Radichel. Teri Radichel provides cyber security assessments, pen testing, and research services through her company, 2nd Sight Lab. She is also the founder of the AWS Architects Seattle Meetup.

While traveling to deliver cloud security training, I connect to Wi-Fi networks, both in my hotel room and in the classroom using a VPN. Most companies expose a remote VPN endpoint to the entire internet. I came up with a hypothesis that I could use an AWS IoT button to allow network access only to required locations. What if a VPN user could click to get access, which would trigger opening a network rule, and double-click to disallow network traffic again? I tested it out this idea, and you can see the results below.

You might be wondering why you might want to use a VPN for remote cloud administration. Why an AWS IoT button instead of a laptop or mobile application? More on that in my cloud security blog.

Initially, I wanted to use the AWS IoT Enterprise Button because it allows an organization to have control over the certificates used on the devices. It also uses Wi-Fi, and I was hoping to capture the button IP address to grant network access. To do that, I had to be able to prove that the button received the same IP address from the Wi-Fi network as my laptop. Unfortunately, due to captive portals used by some wireless networks, I had problems connecting the button at some locations.

Next, I tried the AT&T LTE-M Button. I was able to get this button to work for my use case, but with a few less than user-friendly requirements. Because this button is on a cellular network rather than the Wi-Fi I use to connect to my VPN in a hotel room, I can’t auto-magically determine the IP address. I must manually set it using the AWS IoT mobile application.

The other issue I had is that some networks change the public IP addresses of the Wi-Fi client after the VPN connection. The before and after IP addresses are always in the same network block but are not consistent. Instead of using a single IP address, the user has to understand how to figure out what IP range to pass into the button. This proof of concept implementation works well but would not be an ideal solution for non-network-savvy users.

The good news is that I don’t have to log into my AWS account with administrative privileges to change the network settings and allow access to the VPN endpoint from my location. The AWS IoT button user has limited permissions, as does the role for the AWS Lambda function that grants access. The AWS IoT button is a form of multi-factor authentication.

Configure the button with a Lambda function

Caveat: This is not a fully tested or production-ready solution, but it is a starting point to give you some ideas for the implementation of on-demand network access to administrative endpoints. The roles for the button and the Lambda function can be much more restrictive than the ones I used in my proof-of-concept implementation.

1. Set up your VPN endpoint (the instructions are beyond the scope of this post). You can use something like OpenVPN or any of the AWS Marketplace options that allow you to create VPNs for remote access.

2. Jeff Barr has already written a most excellent post on how to set up your AWS IoT button with a Lambda function. The process is straight-forward.

3. To allow your button to change the network, add the ability for your Lambda role to replace network ACL entries. This role enables the assigned resource to update any rule in the account—not recommended! Limit this further to specific network ACLs. Also, make sure that users can only edit the placement attributes of their own assigned buttons.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ec2:ReplaceNetworkAclEntry",
            "Resource": "*"
        }
    ]
}

4. Write the code.

For my test, I edited the default code that comes with the button to prove that what I was trying to do would work. I left in the lines that send a text message, but after the network change. That way, if there’s an error, the user doesn’t get the SNS message.

Additionally, you always, always, always want to validate any inputs sent into any application from a client. I added log lines to show where you can add that. Change these variables to match your environment: vpcid, nacl, rule. The rule parameter is the rule in your network ACL that is updated to use the IP address provided with the button application.

from __future__ import print_function

import boto3
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

sns = boto3.client('sns')

def lambda_handler(event, context):

    logger.info('Received event: ' + json.dumps(event))

    attributes = event['placementInfo']['attributes']

    phone_number = attributes['phoneNumber']
    message = attributes['message']
    ip = attributes['ip']

    logger.info("Need code here to validate this is a valid IP address")
    logger.info("Need code here to validate the message")
    logger.info("Need code here to validate the phone number")

    for key in attributes.keys():
        message = message.replace('{{%s}}' % (key), attributes[key])
    message = message.replace('{{*}}', json.dumps(attributes))

    dsn = event['deviceInfo']['deviceId']
    click_type = event['deviceEvent']['buttonClicked']['clickType']
  
    vpcid = 'vpc-xxxxxxxxxxxxxxxx'
    nacl = 'acl-xxxxxxxxxxxxxxx'
    rule = 200
    
    cidr =  ip + '/32'
    
    message = message + " " + cidr
    
    client = boto3.client('ec2')

    response = client.replace_network_acl_entry(
        NetworkAclId=nacl,
        CidrBlock=cidr,
        DryRun=False,
        Egress=False,
        PortRange={
            'From': 500,
            'To': 500
        },
        Protocol="17",
        RuleAction="allow",
        RuleNumber=rule
    )
    
    sns.publish(PhoneNumber=phone_number, Message=message)

    logger.info('SMS has been sent to ' + phone_number)

 

5. Write the double-click code to remove network access. I left this code as an exercise for the reader. If you understand how to edit the network ACL in the previous step, you should be able to write the double-click function to disallow traffic by changing the line RuleAction=”allow” to RuleAction=”deny”. You have now blocked access to the network port that allows remote users to connect to the VPN.

Test the button

1. Get the public IP address for your current network by going to a site like https://whatismyip.com.

For this post, assume that you only need a single IP address. However, you could easily change the code above to allow for an IP range instead (or in other words, a CIDR block).

2. Log in to the phone app for the button.

3. Choose Projects and select your button project. Mine is named vpn2.

4. The project associated with the Lambda function that you assigned to the button requires the following placement attributes:
message: Network updated!
phoneNumber: The phone number to receive the text.
ip: The IP address from whatismyip.com.

5. Select an existing attribute to change it or choose + Add Placement Attribute to add a new one.

6. Press your AWS IoT button to trigger the Lambda function. If it runs without error, you get the text message with the IP address that you entered.

7. Check your VPC network ACL rule to verify the change to the correct IP address.

8. Verify that you can connect to the VPN.

9. Assuming you implemented the double-click function to disable access, double-click the button to change the network ACL rule to “deny” instead of “allow”.

Now you know how to use an AWS IoT button to change a network rule on demand. Hopefully, this post sparks some ideas for adding additional layers of security to your AWS VPN and administrative endpoints!

Teri Radichel

Teri Radichel