Documente Academic
Documente Profesional
Documente Cultură
Setting up a secure VPN is easier than you might think. With F5 APM and
Google authenticator you’re up and running soon.
Contents [hide]
1 Configure a functioning VPN base
2 Add local authentication
3 Adding Google authenticator to an access policy
o 3.1 Create the necessary iRules
3.1.1 ga_code_verify
3.1.2 generate_ga_code
o 3.2 Generate Google authenticator tokens
3.2.1 Create the VIP used to generate Google authenticator tokens
3.2.2 Generate a token
o 3.3 Save the key in a data group list
o 3.4 Update the Virtual server with the verification iRule
o 3.5 Update the Access Policy
4 Some notes
2. Choose the Network Access Setup Wizard for Remote Access and click Next
4. Then select “No authentication” (we will set that up later) and Next.
5. Configure lease pool. This is a the addresses that your clients will get when
connecting to the VPN.
Enter the IP range you wish to use, and click on Add, then NextExample:
6. Click next again to configure the network access.Force all traffic through tunnel
Means that all traffic will go through the tunnelUse split tunnel for traffic
Only the traffic you want to go through the tunnel will go through the tunnel.I
would advice using split tunnel and this guide will continue based on that.
7. Choosing split tunnel will add an additional part of the form where you will choose
which traffic should go through the tunnel.
You will have to enter all your internal networks here (or the ones you want VPN
clients to access)Also add your local domain if you have one, then click
Next.Example:
9. Enter the IP of the virtual server that will serve the VPN clients, click Next
8. Now the visual policy editor should launch. Click on the plus sign between “Logon
page” and “Resource assign”.
9. Select the Authentication tab and then LocalDB Auth. Then click “Add Item”
Now you have a fully functioning VPN service (provided that you open up the
firewall, of course) and technically you could stop here. But let’s add the two
factor authentication to make it more secure!
https://devcentral.f5.com/articles/two-factor-authentication-with-google-
authenticator-and-apm
To create rules, go to Local Traffic, iRules and the click on Create. Copy the
code from below, and paste it into the iRule Window.
It’s important that you give the rule ga_code_verify the exact same name as
stated here, otherwise it won’t work later.
ga_code_verify
This rule is used to calculate and verify the Google authenticator token
1 when ACCESS_POLICY_AGENT_EVENT {
2
3 if { [ACCESS::policy agent_id] eq "ga_code_verify" } {
4 ### Google Authenticator verification settings ###
5
6 # lock the user out after x attempts for a period of x seconds
7 set static::lockout_attempts 3
8 set static::lockout_period 30
9
10 # logon page session variable name for code attempt form field
11 set static::ga_code_form_field "ga_code_attempt"
12
13 # key (shared secret) storage method: ldap, ad, or datagroup
14 set static::ga_key_storage "datagroup"
15
16 # LDAP attribute for key if storing in LDAP (optional)
17 set static::ga_key_ldap_attr "google_auth_key"
18
19 # Active Directory attribute for key if storing in AD (optional)
20 set static::ga_key_ad_attr "google_auth_key"
21
22 # datagroup name if storing key in a datagroup (optional)
23 set static::ga_key_dg "google_auth_keys"
24
25
26 #####################################
27 ### DO NOT MODIFY BELOW THIS LINE ###
28 #####################################
29
30 # set lockout table
31 set static::lockout_state_table "[virtual name]_lockout_status"
32
33 # set variables from APM logon page
34 set username [ACCESS::session data get session.logon.last.username]
35 set ga_code_attempt [ACCESS::session data get session.logon.last.$static::ga_code_form_field]
36
37 # retrieve key from specified storage
38 set ga_key ""
39
40 switch $static::ga_key_storage {
41 ldap {
42 set ga_key [ACCESS::session data get session.ldap.last.attr.$static::ga_key_ldap_attr]
43 }
44 ad {
45 set ga_key [ACCESS::session data get session.ad.last.attr.$static::ga_key_ad_attr]
46 }
47 datagroup {
48 set ga_key [class lookup $username $static::ga_key_dg]
49 }
50 }
51
52 # increment the number of login attempts for the user
53 set prev_attempts [table incr -notouch -subtable $static::lockout_state_table $username]
54 table timeout -subtable $static::lockout_state_table $username $static::lockout_period
55
56 # verification result value:
57 # 0 = successful
58 # 1 = failed
59 # 2 = no key found
60 # 3 = invalid key length
61 # 4 = user locked out
62
63 # make sure that the user isn't locked out before calculating GA code
64 if { $prev_attempts <= $static::lockout_attempts } {
65
66 # check that a valid key was retrieved, then proceed
67 if { [string length $ga_key] == 16 } {
68 # begin - Base32 decode to binary
69
70 # Base32 alphabet (see RFC 4648)
71 array set static::b32_alphabet {
72 A0 B1 C2 D3
73 E4 F5 G6 H7
74 I 8 J 9 K 10 L 11
75 M 12 N 13 O 14 P 15
76 Q 16 R 17 S 18 T 19
77 U 20 V 21 W 22 X 23
78 Y 24 Z 25 2 26 3 27
79 4 28 5 29 6 30 7 31
80 }
81
82 set ga_key [string toupper $ga_key]
83 set l [string length $ga_key]
84 set n 0
85 set j 0
86 set ga_key_bin ""
87
88 for { set i 0 } { $i < $l } { incr i } {
89 set n [expr $n << 5]
90 set n [expr $n + $static::b32_alphabet([string index $ga_key $i])]
91 set j [incr j 5]
92
93 if { $j >= 8 } {
94 set j [incr j -8]
95 append ga_key_bin [format %c [expr ($n & (0xFF << $j)) >> $j]]
96 }
97 }
98
99 # end - Base32 decode to binary
100
101 # begin - HMAC-SHA1 calculation of Google Auth token
102
103 set time [binary format W* [expr [clock seconds] / 30]]
104
105 set ipad ""
106 set opad ""
107
108 for { set j 0 } { $j < [string length $ga_key_bin] } { incr j } {
109 binary scan $ga_key_bin @${j}H2 k
110 set o [expr 0x$k ^ 0x5C]
111 set i [expr 0x$k ^ 0x36]
112 append ipad [format %c $i]
113 append opad [format %c $o]
114 }
115
116 while { $j < 64 } {
117 append ipad 6
118 append opad \\
119 incr j
120 }
121
122 binary scan [sha1 $opad[sha1 ${ipad}${time}]] H* token
123
124 # end - HMAC-SHA1 calculation of Google Auth hex token
125
126 # begin - extract code from Google Auth hex token
127
128 set offset [expr ([scan [string index $token end] %x] & 0x0F) << 1]
129 set ga_code [expr (0x[string range $token $offset [expr $offset + 7]] & 0x7FFFFFFF) % 1000000]
130 set ga_code [format %06d $ga_code]
131
132 # end - extract code from Google Auth hex token
133
134 if { $ga_code_attempt eq $ga_code } {
135 # code verification successful
136 set ga_result 0
137 } else {
138 # code verification failed
139 set ga_result 1
140 }
141 } elseif { [string length $ga_key] > 0 } {
142 # invalid key length, greater than 0, but not length not equal to 16 chars
143 set ga_result 3
144 } else {
145 # could not retrieve user's key
146 set ga_result 2
147 }
148 } else {
149 # user locked out due to too many failed attempts
150 set ga_result 4
151 }
152
153 # set code verification result in session variable
154 ACCESS::session data set session.custom.ga_result $ga_result
155 }
156 }
generate_ga_code
This rule is used to generate the tokens for google authenticator.
1 when HTTP_REQUEST {
2 set account [URI::query [HTTP::uri] "account"]
3 set domain [URI::query [HTTP::uri] "domain"]
4 set secret [URI::query [HTTP::uri] "secret"]
5 set qr_code [URI::query [HTTP::uri] "qr_code"]
6
7 if { ([HTTP::path] starts_with "/ga_secret_generator") && ($account ne "") && ($domain ne "") } {
8 if { [string length $secret] <= 10 } {
9 set secret [b64encode [md5 [expr rand()]]]
10 }
11
12 set secret [string range $secret 0 9]
13
14 array set b32_alphabet_inv {
15 0A 1B 2C 3D
16 4E 5F 6G 7H
17 8 I 9 J 10 K 11 L
18 12 M 13 N 14 O 15 P
19 16 Q 17 R 18 S 19 T
20 20 U 21 V 22 W 23 X
21 24 Y 25 Z 26 2 27 3
22 28 4 29 5 30 6 31 7
23 }
24
25 set secret_b32 ""
26 set l [string length $secret]
27 set n 0
28 set j 0
29
30 # encode loop is outlined in RFC 4648 (http://tools.ietf.org/html/rfc4648#page-8)
31 for { set i 0 } { $i < $l } { incr i } {
32 set n [expr $n << 8]
33 set n [expr $n + [scan [string index $secret $i] %c]]
34 set j [incr j 8]
35
36 while { $j >= 5 } {
37 set j [incr j -5]
38 append secret_b32 $b32_alphabet_inv([expr ($n & (0x1F << $j)) >> $j])
39 }
40 }
41
42 # pad final input group with zeros to form an integral number of 5-bit groups, then encode
43 if { $j > 0 } { append secret_b32 $b32_alphabet_inv([expr $n << (5 - $j) & 0x1F]) }
44
45 # if the final quantum is not an integral multiple of 40, append "=" padding
46 set pad [expr 8 - [string length $secret_b32] % 8]
47 if { ($pad > 0) && ($pad < 8) } { append secret_b32 [string repeat = $pad] }
48
49 set ga_qr_code_link "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/"
50 append ga_qr_code_link "$account@$domain"
51 append ga_qr_code_link "%3Fsecret%3D"
52 append ga_qr_code_link $secret_b32
53
54
55 set ga_secret_http_resp {<html>
56 <body>
57 <div align="center">
58 }
59
60 if { $qr_code eq "yes" } {
61 append ga_secret_http_resp " <img src=\"$ga_qr_code_link\" />\n"
62 }
63
64 append ga_secret_http_resp " <p>account: $account@$domain"
65 append ga_secret_http_resp "key (secret): $secret_b32</p>\n </div>\n </body>\n</html>"
66
67 HTTP::respond 200 content $ga_secret_http_resp
68 } else {
69 HTTP::respond 200 content {<html>
70 <body>
71 <h2><a href="http://goo.gl/edmb2">Google Authenticator</a> key (shared secret) generator</h2>
72 <form action="/ga_secret_generator" method="GET">
73 <table cellspacing="1" cellpadding="1" border="0">
74 <tr>
75 <th align="left">account:</th>
76 <td><input name="account" type="text" size="10"> @ <input name="domain" type="text" size="20"></td>
77 </tr>
78 <tr>
79 <th align="left">secret:</th>
80 <td><input name="secret" type="text" size="10"> *optional 10 character key (additional chars truncated), random secret used if
81 blank</td>
82 </tr>
83 <tr>
84 <th align="left">generate QR code?</th>
85 <td><input name="qr_code" type="checkbox" value="yes"> *a request will be made to Google to generate QR code</td>
86 </tr>
87 </table>
88 <input type="submit" value="Submit">
89 </form>
90 </body>
91 </html>}
92 }
}
2. Click on Create
8. Click on Finished
Generate a token
1. Surf to the address of the VIP,
ie https://generategacode.mydomain.local, or http://192.168.1.50 (if that’s your
VIP ip)
2. You should then see this page:
3. Enter the username of your user and a name for the token after the “@” sign. We
need the name to label the token in the google authenticator app.
4. Enter a secret if you like, or let the load balancer generate it for you. You will not
have to remember this secret later.
6. Open up your Google Authenticator app and touch the “plus sign”, select scan
barcode and scan the QR code.
2. Click on Create
3. Give the data group list the name google_auth_keys and add your user name and
the secret generated earlier
4. Click Finished
2. Find the the Virtual server created during the Wizard (the one the does not have
redirect in it’s name) and click on it:
3. In the following page, choose Resources and click on manage in the iRules section
4. Find the ga_code_verify rule in the right list and click on the arrows pointing left.
The rule should now be moved to the left side, to the enabled select list.
3. In the Visual policy editor, click the plus sign between LocalDB Auth and
Resource Assign
4. In the Logon tab, choose Logon Page and then Add Item
5. Change the text as marked in this picture:
6. Click Save
13. Click on the Set default tab and set the default to Failure.
15. Edit the new macro by clicking on the plus sign in the macro settings
16. Go to the General Purpose Tab, click on iRule event and then Add Item
17. Name: Google Auth verification
ID: ga_code_verify
21. Now we’re going to insert the Macro in the main policy. Click on the plus sign
between Get GA Token and resource assign
22. Click on the Macro tab and select your Verify Google Token macro. click “Add
Item”
You will want to have a legitimate certificate on the Virtual server serving your
VPN. The default setting is a self signed certificate.