Skip to content
This repository has been archived by the owner on Jan 31, 2021. It is now read-only.

Commit

Permalink
* Add IPv6 support
Browse files Browse the repository at this point in the history
* Fix: Create SSH key if one doesn't exist in account
* Fix: Retry firewall creation
* Add iptables configuration with ICMP/SSH rate limiting
* Migrate to ECDSA keys instead of RSA
  • Loading branch information
dan-v committed Nov 7, 2017
1 parent e7916f2 commit aa6db28
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 44 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@
```

### CLI Examples
* Deploy a new VPN and configure for immediate use
* Deploy a new VPN droplet and configure OSX VPN
```sh
./dosxvpn deploy --region sfo2 --auto-configure
```
* List dosxvpn VPN instances
* List dosxvpn VPN droplets
```sh
./dosxvpn ls
```
* Remove dosxvpn VPN instance
* Remove dosxvpn VPN droplet and OSX VPN profile
```sh
./dosxvpn rm --name <name>
./dosxvpn rm --name <name> --remove-profile
```

## FAQ
Expand All @@ -54,9 +54,10 @@
4. <b>How much does this cost?</b> This launches a 512MB DigitalOcean droplet that costs $5/month currently.
5. <b>What is the bandwidth limit?</b> The 512MB DigitalOcean droplet has a 1TB bandwidth limit. This does not appear to be strictly enforced.
6. <b>Where does dosxvpn store VPN configuration files?</b> You can find all deployed VPN configuration files in your ~/.dosxvpn directory.
7. <b>Are you going to support other VPS providers?</b> Not right now.
8. <b>Will this make me completely anonymous?</b> No, absolutely not. All of your traffic is going through a VPS which could be traced back to your account. You can also be tracked still with [browser fingerprinting](https://panopticlick.eff.org/), etc. Your [IP address may still leak](https://ipleak.net/) due to WebRTC, Flash, etc.
9. <b>How do I uninstall this thing on OSX?</b> You can uninstall through the Web interface, which will also remove the running droplet in your DigitalOcean account. Alternatively go to System Preferences->Network, click on dosxvpn-* and click the '-' button in the bottom left to delete the VPN. Don't forget to also remove the droplet that is deployed in your DigitalOcean account.
7. <b>How do I SSH into the deployed droplet?</b> Assuming you had public SSH keys uploaded to your DigitalOcean account when the VPN was deployed, all of those keys should be authorized for access. You can SSH using any of those keys: `ssh -i <ssh-private-key> core@<vpn-ip>`. If you had no SSH keys uploaded to your DigitalOcean account, then a temporary key was autogenerated for you and you will need to redeploy if you want SSH access.
8. <b>Are you going to support other VPS providers?</b> Not right now.
9. <b>Will this make me completely anonymous?</b> No, absolutely not. All of your traffic is going through a VPS which could be traced back to your account. You can also be tracked still with [browser fingerprinting](https://panopticlick.eff.org/), etc. Your [IP address may still leak](https://ipleak.net/) due to WebRTC, Flash, etc.
10. <b>How do I uninstall this thing on OSX?</b> You can uninstall through the Web interface, which will also remove the running droplet in your DigitalOcean account. Alternatively go to System Preferences->Network, click on dosxvpn-* and click the '-' button in the bottom left to delete the VPN. Don't forget to also remove the droplet that is deployed in your DigitalOcean account.

# Powered By
* [strongSwan](https://strongswan.org/) - IPsec-based VPN software
Expand Down
4 changes: 3 additions & 1 deletion cmd/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

var name string
var removeProfile bool

var rmCmd = &cobra.Command{
Use: "rm",
Expand All @@ -23,7 +24,7 @@ var rmCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
_, err := deploy.RemoveVPN(getCliToken(), name)
_, err := deploy.RemoveVPN(getCliToken(), name, removeProfile)
if err != nil {
log.Fatal(err)
}
Expand All @@ -34,4 +35,5 @@ var rmCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(rmCmd)
rmCmd.Flags().StringVar(&name, "name", "", "Name of droplet to remove")
rmCmd.Flags().BoolVar(&removeProfile, "remove-profile", false, "Remove VPN profile as well (only for OSX).")
}
36 changes: 29 additions & 7 deletions deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import (
)

const (
DropletBaseName = "dosxvpn"
DropletImage = "coreos-beta"
DropletSize = "512mb"
DropletBaseName = "dosxvpn"
DropletImage = "coreos-beta"
DropletSize = "512mb"
AutogeneratedSSHKey = "dosxvpn"
)

var (
Expand Down Expand Up @@ -91,7 +92,21 @@ func (d *Deployment) Run() error {
log.Println("Getting initial IP...")
initialPublicIP, _ := getPublicIp()
d.InitialPublicIP = initialPublicIP
log.Println("Initial IP is", d.InitialPublicIP)
log.Println("Initial IP is:", d.InitialPublicIP)

log.Println("Getting account SSH keys...")
accountSSHKeys, err := d.doClient.GetAccountSSHKeys()
if err != nil {
log.Fatal(err)
}
if len(accountSSHKeys) == 0 {
log.Println("Did not find an SSH key. Generating an SSH key for account...")
_, err := d.doClient.CreateSSHKey(AutogeneratedSSHKey, d.sshClient.GetPublicKey())
if err != nil {
log.Fatal(err)
}
}
log.Println("Finished getting account SSH keys...")

log.Println("Creating droplet...")
dropletID, err := d.doClient.CreateDroplet(d.Name, d.Region, DropletSize, d.userData, DropletImage)
Expand All @@ -111,9 +126,16 @@ func (d *Deployment) Run() error {
d.VPNIPAddress = d.dropletIP

log.Println("Creating firewall...")
err = d.doClient.CreateFirewall(d.Name, d.dropletID)
if err != nil {
log.Fatal(err)
for attempt := 0; attempt < 5; attempt++ {
time.Sleep(time.Duration(attempt) * time.Second)

err = d.doClient.CreateFirewall(d.Name, d.dropletID)
if err != nil {
continue
}
if attempt >= 5 {
log.Fatalf("Timeout waiting to create firewall for droplet %v", dropletID)
}
}
log.Println("Finished creating firewall...")

Expand Down
14 changes: 8 additions & 6 deletions deploy/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/dan-v/dosxvpn/vpn"
)

func RemoveVPN(token, name string) ([]string, error) {
func RemoveVPN(token, name string, removeProfile bool) ([]string, error) {
log.Printf("Listing droplets..")
client := doclient.New(token)
droplets, err := client.ListDroplets()
Expand Down Expand Up @@ -41,12 +41,14 @@ func RemoveVPN(token, name string) ([]string, error) {
}
}

log.Printf("Removing OSX VPN profile for %s", name)
err = vpn.OSXRemoveVPN(name)
if err != nil {
log.Printf("Failed to remove OSX VPN profile for %s. %v", name, err)
if removeProfile {
log.Printf("Removing OSX VPN profile for %s", name)
err = vpn.OSXRemoveVPN(name)
if err != nil {
log.Printf("Failed to remove OSX VPN profile for %s. %v", name, err)
}
log.Printf("Finished removing OSX VPN profile for %s", name)
}
log.Printf("Finished removing OSX VPN profile for %s", name)

return removedDroplets, nil
}
27 changes: 24 additions & 3 deletions doclient/do.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ func (c *Client) WaitForDropletIP(dropletID int) (ip string, err error) {
return ip, nil
}

func (c *Client) CreateSSHKey(name, publicKey string) (id int, err error) {
createRequest := &godo.KeyCreateRequest{
Name: "dosxvpn",
PublicKey: publicKey,
}
key, _, err := c.doClient.Keys.Create(context.TODO(), createRequest)
if err != nil {
return 0, err
}
return key.ID, nil
}

func (c *Client) CreateDroplet(name, region, size, userData, image string) (id int, err error) {
createRequest := &godo.DropletCreateRequest{
Name: name,
Expand All @@ -65,9 +77,13 @@ func (c *Client) CreateDroplet(name, region, size, userData, image string) (id i
Image: godo.DropletCreateImage{
Slug: image,
},
IPv6: true,
}

accountSSHKeys, err := c.getAccountSSHKeys()
accountSSHKeys, err := c.GetAccountSSHKeys()
if err != nil {
return 0, err
}
for _, key := range accountSSHKeys {
keyToAdd := godo.DropletCreateSSHKey{ID: key.ID}
createRequest.SSHKeys = append(createRequest.SSHKeys, keyToAdd)
Expand Down Expand Up @@ -138,8 +154,7 @@ func (c *Client) DeleteFirewall(firewallID string) error {
return nil
}

func (c *Client) getAccountSSHKeys() ([]godo.Key, error) {
// Query all the SSH keys on the account so we can include them in the droplet.
func (c *Client) GetAccountSSHKeys() ([]godo.Key, error) {
keys, _, err := c.doClient.Keys.List(context.TODO(), nil)
if err != nil {
return nil, err
Expand All @@ -149,6 +164,12 @@ func (c *Client) getAccountSSHKeys() ([]godo.Key, error) {

func (c *Client) generateInboundFirewallRules() []godo.InboundRule {
return []godo.InboundRule{
{
Protocol: "icmp",
Sources: &godo.Sources{
Addresses: []string{"0.0.0.0/0", "::/0"},
},
},
{
Protocol: "tcp",
PortRange: "22",
Expand Down
5 changes: 2 additions & 3 deletions genconfig/android_template.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package genconfig

const androidConfigTemplate = `
{
const androidConfigTemplate = `{
"uuid": "{{.UUID}}",
"name": "{{.Name}}",
"type": "ikev2-cert",
Expand All @@ -13,7 +12,7 @@ const androidConfigTemplate = `
"block-ipv6": true
},
"local": {
"id": "client@{{.IP}}",
"id": "{{.IP}}",
"p12": "{{.PrivateKey}}"
},
"mtu": 1280
Expand Down
27 changes: 15 additions & 12 deletions genconfig/apple_template.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package genconfig

