Overview
We previously introduced the subject of red team infrastructure and discussed some of our thoughts on the best practices for managing that infrastructure. In this post, we’ll continue that discussion and primarily focus around the observations we’ve made while developing command-line applications to manage some aspects of that server infrastructure. tl;dr: SSO/FIDO U2F with CLI apps in a zero trust setup.
Background
Praetorian relies on custom applications for red team engagements and the corresponding infrastructure deployments. Engineers performing the operations and working on the assessments need a way to access this infrastructure. Engineers also need to have flexibility on how to create additional tooling to support further work. Internally, we’ve developed command-line tools to connect to custom application APIs. Commonly, red teams may provision access to custom applications via network ACLs (Access Control Lists), restricting access to an operator VPN (Virtual Private Network) endpoint. Access to the APIs is possible after connecting to that VPN, as the firewall rules permit traffic to the API servers from the appropriate source IP addresses. However, there are drawbacks to this approach. Anyone with access to the VPN is able to connect to the APIs. Granular permission controls are difficult to implement if that VPN is shared between many teams, or perhaps by the whole company.
However, there are advantages to architectures not relying on a VPN. It might be advantageous to have the flexibility to interact with the API from any public IP address instead of through a VPN. This flexibility is especially useful when managing connections between attributable (a company VPN) and non attributable proxies. For example, when connecting to an attributable VPN to access C2 (Command and Control) infrastructure but then needing to change connections to a non-attributable VPN to access an external login portal. At Praetorian we are big proponents of the zero trust or “BeyondCorp” style network architecture. A zero trust style network would help address the drawbacks of the VPN-like approach. So, we implemented a workflow to transition from a VPN like setup to a zero trust architecture, allowing us to use FIDO U2F (Universal 2nd Factor) hardware tokens for command line applications while removing the requirement for a VPN.
Setup
Cloudflare Access and Cloudflare Argo are the two solutions that enable SSO (Single Sign-On) with CLI (command line interface) apps. See here. We’ll assume that the reader has followed the Cloudflare documentation for access and can access a server from the CLI (relevant Cloudflare docs). Cloudflare supports configuring 2FA (Two Factor Authentication) through an SSO provider. We have configured this to mimic our standard authentication procedure, using a hardware YubiKey as a second-factor authentication token.
Actual Usage
The Cloudflare documentation stops after showing how to use the cloudflared tool to authenticate to the server and send a curl request. However, this authentication flow still needs to be incorporated into a CLI application. The integration is left up to the Cloudflare documentation’s reader, so we’ll outline one approach we use for some of our APIs. The neat thing about this whole process is that it’s very flexible. The approach described below might work for one team but not another. It’s very easy to modify and incorporate to suit any team’s needs. Our starting point is where the Cloudflare documentation ends.
Running the following command completes the authentication process to the server as expected and saves a token in the user’s ~/.cloudflared
directory:
/home/dev $ cloudflared access login http://example.com
The token is saved as example.com-token. This token can then be used to connect to the backend server in HTTP requests by including it in the cf-access-token header. A sample Python3 CLI API tool to access the token could be:
#!/usr/bin/env python3import requests
def connect(hostname):server = 'https://' + hostnamefile_path = os.path.abspath('~/.cloudflared/' + hostname + '-token')with open(file_path, 'r') as f:token = ''.join(f.readlines())cf_header = {'cf-access-token': token,}
r = requests.get(server + '/hello', headers=cf_header)print(r.data)
if __name__ == "__main__":if len(sys.argv) != 2:print('Usage: ./sample-cli.py [server.hostname]')sys.exit(1)connect(sys.argv[1])
Please note, this sample code won’t actually run or do anything, but provides an idea for how to include the token in a simple command-line script. While useful, we still need to authenticate to the server using cloudflared. So, let’s wrap that python script into a shell script:
function sample-cli(){if [ -n "$ZSH_VERSION" ]; then emulate -L sh; fiif [ "$1" == "" ]; thenecho "USAGE: sample-cli [keyserver.hostname]"exitficloudflared access login https://$1python3 /path/to/sample-cli.py $1}
The sample script could be sourced into zsh/bash when needed, or included in the user’s dotfiles, etc. Cloudflare’s documentation provides other ways to accomplish the same process. They provide the example of using the token as an environment variable within scripts, that works too. Really, there are tons of options here; it really comes down to what you’re trying to develop.
Then, from an end user’s perspective, the workflow to use the CLI tool and connect to the server API becomes:
The browser window that cloudflared opens prompts for authentication, and then the CLI tool authenticates with the acquired token.
Conclusion
With Cloudflare access and Argo tunnel, we’ve been able to add multi-factor authentication to our command-line scripts that previously would have connected directly to the servers hosting our APIs. The actual implementation of functionality is very flexible and entirely up to what works best for our team and needs. The main takeaway here is that adding and incorporating multi-factor authentication controls in seemingly benign command line applications is not only possible, but fairly straightforward.
References
Argo Tunnel
Cloudflare Access
Connecting from CLI
Share via: