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

Commit aa6db28

Browse files
committed
* Add IPv6 support
* 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
1 parent e7916f2 commit aa6db28

File tree

12 files changed

+204
-44
lines changed

12 files changed

+204
-44
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@
3434
```
3535

3636
### CLI Examples
37-
* Deploy a new VPN and configure for immediate use
37+
* Deploy a new VPN droplet and configure OSX VPN
3838
```sh
3939
./dosxvpn deploy --region sfo2 --auto-configure
4040
```
41-
* List dosxvpn VPN instances
41+
* List dosxvpn VPN droplets
4242
```sh
4343
./dosxvpn ls
4444
```
45-
* Remove dosxvpn VPN instance
45+
* Remove dosxvpn VPN droplet and OSX VPN profile
4646
```sh
47-
./dosxvpn rm --name <name>
47+
./dosxvpn rm --name <name> --remove-profile
4848
```
4949

5050
## FAQ
@@ -54,9 +54,10 @@
5454
4. <b>How much does this cost?</b> This launches a 512MB DigitalOcean droplet that costs $5/month currently.
5555
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.
5656
6. <b>Where does dosxvpn store VPN configuration files?</b> You can find all deployed VPN configuration files in your ~/.dosxvpn directory.
57-
7. <b>Are you going to support other VPS providers?</b> Not right now.
58-
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.
59-
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.
57+
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.
58+
8. <b>Are you going to support other VPS providers?</b> Not right now.
59+
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.
60+
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.
6061

6162
# Powered By
6263
* [strongSwan](https://strongswan.org/) - IPsec-based VPN software

cmd/rm.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
var name string
12+
var removeProfile bool
1213

1314
var rmCmd = &cobra.Command{
1415
Use: "rm",
@@ -23,7 +24,7 @@ var rmCmd = &cobra.Command{
2324
return nil
2425
},
2526
Run: func(cmd *cobra.Command, args []string) {
26-
_, err := deploy.RemoveVPN(getCliToken(), name)
27+
_, err := deploy.RemoveVPN(getCliToken(), name, removeProfile)
2728
if err != nil {
2829
log.Fatal(err)
2930
}
@@ -34,4 +35,5 @@ var rmCmd = &cobra.Command{
3435
func init() {
3536
RootCmd.AddCommand(rmCmd)
3637
rmCmd.Flags().StringVar(&name, "name", "", "Name of droplet to remove")
38+
rmCmd.Flags().BoolVar(&removeProfile, "remove-profile", false, "Remove VPN profile as well (only for OSX).")
3739
}

deploy/deploy.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ import (
2626
)
2727

2828
const (
29-
DropletBaseName = "dosxvpn"
30-
DropletImage = "coreos-beta"
31-
DropletSize = "512mb"
29+
DropletBaseName = "dosxvpn"
30+
DropletImage = "coreos-beta"
31+
DropletSize = "512mb"
32+
AutogeneratedSSHKey = "dosxvpn"
3233
)
3334

3435
var (
@@ -91,7 +92,21 @@ func (d *Deployment) Run() error {
9192
log.Println("Getting initial IP...")
9293
initialPublicIP, _ := getPublicIp()
9394
d.InitialPublicIP = initialPublicIP
94-
log.Println("Initial IP is", d.InitialPublicIP)
95+
log.Println("Initial IP is:", d.InitialPublicIP)
96+
97+
log.Println("Getting account SSH keys...")
98+
accountSSHKeys, err := d.doClient.GetAccountSSHKeys()
99+
if err != nil {
100+
log.Fatal(err)
101+
}
102+
if len(accountSSHKeys) == 0 {
103+
log.Println("Did not find an SSH key. Generating an SSH key for account...")
104+
_, err := d.doClient.CreateSSHKey(AutogeneratedSSHKey, d.sshClient.GetPublicKey())
105+
if err != nil {
106+
log.Fatal(err)
107+
}
108+
}
109+
log.Println("Finished getting account SSH keys...")
95110

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

113128
log.Println("Creating firewall...")
114-
err = d.doClient.CreateFirewall(d.Name, d.dropletID)
115-
if err != nil {
116-
log.Fatal(err)
129+
for attempt := 0; attempt < 5; attempt++ {
130+
time.Sleep(time.Duration(attempt) * time.Second)
131+
132+
err = d.doClient.CreateFirewall(d.Name, d.dropletID)
133+
if err != nil {
134+
continue
135+
}
136+
if attempt >= 5 {
137+
log.Fatalf("Timeout waiting to create firewall for droplet %v", dropletID)
138+
}
117139
}
118140
log.Println("Finished creating firewall...")
119141

deploy/rm.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/dan-v/dosxvpn/vpn"
99
)
1010

11-
func RemoveVPN(token, name string) ([]string, error) {
11+
func RemoveVPN(token, name string, removeProfile bool) ([]string, error) {
1212
log.Printf("Listing droplets..")
1313
client := doclient.New(token)
1414
droplets, err := client.ListDroplets()
@@ -41,12 +41,14 @@ func RemoveVPN(token, name string) ([]string, error) {
4141
}
4242
}
4343

44-
log.Printf("Removing OSX VPN profile for %s", name)
45-
err = vpn.OSXRemoveVPN(name)
46-
if err != nil {
47-
log.Printf("Failed to remove OSX VPN profile for %s. %v", name, err)
44+
if removeProfile {
45+
log.Printf("Removing OSX VPN profile for %s", name)
46+
err = vpn.OSXRemoveVPN(name)
47+
if err != nil {
48+
log.Printf("Failed to remove OSX VPN profile for %s. %v", name, err)
49+
}
50+
log.Printf("Finished removing OSX VPN profile for %s", name)
4851
}
49-
log.Printf("Finished removing OSX VPN profile for %s", name)
5052

5153
return removedDroplets, nil
5254
}

doclient/do.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ func (c *Client) WaitForDropletIP(dropletID int) (ip string, err error) {
5656
return ip, nil
5757
}
5858

59+
func (c *Client) CreateSSHKey(name, publicKey string) (id int, err error) {
60+
createRequest := &godo.KeyCreateRequest{
61+
Name: "dosxvpn",
62+
PublicKey: publicKey,
63+
}
64+
key, _, err := c.doClient.Keys.Create(context.TODO(), createRequest)
65+
if err != nil {
66+
return 0, err
67+
}
68+
return key.ID, nil
69+
}
70+
5971
func (c *Client) CreateDroplet(name, region, size, userData, image string) (id int, err error) {
6072
createRequest := &godo.DropletCreateRequest{
6173
Name: name,
@@ -65,9 +77,13 @@ func (c *Client) CreateDroplet(name, region, size, userData, image string) (id i
6577
Image: godo.DropletCreateImage{
6678
Slug: image,
6779
},
80+
IPv6: true,
6881
}
6982

70-
accountSSHKeys, err := c.getAccountSSHKeys()
83+
accountSSHKeys, err := c.GetAccountSSHKeys()
84+
if err != nil {
85+
return 0, err
86+
}
7187
for _, key := range accountSSHKeys {
7288
keyToAdd := godo.DropletCreateSSHKey{ID: key.ID}
7389
createRequest.SSHKeys = append(createRequest.SSHKeys, keyToAdd)
@@ -138,8 +154,7 @@ func (c *Client) DeleteFirewall(firewallID string) error {
138154
return nil
139155
}
140156

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

150165
func (c *Client) generateInboundFirewallRules() []godo.InboundRule {
151166
return []godo.InboundRule{
167+
{
168+
Protocol: "icmp",
169+
Sources: &godo.Sources{
170+
Addresses: []string{"0.0.0.0/0", "::/0"},
171+
},
172+
},
152173
{
153174
Protocol: "tcp",
154175
PortRange: "22",

genconfig/android_template.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package genconfig
22

3-
const androidConfigTemplate = `
4-
{
3+
const androidConfigTemplate = `{
54
"uuid": "{{.UUID}}",
65
"name": "{{.Name}}",
76
"type": "ikev2-cert",
@@ -13,7 +12,7 @@ const androidConfigTemplate = `
1312
"block-ipv6": true
1413
},
1514
"local": {
16-
"id": "client@{{.IP}}",
15+
"id": "{{.IP}}",
1716
"p12": "{{.PrivateKey}}"
1817
},
1918
"mtu": 1280

genconfig/apple_template.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package genconfig
22

3-
const mobileConfigTemplate = `
4-
<?xml version="1.0" encoding="UTF-8"?>
3+
const mobileConfigTemplate = `<?xml version="1.0" encoding="UTF-8"?>
54
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65
<plist version="1.0">
76
<dict>
@@ -65,14 +64,14 @@ const mobileConfigTemplate = `
6564
<string>Certificate</string>
6665
<key>ChildSecurityAssociationParameters</key>
6766
<dict>
68-
<key>DiffieHellmanGroup</key>
69-
<integer>2</integer>
67+
<key>DiffieHellmanGroup</key>
68+
<integer>19</integer>
7069
<key>EncryptionAlgorithm</key>
71-
<string>3DES</string>
70+
<string>AES-128-GCM</string>
7271
<key>IntegrityAlgorithm</key>
73-
<string>SHA1-96</string>
72+
<string>SHA2-512</string>
7473
<key>LifeTimeInMinutes</key>
75-
<integer>1440</integer>
74+
<integer>20</integer>
7675
</dict>
7776
<key>DeadPeerDetectionRate</key>
7877
<string>Medium</string>
@@ -87,18 +86,22 @@ const mobileConfigTemplate = `
8786
<key>IKESecurityAssociationParameters</key>
8887
<dict>
8988
<key>DiffieHellmanGroup</key>
90-
<integer>2</integer>
89+
<integer>19</integer>
9190
<key>EncryptionAlgorithm</key>
92-
<string>3DES</string>
91+
<string>AES-128-GCM</string>
9392
<key>IntegrityAlgorithm</key>
94-
<string>SHA1-96</string>
93+
<string>SHA2-512</string>
9594
<key>LifeTimeInMinutes</key>
96-
<integer>1440</integer>
95+
<integer>20</integer>
9796
</dict>
9897
<key>LocalIdentifier</key>
99-
<string>client@{{.IP}}</string>
98+
<string>{{.IP}}</string>
10099
<key>PayloadCertificateUUID</key>
101100
<string>{{.UUID1}}</string>
101+
<key>CertificateType</key>
102+
<string>ECDSA256</string>
103+
<key>ServerCertificateIssuerCommonName</key>
104+
<string>{{.IP}}</string>
102105
<key>RemoteAddress</key>
103106
<string>{{.IP}}</string>
104107
<key>RemoteIdentifier</key>

services/coreos/coreos.go

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,90 @@ write_files:
1818
AllowUsers core
1919
PasswordAuthentication no
2020
ChallengeResponseAuthentication no
21+
- path: /var/lib/iptables/rules-save
22+
permissions: 0644
23+
owner: root:root
24+
content: |
25+
*nat
26+
:PREROUTING ACCEPT [0:0]
27+
:POSTROUTING ACCEPT [0:0]
28+
-A POSTROUTING -s 192.168.99.0/24 -m policy --pol none --dir out -j MASQUERADE
29+
COMMIT
30+
*filter
31+
:INPUT DROP [0:0]
32+
:FORWARD DROP [0:0]
33+
:OUTPUT ACCEPT [0:0]
34+
-A INPUT -i lo -j ACCEPT
35+
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
36+
-A INPUT -p esp -j ACCEPT
37+
-A INPUT -p ah -j ACCEPT
38+
-A INPUT -p ipencap -m policy --dir in --pol ipsec --proto esp -j ACCEPT
39+
-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
40+
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
41+
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --rttl --name SSH -j DROP
42+
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
43+
-A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
44+
-A INPUT -d 1.1.1.1 -p udp -j ACCEPT
45+
-A INPUT -d 1.1.1.1 -p tcp -j ACCEPT
46+
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
47+
-A FORWARD -m conntrack --ctstate NEW -s 192.168.99.0/24 -m policy --pol ipsec --dir in -j ACCEPT
48+
COMMIT
49+
- path: /var/lib/ip6tables/rules-save
50+
permissions: 0644
51+
owner: root:root
52+
content: |
53+
*nat
54+
:PREROUTING ACCEPT [0:0]
55+
:POSTROUTING ACCEPT [0:0]
56+
-A POSTROUTING -s fd9d:bc11:4020::/48 -m policy --pol none --dir out -j MASQUERADE
57+
COMMIT
58+
*filter
59+
:INPUT DROP [0:0]
60+
:FORWARD DROP [0:0]
61+
:OUTPUT ACCEPT [0:0]
62+
:ICMPV6-CHECK - [0:0]
63+
:ICMPV6-CHECK-LOG - [0:0]
64+
-A INPUT -i lo -j ACCEPT
65+
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
66+
-A INPUT -p esp -j ACCEPT
67+
-A INPUT -m ah -j ACCEPT
68+
-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
69+
-A INPUT -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT
70+
-A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
71+
-A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
72+
-A INPUT -p icmpv6 --icmpv6-type redirect -m hl --hl-eq 255 -j ACCEPT
73+
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
74+
-A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --rttl --name SSH -j DROP
75+
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
76+
-A INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT
77+
-A INPUT -d fd9d:bc11:4020::/48 -p udp -j ACCEPT
78+
-A INPUT -d fd9d:bc11:4020::/48 -p tcp -j ACCEPT
79+
-A FORWARD -j ICMPV6-CHECK
80+
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
81+
-A FORWARD -m conntrack --ctstate NEW -s fd9d:bc11:4020::/48 -m policy --pol ipsec --dir in -j ACCEPT
82+
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type router-solicitation -j ICMPV6-CHECK-LOG
83+
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type router-advertisement -j ICMPV6-CHECK-LOG
84+
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type neighbor-solicitation -j ICMPV6-CHECK-LOG
85+
-A ICMPV6-CHECK -p icmpv6 -m hl ! --hl-eq 255 --icmpv6-type neighbor-advertisement -j ICMPV6-CHECK-LOG
86+
-A ICMPV6-CHECK-LOG -j LOG --log-prefix "ICMPV6-CHECK-LOG DROP "
87+
-A ICMPV6-CHECK-LOG -j DROP
88+
COMMIT
89+
2190
coreos:
2291
update:
2392
reboot-strategy: reboot
2493
locksmith:
2594
window-start: 10:00
2695
window-length: 1h
2796
units:
28-
- name: "etcd2.service"
29-
command: "start"
97+
- name: etcd2.service
98+
command: start
99+
- name: iptables-restore.service
100+
enable: true
101+
command: start
102+
- name: ip6tables-restore.service
103+
enable: true
104+
command: start
30105
- name: dummy-interface.service
31106
command: start
32107
content: |
@@ -36,6 +111,6 @@ coreos:
36111
[Service]
37112
User=root
38113
Type=oneshot
39-
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"
114+
ExecStart=/bin/sh -c "modprobe dummy; ip link set dummy0 up; ifconfig dummy0 1.1.1.1/32"
40115
`
41116
}

0 commit comments

Comments
 (0)