const mobileConfigTemplate = `
<?xml version="1.0" encoding="UTF-8"?>
const mobileConfigTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
Expand Down Expand Up @@ -65,14 +64,14 @@ const mobileConfigTemplate = `
<string>Certificate</string>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>2</integer>
<key>DiffieHellmanGroup</key>
<integer>19</integer>
<key>EncryptionAlgorithm</key>
<string>3DES</string>
<string>AES-128-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA1-96</string>
<string>SHA2-512</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
<integer>20</integer>
</dict>
<key>DeadPeerDetectionRate</key>
<string>Medium</string>
Expand All @@ -87,18 +86,22 @@ const mobileConfigTemplate = `
<key>IKESecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>2</integer>
<integer>19</integer>
<key>EncryptionAlgorithm</key>
<string>3DES</string>
<string>AES-128-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA1-96</string>
<string>SHA2-512</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
<integer>20</integer>
</dict>
<key>LocalIdentifier</key>
<string>client@{{.IP}}</string>
<string>{{.IP}}</string>
<key>PayloadCertificateUUID</key>
<string>{{.UUID1}}</string>
<key>CertificateType</key>
<string>ECDSA256</string>
<key>ServerCertificateIssuerCommonName</key>
<string>{{.IP}}</string>
<key>RemoteAddress</key>
<string>{{.IP}}</string>
<key>RemoteIdentifier</key>
Expand Down
81 changes: 78 additions & 3 deletions services/coreos/coreos.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,90 @@ write_files:
AllowUsers core
PasswordAuthentication no
ChallengeResponseAuthentication no
- path: /var/lib/iptables/rules-save
permissions: 0644
owner: root:root
content: |
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.99.0/24 -m policy --pol none --dir out -j MASQUERADE
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p esp -j ACCEPT
-A INPUT -p ah -j ACCEPT
-A INPUT -p ipencap -m policy --dir in --pol ipsec --proto esp -j ACCEPT
-A INPUT -p icmp --icmp-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --rttl --name SSH -j DROP
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
-A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
-A INPUT -d 1.1.1.1 -p udp -j ACCEPT
-A INPUT -d 1.1.1.1 -p tcp -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -m conntrack --ctstate NEW -s 192.168.99.0/24 -m policy --pol ipsec --dir in -j ACCEPT
COMMIT
- path: /var/lib/ip6tables/rules-save
permissions: 0644
owner: root:root
content: |
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s fd9d:bc11:4020::/48 -m policy --pol none --dir out -j MASQUERADE
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:ICMPV6-CHECK - [0:0]
:ICMPV6-CHECK-LOG - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p esp -j ACCEPT
-A INPUT -m ah -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type redirect -m hl --hl-eq 255 -j ACCEPT
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --rttl --name SSH -j DROP
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
-A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
-A INPUT -d fd9d:bc11:4020::/48 -p udp -j ACCEPT
-A INPUT -d fd9d:bc11:4020::/48 -p tcp -j ACCEPT
-A FORWARD -j ICMPV6-CHECK
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -m conntrack --ctstate NEW -s fd9d:bc11:4020::/48 -m policy --pol ipsec --dir in -j ACCEPT
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type router-solicitation -j ICMPV6-CHECK-LOG
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type router-advertisement -j ICMPV6-CHECK-LOG
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type neighbor-solicitation -j ICMPV6-CHECK-LOG
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type neighbor-advertisement -j ICMPV6-CHECK-LOG
-A ICMPV6-CHECK-LOG -j LOG --log-prefix "ICMPV6-CHECK-LOG DROP "
-A ICMPV6-CHECK-LOG -j DROP
COMMIT
coreos:
update:
reboot-strategy: reboot
locksmith:
window-start: 10:00
window-length: 1h
units:
- name: "etcd2.service"
command: "start"
- name: etcd2.service
command: start
- name: iptables-restore.service
enable: true
command: start
- name: ip6tables-restore.service
enable: true
command: start
- name: dummy-interface.service
command: start
content: |
Expand All @@ -36,6 +111,6 @@ coreos:
[Service]
User=root
Type=oneshot
ExecStart=/bin/sh -c "modprobe dummy; ip link set dummy0 up; ifconfig dummy0 1.1.1.1/32; echo 1.1.1.1 pi.hole >> /etc/hosts"
ExecStart=/bin/sh -c "modprobe dummy; ip link set dummy0 up; ifconfig dummy0 1.1.1.1/32"
`
}
Loading

0 comments on commit aa6db28

Please sign in to comment.