Вы находитесь на странице: 1из 24

Setting up F5 APM with Google Authenticator

Setting up a secure VPN is easier than you might think. With F5 APM and
Google authenticator you’re up and running soon.

There is an article on devcentral doing this but I thought it could be a bit


simpler so I wrote my own. Tested on version 12 but should be more or less
applicable to version 11 as well. Please let me know if there’s any differences
and I’ll update the article.

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

Configure a functioning VPN base


1. Start with opening up the Device Wizards

2. Choose the Network Access Setup Wizard for Remote Access and click Next

3. Choose a policy name 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:

8. Enter the IP of your dns server, click Next

9. Enter the IP of the virtual server that will serve the VPN clients, click Next

10. Review your choices, click Next, then finished.


Add local authentication
Remember when we chose “No authentication in the previous step? This is
where we make sure that users needs to authenticate.

1. Go to Access Policy, Local User DB, Manage Instances

2. Click on Create New Instance

3. Choose a name, click OK

4. Then go to Access Policy, Local User DB, Manage users


5. Click on Create user, enter username, password and select the instance you just
created.

6. Now go to Access Policy, Access Profiles


7. Click on Edit in the Access Policy column

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”

10. Select your LocalDB Instance and click on “Save”


11. Your visual policy editor should look like this now:

12. Click on Apply Access Policy

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!

Adding Google authenticator to an


access policy
This part is a bit trickier, but you’ll make it.

Create the necessary iRules


These rules are shamelessly stolen from the original article:

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 }
}

Generate Google authenticator tokens


Create the VIP used to generate Google
authenticator tokens
1. Go to Local Traffic, Virtual servers

2. Click on Create

3. Give the virtual server a name, ie generate_ga_token

4. Give the virtual server an IP on your local subnet

5. Assign an HTTP profile

6. Assign an SSL profile (if you want to use SSL)

7. Assign generate_ga_code irule to the virtual server

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.

5. Check generate QR code and click on “Submit”

6. Open up your Google Authenticator app and touch the “plus sign”, select scan
barcode and scan the QR code.

7. Save the secret, we will need it soon.

Save the key in a data group list


1. Go to Local Traffic, iRules, Data Group Lists

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

Update the Virtual server with the


verification iRule
1. Go to Local Traffic, Virtual servers

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.

Update the Access Policy


So now we have a google authentication token in our phone, the irules has
been created and assigned. Now we just need to tie it all together in the access
policy.
1. Now go to Access Policy, Access Profiles

2. Click on Edit in the Access Policy column

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

7. Then click on Add New Macro


8. Name it and click Save:

9. Now click on Edit Terminals in the Macro settings

10. Click on Add Terminal

11. Name the terminal “Failure”

12. Rename the terminal called “Out” to successful

13. Click on the Set default tab and set the default to Failure.

14. Click on save

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

18. Then click on Branch rules, Add Brand Rule


Name and change the expression according to the following image (make sure
they’re in the same order)For lazy people that don’t want to type (replace the X
with the number):
expr { [mcget {session.custom.ga_result}] == X }

19. Then click Save


20. Click on the terminals and set Successful to Successful and the rest to Failure

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”

23. Now click on Apply Access Policy

Your final policy should look like this:

And now we’re done!


Some notes
 If you want to use mobile phones you might run into trouble with the default anti-
virus policy.

 You will want to have a legitimate certificate on the Virtual server serving your
VPN. The default setting is a self signed certificate.