Skip to content

Commit

Permalink
Merge pull request #10 from nelsonenzo/auth
Browse files Browse the repository at this point in the history
basic auth
  • Loading branch information
nelsonenzo authored Dec 10, 2023
2 parents f4faef7 + c1caced commit 1e51d4b
Show file tree
Hide file tree
Showing 6 changed files with 3,378 additions and 573 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
## ToLocal
Automates AWS to create a revere proxie for HTTPS requests from a public DNS record to your service running on `localhost:port`.
Automates AWS to create a reverse proxy for HTTPS requests from a public DNS record to your service running on `localhost:port`.
for example:

https://api.dev.yourdomain.com -> localhost:4000

It is all deployed in your own AWS cloud.

## Requirements
- node > v20.0.0
Expand Down
3,813 changes: 3,282 additions & 531 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"@aws-sdk/client-ec2": "^3.468.0",
"@aws-sdk/client-route-53": "^3.468.0",
"@aws-sdk/credential-providers": "^3.468.0",
"apache-md5": "^1.1.8",
"arg": "^4.1.3",
"axios": "^1.6.2",
"esm": "^3.2.25",
"execa": "^2.1.0",
"got": "^14.0.0",
"inquirer": "^7.1.0",
"ncp": "^2.0.0",
"openssl-nodejs": "^1.0.5",
"pass": "^0.2.0",
"path": "^0.12.7",
"public-ip": "^6.0.1",
"ssh-keygen": "^0.5.0",
"sudo-prompt": "^9.2.1",
"superagent": "^8.1.2",
"util": "^0.12.3"
Expand Down
65 changes: 54 additions & 11 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,30 @@ import { EC2 } from '@aws-sdk/client-ec2';
import { Route53 } from '@aws-sdk/client-route-53';

import { publicIpv4 } from 'public-ip';
import keygen from 'ssh-keygen';

import md5 from 'apache-md5'

