├── LICENSE ├── README.md └── tugshuttle /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Louis van Harten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TugShuttle 2 | =============== 3 | ## Purpose 4 | TugShuttle is a command line tool that starts a DigitalOcean Droplet and tunnels your traffic to it over SSH making it a temporary VPN. As the name suggests, it uses Tugboat to interface with DigitalOcean and Sshuttle to tunnel your traffic to the Droplet. 5 | 6 | ![An example use case for a temporary VPN. Watching all of Blackadder would cost $0.09 in DigitalOcean credit. Art style "borrowed" from Randall Munroe (xkcd.com)](https://cloud.githubusercontent.com/assets/6605273/11608014/c05a5cda-9b5d-11e5-8873-fa5b1c9f971c.png) 7 | 8 | VPNs have many valid use cases and can be quite convenient at times, but they tend to require a monthly subscription. That is hard to justify if you *almost* never use it. What's special about TugShuttle tunnels is that it's really easy to quickly spin one up *and down again* without needing a monthly subscription for anything (as is the nature of DigitalOcean Droplets). Droplets only cost $0.007 per hour (not a typo, that's less than one cent) so watching a region locked movie only sets you back approximately one additional cent. It's pretty manageable. 9 | 10 | ## Prerequisites / Dependencies 11 | 1) A DigitalOcean account with some credit. Renting a Droplet is very cheap, but it's not free. If you don't have one, you can make an account [here](https://www.digitalocean.com/?refcode=433d02d1a833) (note: this is a referral link, meaning you get $10 starting credit. If you don't feel comfortable using that, you can sign up on the non-referral page [here](https://cloud.digitalocean.com/registrations/new)) 12 | 13 | 2) A valid SSH key linked to your DigitalOcean account. You can link one [here](https://cloud.digitalocean.com/settings/security). If you've never used SSH keys before, learn how to create them [here](https://help.github.com/articles/generating-ssh-keys/). 14 | 15 | 3) Sshuttle. It's in the repositories. You can also grab it from the [Github page](https://github.com/apenwarr/sshuttle). 16 | 17 | 4) Tugboat. Get it with "gem install tugboat" (requires Ruby) 18 | 19 | ## Installation 20 | Clone the repository and put "tugshuttle" in any folder in your $PATH. 21 | 22 | ## Usage 23 | `tugshuttle $server`, where $server is one of: `ams1`, `ams2`, `ams3`, `fra1`, `lon1`, `nyc1`, `nyc2`, `nyc3`, `sfo1`, `sgp1`, `tor1`. This connects you to a server in Amsterdam, Frankfurt, New York City, San Francisco, Singapore or Toronto respectively. 24 | 25 | When Tugshuttle is first run and ~/.tugboat is not present / properly configured, the program will ask for an Access Token. You can find it [here](https://cloud.digitalocean.com/settings/tokens/new). 26 | 27 | If no SSH key is linked to your DigitalOcean account, Tugshuttle will ask you to do that before continuing. You can link your SSH key [here](https://cloud.digitalocean.com/settings/security). If you've never used SSH keys before, read about how to generate and use them [here](https://help.github.com/articles/generating-ssh-keys/). 28 | 29 | **Important:** If Tugshuttle receives a SIGKILL, it will not be able to destroy the Droplet it created. If this happens, the Droplet will run until you manually destroy it. You can either do this via the DO website, using `tugboat destroy tugshuttle` or using `tugshuttle clean`. 30 | 31 | ## License 32 | :[MIT LICENSE](LICENSE) 33 | -------------------------------------------------------------------------------- /tugshuttle: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##################################################################################### 4 | # # 5 | # The MIT License (MIT) # 6 | # # 7 | # Copyright (c) 2016 Louis van Harten # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy # 10 | # of this software and associated documentation files (the "Software"), to deal # 11 | # in the Software without restriction, including without limitation the rights # 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # 13 | # copies of the Software, and to permit persons to whom the Software is # 14 | # furnished to do so, subject to the following conditions: # 15 | # # 16 | # The above copyright notice and this permission notice shall be included in all # 17 | # copies or substantial portions of the Software. # 18 | # # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # 25 | # SOFTWARE. # 26 | # # 27 | ##################################################################################### 28 | 29 | show_help() { 30 | printf "usage:\n\ttugshuttle loc#\n\nwith loc# in:\n\tams1, ams2, ams3, fra1, lon1, nyc1, nyc2, nyc3, sfo1, sgp1, tor1\n" 31 | printf "\nIf the Tugshuttle process receives a SIGKILL while it is running,\nit will not be able to clean up the Droplet. " 32 | printf "You will be billed \nDigitalOcean credit for Droplets that are not destroyed.\n" 33 | printf "To clean up lingering Tugshuttle Droplets, run:\n\n\ttugshuttle clean\n" 34 | } 35 | 36 | show_defaults () { 37 | if [ ! -f "/home/$USER/.tugboat" ]; 38 | then 39 | echo "Default values are stored in ~/.tugboat and this file doesn't exist!" 40 | echo 41 | echo "Either run tugshuttle or tugboat at least once to generate a ~/.tugboat file." 42 | exit 0 43 | else 44 | echo "Default tugboat values:" 45 | echo 46 | echo "Region: $(grep "region:" ~/.tugboat | sed "s@^.*region: @@")" 47 | echo "SSH Key: $(grep "ssh_key_path:" ~/.tugboat | sed "s@^.*ssh_key_path: @@")" 48 | echo 49 | echo "Digital Ocean Access Token:" 50 | echo "$(grep "access_token" ~/.tugboat | sed "s@^.*access_token: @@")" 51 | echo 52 | echo "Change these values by editing ~/.tugboat" 53 | exit 0 54 | fi 55 | } 56 | 57 | clean_up_droplets() { 58 | if [[ -z "$(tugboat droplets | grep tugshuttle-to-)" ]] 59 | then 60 | printf "\nNo more Tugshuttle Droplets found!\n" 61 | else 62 | tugboat destroy tugshuttle-to- 63 | printf "\nConfirming deletion" 64 | for loopvar in `seq 1 10`; 65 | do 66 | printf "." 67 | sleep 1 68 | done 69 | printf ".\n" 70 | printf "\nOne Tugshuttle Droplet deleted!\n" 71 | clean_up_droplets 72 | fi 73 | exit 74 | } 75 | 76 | list_regions() { 77 | 78 | echo "Please select a region for the Droplet:" 79 | echo 80 | PS3="==> " 81 | do_regions=($(tugboat regions | grep slug |sed -e "s/.*slug:\ //" | tr ')' ' ')) 82 | if [[ -z "$do_regions" ]] 83 | then 84 | do_regions=("ams2" "ams3" "blr1" "fra1" "lon1" "nyc1" "nyc2" "nyc3" "sfo1" "sfo2" "sgp1" "tor1") 85 | fi 86 | 87 | select opt in "${do_regions[@]}" "Help" "Quit"; 88 | do 89 | printf "%s\n\n\n" "-------------------------------------------" 90 | if (( REPLY == 2 + ${#do_regions[@]} )) ; then 91 | echo "No Tunnel created." 92 | exit 0 93 | 94 | elif (( REPLY == 1 + ${#do_regions[@]} )) ; then 95 | show_help 96 | exit 0 97 | 98 | elif (( REPLY > 0 && REPLY <= ${#do_regions[@]} )) ; then 99 | echo 100 | echo ""$opt" selected." 101 | region="$opt" 102 | break 103 | else 104 | echo "Invalid option." 105 | fi 106 | done 107 | } 108 | 109 | #first set-up in case tugboat isn't configured 110 | if [ ! -z "$(tugboat info | grep authorize)" ] || [ ! -z "$(tugboat droplets | grep from\ API:\ unauthorized)" ] 111 | then 112 | printf "Tugboat could not find a valid (read/write) DigitalOcean Access Token.\nYou can get an Access Token from " 113 | printf "https://cloud.digitalocean.com/settings/tokens/new\n\nEnter your access token: " 114 | read acctoken 115 | if [ -e ~/.tugboat ]; then mv ~/.tugboat ~/.tugboat.bak; printf "Existing ~/.tugboat settings stored in ~/.tugboat.bak\n\n"; fi 116 | printf "%s\n%s\n%s\n" "---" "authentication:" " access_token: $acctoken" > ~/.tugboat 117 | printf "%s\n%s\n%s\n%s\n" "ssh:" " ssh_user: root" " ssh_key_path: ~/.ssh/id_rsa" " ssh_port: '22'" >> ~/.tugboat 118 | printf "%s\n%s\n%s\n%s\n" "defaults:" " region: lon1" " image: ubuntu-14-04-x64" " size: 512mb" >> ~/.tugboat 119 | printf "%s\n%s\n%s\n" " ssh_key: ''" " private_networking: 'false'" " backups_enabled: 'false'" >> ~/.tugboat 120 | 121 | #correct token if invalid 122 | validAuth='' 123 | while [ -z "$validAuth" ] 124 | do 125 | if [ ! -z "$(tugboat droplets 2>/dev/null | grep from\ API:\ unauthorized)" ] 126 | then 127 | printf "\n (!) Access Token invalid.\n\nYou can get an Access Token from " 128 | printf "https://cloud.digitalocean.com/settings/tokens/new\n\nEnter your access token: " 129 | read acctoken 130 | sed -e "s/access_token:.*/access_token:\ $acctoken/" ~/.tugboat > ~/.tugboat.tmp && mv ~/.tugboat.tmp ~/.tugboat 131 | else 132 | validAuth='success' 133 | fi 134 | done 135 | 136 | while [ -z "$(tugboat keys 2>/dev/null | grep id:)" ] 137 | do 138 | printf "\n%s\n" "No SSH keys linked to this DO account." 139 | printf "%s\n\n" "Add your public key at https://cloud.digitalocean.com/settings/security" 140 | read -p "Press [Enter] when done" 141 | done 142 | 143 | printf "\n\n$(tugboat keys 2>/dev/null | grep id: | grep -nT Name | sed -e s/,\ fingerprint:.*//)\n\n" 144 | read -p "Choose a linked SSH key ["1", "2", "3", ...] " 145 | keyID="$(tugboat keys 2>/dev/null | grep id: | grep -nT Name | sed -e s/,\ fingerprint:.*// | sed -n "$REPLY""p" |\ 146 | sed -e s/.*id:\ // | sed -e "s/)//" )" 147 | sed -e "s/ssh_key:.*/ssh_key:\ $keyID/" ~/.tugboat > ~/.tugboat.tmp && mv ~/.tugboat.tmp ~/.tugboat 148 | 149 | printf "\nChanges written to ~/.tugboat\nIf any values are incorrect or change, edit ~/.tugboat.\n\n" 150 | fi 151 | 152 | if [[ ! $# > 0 ]] 153 | then 154 | show_help 155 | printf "\n%s" "-------------------------------------------------" 156 | printf "\n\nAssuming you want to start a tunnel...\n" 157 | list_regions 158 | fi 159 | 160 | while [[ $# > 0 ]] 161 | do 162 | key="$1" 163 | case $key in 164 | -r|--region) 165 | region="$2" 166 | shift 167 | ;; 168 | -h|--help) 169 | show_help 170 | exit 0 171 | ;; 172 | -c|--clean|clean) 173 | clean_up_droplets 174 | exit 0 175 | ;; 176 | --defaults|--default) 177 | show_defaults 178 | ;; 179 | -*) 180 | echo "Error: Unknown option: $1" >&2 181 | show_help 182 | exit 0 183 | ;; 184 | ams2|ams3|blr1|fra1|lon1|nyc1|nyc2|nyc3|sfo1|sfo2|sgp1|tor1) 185 | region="$key" 186 | ;; 187 | *) 188 | echo "Unrecognized region or option: ${key}" 189 | list_regions 190 | ;; 191 | esac 192 | shift 193 | done 194 | 195 | 196 | keyID=$(cat ~/.tugboat | grep ssh_key: | sed -e "s/.*ssh_key://" -e "s/'//g") 197 | 198 | if [[ -z "$(tugboat droplets | grep tugshuttle-to-$region)" ]] 199 | then 200 | tugboat create tugshuttle-to-$region -r $region -s 512mb -i ubuntu-14-04-x64 -k $keyID 201 | else 202 | printf "\nExisting Droplet found in $region. Using that one...\n" 203 | fi 204 | tugboat wait tugshuttle-to-$region --state active 205 | IPtunnel=$(tugboat info tugshuttle-to-$region | grep IP4: | sed s/IP4:// | tr -d '[[:space:]]') 206 | printf "\nIP of server: $IPtunnel\n\n" 207 | keyscanout='' 208 | while [ -z "$keyscanout" ] 209 | do 210 | sleep 2 211 | keyscanout=$(ssh-keyscan $IPtunnel 2>&1 >> ~/.ssh/known_hosts) 212 | printf "Attempting to add server to known host\n\n" 213 | done 214 | sshuttle -r root@$IPtunnel 0.0.0.0/0 -vv 215 | yes | tugboat destroy tugshuttle-to-$region 216 | --------------------------------------------------------------------------------