Forum Discussion

mahnsc's avatar
mahnsc
Icon for Nimbostratus rankNimbostratus
Apr 06, 2012

Multi-Conditional iRule using Basic Auth and 9.4.3

I received a request recently regarding a customer of my customer who has multiple authentication and authorization requirements for web service requests. The requirements were broken down as follows:

1. Requests only matching URI named "/uri/" should be validated.

2. Requests with a Content-Type of "text/xml" should be validated.

3. Requests must only be allowed from IP address w.x.y.z

4. Requests must be protected with basic auth userid/password

My OS version is 9.4.3

I have written the following rule, which intends to follow these guidelines but I have doubts that line 7 will do what I'm hoping it will do. Building off of the rule from the HTTP Basic Access Authentication iRule Style article, this is what I currently have:

when HTTP_REQUEST {
  if {[HTTP::uri] contains "/uri/"} {
    if { [HTTP::header "Content-Type"] contains "text/xml" } {
      if { ! ( [IP::addr [IP::client_addr] equals w.x.y.z/m.a.s.k] ) } {
        if { [HTTP::header exists Authorization] } {
          binary scan [md5 [HTTP::password]] H* password
            if { [matchclass [HTTP::username] equals $users] equals $password } {
              log local0. "User [HTTP::username] authorized to access /uri/"
              } elseif { [string length [HTTP::password]] != 0} {
                  log local0. "User [HTTP::username] not authorized to access /uri/"
                  HTTP::respond 401
                } else {
                  HTTP::respond 401
                }
        }
      HTTP::respond 403
      log local0. "[IP::client_addr]:[TCP::client_port]: Sending 403 Response"
      }
    }
  }
}

Surprisingly, this saves with no parsing errors but since this is 9.4.3, class lookup does not work so I needed to convert this to using match class and now I'm afraid that this won't actually do what I want it to do, which is check to see if the userid is using a valid password.

I'm also not really certain whether the HTTP::respond 401 after the 'else' is needed but I have it in there now simply to include the 'else'.

Can someone take a few moments to look this over and let me know where I need to make some changes?
  • mine is 10.2.3. for 9.4.3, can you try findclass [HTTP::username] users " " instead of class lookup?

    findclass wiki

    https://devcentral.f5.com/wiki/iRules.findclass.ashx

    [root@ve1023:Active] config  b virtual bar list
    virtual bar {
       snat automap
       pool foo
       destination 172.28.19.79:80
       ip protocol 6
       rules myrule
       profiles {
          http {}
          tcp {}
       }
    }
    [root@ve1023:Active] config  b class users list
    class users {
       "foo" { "acbd18db4cc2f85cedef654fccc4a4d8" }
    }
    [root@ve1023:Active] config  b rule myrule list
    rule myrule {
       when HTTP_REQUEST {
       if { [HTTP::uri] contains "/uri/" } {
          if { [HTTP::header "Content-Type"] contains "text/xml" } {
             if { ! ( [IP::addr [IP::client_addr] equals 1.1.1.0/24] ) } {
                binary scan [md5 [HTTP::password]] H* password
                if { [class lookup [HTTP::username] users] equals $password } {
                   log local0. "User [HTTP::username] authorized to access /uri/"
                } else {
                   if { [string length [HTTP::password]] != 0 } {
                      log local0. "User [HTTP::username] not authorized to access /uri/"
                   }
                   HTTP::respond 401
                   log local0. "[IP::client_addr]:[TCP::client_port]: Sending 401 Response"
                }
             }
          }
       }
    }
    }
    
    1
    [root@ve1023:Active] config  curl -i http://172.28.19.79/uri/ -H "Content-Type: text/xml" -d "test=abc"
    HTTP/1.0 401 Unauthorized
    WWW-Authenticate: Basic realm=""
    Server: BigIP
    Connection: Keep-Alive
    Content-Length: 0
    
    [root@ve1023:Active] config  tail -f /var/log/ltm
    Apr  5 21:23:16 local/tmm info tmm[4797]: Rule myrule : 172.28.19.80:43504: Sending 401 Response
    
    2
    [root@ve1023:Active] config  curl -i -u foo:foo http://172.28.19.79/uri/ -H "Content-Type: text/xml" -d "test=abc"
    HTTP/1.1 404 Not Found
    Date: Fri, 06 Apr 2012 04:24:54 GMT
    Server: Apache/2.2.3 (CentOS)
    Content-Length: 279
    Content-Type: text/html; charset=iso-8859-1
    
    ...snipped...
    
    [root@ve1023:Active] config  tail -f /var/log/ltm
    Apr  5 21:24:25 local/tmm info tmm[4797]: Rule myrule : User foo authorized to access /uri/
    
    3
    [root@ve1023:Active] config  curl -i -u foo:wrong http://172.28.19.79/uri/ -H "Content-Type: text/xml" -d "test=abc"
    HTTP/1.0 401 Unauthorized
    WWW-Authenticate: Basic realm=""
    Server: BigIP
    Connection: Keep-Alive
    Content-Length: 0
    
    [root@ve1023:Active] config  tail -f /var/log/ltm
    Apr  5 21:24:59 local/tmm info tmm[4797]: Rule myrule : User foo not authorized to access /uri/
    Apr  5 21:24:59 local/tmm info tmm[4797]: Rule myrule : 172.28.19.80:43591: Sending 401 Response
    
  • mahnsc's avatar
    mahnsc
    Icon for Nimbostratus rankNimbostratus
    I'm going to have an opportunity to test this a little later on today but I was curious about the format of your class. I created a class in the Data Group List editor of the web console as a string that follows a format of " userid:md5sum_of_password " but yours looks like it has a format of: "userid" {"md5sum_of_password" }. Is your entry literally a string on a single line of: "foo" { "acbd18db4cc2f85cedef654fccc4a4d8" }

     

     

    Using your ID as an example, my class's entry is a single line string like: foo:acbd18db4cc2f85cedef654fccc4a4d8
  • if you use colon (:) as separator in findclass (findclass [HTTP::username] $::users ":"), data group should look like this.

    [root@B3600-R67-S43:Active] config  b class users list
    class users {
       "foo:acbd18db4cc2f85cedef654fccc4a4d8"
    }
    
  • in 9.4.3, $:: is required to reference data group.

     

     

    CMP Compatibility (Class / Data Group List References section)

     

    https://devcentral.f5.com/wiki/iRules.cmpcompatibility.ashx
  • mahnsc's avatar
    mahnsc
    Icon for Nimbostratus rankNimbostratus
    Great. I had already made the edit to the separator so this is good news. Thanks again for your time!
  • mahnsc's avatar
    mahnsc
    Icon for Nimbostratus rankNimbostratus
    So, things ended up not working too well for me. There were a couple issues. The first was that the client was sending a base64 encoded value for userid and password in the Authorization header, which my rule above doesn't seem to account to for. Do I need to decode this value first prior to doing the binary scan or am I just better off with storing the base64 value in the data group?

     

     

    The second problem is probably due to me missing an else somewhere but both unauthenticated AND authenticated requests were working fine when I tested this tonight. Requests lacking an Authorization header should have been rejected with a 401 status code.

     

     

    If anyone has some time to spare, can you give me some pointers to help me figure this out? With the poor testing results with authentication, we didn't even get around to testing the other conditions.
  • mahnsc's avatar
    mahnsc
    Icon for Nimbostratus rankNimbostratus
    Disregard the first question about decoding userid and password now that I've taken the time to re-read the wiki entries on HTTP::username and HTTP::password.