async function tolocalConfigDir(options){
let dir = options.configDir;
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
}
async function copyTerraformFiles(options) {
async function sshKeyGen(options){
let location = path.join(options.configDir,'tolocal_rsa')
keygen({
location: location,
read: true
}, function(err, out){
if(err) return console.log('config sshKeyGen Failed: '+err);
return true;
// console.log('Keys created!');
// console.log('private key: '+out.key);
// console.log('public key: '+out.pubKey);
});
}
async function copyTerraformFiles(options) {
const templateDir = path.resolve(
new URL(import.meta.url).pathname,
'../../terraform'
Expand All @@ -32,8 +48,9 @@ async function copyTerraformFiles(options) {
clobber: true,
});
}

// this coppies the file, but for some reason tfvars will not get read
// tried using await througought and it had no effect.
// tried using await througought and it had no effect.
// it should already be awaited at the bottom, when this function is called.
// work around, use --dev flag, then run again without it.
if (options.isDev) {
Expand Down Expand Up @@ -69,9 +86,32 @@ function readTfvars(options){
return {}
}
}

async function promptForMissingOptions(options,tfvars) {
var my_ip = await publicIpv4()

// //////////////////////
// input: basic auth credentials
// //////////////////////
var username_default = tfvars.hasOwnProperty('username') ? tfvars.username : "tolocal"
var username = await inquirer.prompt({
type: 'input',
name: 'username',
message: 'Basic Auth Username',
default: username_default
})

var password_default = tfvars.hasOwnProperty('password') ? tfvars.password : "tolocal"
var passwordTxt = await inquirer.prompt({
type: 'input',
name: 'password',
message: 'Basic Auth Password',
default: password_default
})

var password = md5(passwordTxt.password)
var tolocal_auth = Buffer.from(`${username.username}:${password}`).toString('base64')

////////////////////////
// input: aws profile
////////////////////////
Expand Down Expand Up @@ -125,7 +165,7 @@ async function promptForMissingOptions(options,tfvars) {
var subnet_params = {
Filters: [
{
Name: "vpc-id",
Name: "vpc-id",
Values: [
options.vpc_id
]
Expand All @@ -138,8 +178,8 @@ async function promptForMissingOptions(options,tfvars) {
var nameTag = subnet.Tags.filter(data => (data.Key == "Name"))
var name = nameTag.length == 0 ? "" : nameTag[0].Value
return {
name: `name: ${name}, id: ${subnet.SubnetId}, az: ${subnet.AvailabilityZone}, cidr: ${subnet.CidrBlock}`,
value: subnet.SubnetId,
name: `name: ${name}, id: ${subnet.SubnetId}, az: ${subnet.AvailabilityZone}, cidr: ${subnet.CidrBlock}`,
value: subnet.SubnetId,
short: subnet.SubnetId,
seperator: '|'
}
Expand All @@ -160,6 +200,7 @@ async function promptForMissingOptions(options,tfvars) {
region: aws_region.aws_region,
});
const raw_hosted_zones = await route53.listHostedZones()
// TODO: handle case when there are no hosted zones.
// console.log(hosted_zones)
var hosted_zones = raw_hosted_zones.HostedZones.map(zone => {
return zone.Name.slice(0, -1);
Expand Down Expand Up @@ -187,7 +228,7 @@ async function promptForMissingOptions(options,tfvars) {
local_tunnel_input_default = tfvars.local_tunnels.map(tunnel => {
return `${tunnel.subdomain}=${tunnel.localport}`
}).join(' ')
}
}

var local_tunnel_input = await inquirer.prompt({
type: 'string',
Expand Down Expand Up @@ -237,7 +278,6 @@ var local_tunnels = transform_tunnel_input(local_tunnel_input.local_tunnel_input
default: ssh_private_key_file_path_default
});


////////////////////////////////////
// the final return statement
////////////////////////////////////
Expand All @@ -255,7 +295,10 @@ var local_tunnels = transform_tunnel_input(local_tunnel_input.local_tunnel_input
local_tunnels: local_tunnels,
ssh_public_key_file_path: ssh_public_key_file_path.ssh_public_key_file_path,
ssh_private_key_file_path: ssh_private_key_file_path.ssh_private_key_file_path,
my_ip: `${my_ip}/32`
my_ip: `${my_ip}/32`,
username: username.username,
password: passwordTxt.password,
tolocal_auth: tolocal_auth
}

};
Expand All @@ -268,11 +311,11 @@ async function tolocalConfigFile(options) {
if (err) throw err;
})
}
export async function config(options) {
export async function config(options) {
await tolocalConfigDir(options).catch( (e) => {console.log(e)})
await copyTerraformFiles(options).catch( (e) => {console.log(e)})
await terraformInit(options).catch( (e) => {console.log(e)})
let tfvars = readTfvars(options)
let tfvars = readTfvars(options)
// promtForMissingOptions
// write tfvars file
// return tfvars.json blob + options
Expand All @@ -285,4 +328,4 @@ export async function config(options) {
// console.log(options)
console.log("config ran");
return options;
}
}
55 changes: 29 additions & 26 deletions terraform/main.tf
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
variable "aws_region" {
variable "aws_region" {
type = string
}
variable "aws_profile" { type = string }
variable "aws_profile" { type = string }
provider "aws" {
region = var.aws_region
region = var.aws_region
profile = var.aws_profile
}
variable "vm_size" { type = string }
variable "vm_size" { type = string }
variable "dns_host_zone" { type = string }
variable "http_only" { type = bool }
variable "vpc_id" { type = string }
variable "subnet_id" { type = string }
variable "http_only" { type = bool }
variable "vpc_id" { type = string }
variable "subnet_id" { type = string }
variable "tolocal_auth" { type = string }

variable "local_tunnels" {
type = list(object({
localport = number
proxyport = number
localport = number
proxyport = number
full_domain = string
}))
}
variable "ssh_public_key_file_path" { type = string }
variable "ssh_public_key_file_path" { type = string }
variable "ssh_private_key_file_path" { type = string }
variable "my_ip" { type = string }

# how to fetch the latest ubuntu ami:
# https://letslearndevops.com/2018/08/23/terraform-get-latest-centos-ami/
data "aws_ami" "latest-ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
owners = ["099720109477"] # Canonical

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20231025"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_security_group" "tolocal_ec2" {
Expand Down Expand Up @@ -85,12 +87,13 @@ resource "aws_instance" "ubuntu_1804" {
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.tolocal_ec2.id]
associate_public_ip_address = true
user_data = templatefile("${path.module}/user_data.sh.tpl",
{
local_tunnels = var.local_tunnels,
public_key = file(var.ssh_public_key_file_path)
https_only = var.http_only
})
user_data = templatefile("${path.module}/user_data.sh.tpl",
{
local_tunnels = var.local_tunnels,
public_key = file(var.ssh_public_key_file_path)
https_only = var.http_only
tolocal_auth = var.tolocal_auth
})
}

#################
Expand All @@ -114,15 +117,15 @@ data "aws_route53_zone" "tld" {

resource "aws_route53_record" "www" {
for_each = toset([
for domain in var.local_tunnels:
for domain in var.local_tunnels :
domain.full_domain
])
zone_id = data.aws_route53_zone.tld.zone_id
name = each.value
# name =
type = "A"
ttl = "300"
records = ["${aws_instance.ubuntu_1804.public_ip}"]
zone_id = data.aws_route53_zone.tld.zone_id
name = each.value
# name =
type = "A"
ttl = "300"
records = ["${aws_instance.ubuntu_1804.public_ip}"]
}

output "ubuntu_ip" { value = "ubuntu@${aws_instance.ubuntu_1804.public_ip}" }
10 changes: 8 additions & 2 deletions terraform/user_data.sh.tpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx

sudo apt-get install -y nginx certbot python3-certbot-nginx apache2-utils

ends="%{ for tunnel in local_tunnels }
${tunnel.full_domain}:${tunnel.proxyport}
Expand All @@ -14,19 +15,24 @@ echo $certbot_domains > /verify_certbot_domains
echo $ends > /verify_ends
echo "${public_key}" >> /home/ubuntu/.ssh/authorized_keys;

echo $(echo ${tolocal_auth} | base64 -d) > /etc/nginx/tolocalauth

for TUNNEL in $ends; do
DOMAIN=$(echo "$TUNNEL" | awk -F ":" '{print $1}')
PROXY_PORT=$(echo "$TUNNEL" | awk -F ":" '{print $2}')

touch /etc/nginx/conf.d/"$DOMAIN".conf
FILE=/etc/nginx/conf.d/"$DOMAIN".conf

cat > $FILE <<- EOM
server {
listen 80;
server_name $DOMAIN;
auth_basic "tolocal auth";
auth_basic_user_file tolocalauth;
location / {
auth_basic "tolocal auth";
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$remote_addr;
proxy_pass http://localhost:$PROXY_PORT;
Expand Down

0 comments on commit 1e51d4b

Please sign in to comment.