Forum Discussion

escman's avatar
escman
Icon for Cirrus rankCirrus
Oct 20, 2022
Solved

Help with tcp/http payload irule

Hi sirs,

I wrote the following irule:

when CLIENT_ACCEPTED {
  TCP::collect
}
when CLIENT_DATA {
  set SECURE_CONN 0
  set payload [TCP::payload]
  log local0. "Payload: $payload"
  if { $payload contains "SECURE_CONN" } {
    set SECURE_CONN 1
    log local0. "SECURE_CONN found!- $SECURE_CONN"
  }
}
when HTTP_RESPONSE {
  if { $SECURE_CONN == 1 } {
    STREAM::expression {@http://domain.com@https://domain.com@} 
    STREAM::enable
  } else { STREAM::disable }
}

My goal is to look at the http SECURE_CONN header (custom header created at the front end load balancer) and perform the http > https stream only for connections that have this value in the tcp payload (I cant use HTTP events due related virtual server already has some policy rules at request event and when I tried to use has_responded it irule dont occur), however, it is not happening as I expect, some connections are converted to https but others are not.. I think it may be related to the size of the payload I am collecting? do I need to collect a larger amount of payload?

thank you guys

  • Glad to assist, and thanks for that, totally my privilege to invest in the community!

    Have you looked at the POLICY::rules command? If you use the matched attribute with contains and your conflicting policy, you might be able to bypass that error and proceed with the more elegant HTTP solution.

    So maybe something like this would work?

    when HTTP_REQUEST {
        if { [POLICY::rules matched] contains "your conflicting executed policy rule" } {
            return
        } else {
            # ... your logic here in HTTP instead of TCP
        }
    }

    I've never used it personally but that's the way I'd try to make this work before attempting it at the TCP layer for two reasons: 1) TCP inspection is more expensive, especially since you have access to the HTTP since it's applied and 2) Division of logic into multiple layers complicates your workflow.

    Regarding the rules as written, I would just focus on the match instead of trying to handle both the match and unmatch condition. So on your front-end 80 vip, I'd just remove the custom header if exists and eliminate the last two lines that aren't brackets:

     

    ## WAF FRONT-END VIRTUAL SERVER 80
    when HTTP_REQUEST {
      ## AVOID PREVIOUS CUSTOM_HEADER HEADER
      if {[HTTP::header exists CUSTOM_HEADER]}{
        HTTP::header remove "CUSTOM_HEADER"
      }
    }

     

    and then on your backend something like:

     

    when RULE_INIT {
      set static::debug_secure_conn 1
    }
    when CLIENT_ACCEPTED {
      TCP::collect
    }
    when CLIENT_DATA {
      set payload [TCP::payload]
      if { $static::debug_secure_conn } {
        log local0. "Payload: $payload"
      }
      if { $payload contains "SECURE_CONN" } {
        set SECURE_CONNBSEG 1
        if { $static::debug_secure_conn } {
          log local0. "SECURE_CONN found - $SECURE_CONN"
        }
      } 
      TCP::release
    }
    when HTTP_REQUEST_RELEASE {
      STREAM::disable
    }
    when HTTP_RESPONSE {
      if { [info exists SECURE_CONNBSEG] } {
        if { $static::debug_secure_conn } {
          log local0. "Value SECURE = $SECURE_CONN"
        }
        STREAM::expression {@http://domain.com@https://domain.com@}
        STREAM::enable
      }
    }

     

    Not at all tested, but food for thought.

5 Replies

  • Hi escman, if the front end is inserting an HTTP header, is there a reason you aren't reading that over doing a TCP::collect? Maybe try something like this:

    when HTTP_REQUEST {
      if { [HTTP::header exists SECURE_CONN] {
       set SECURE_CONN 1
       STREAM::disable
       HTTP::header remove "Accept-Encoding"
    }
    when HTTP_RESPONSE {
       if {([HTTP::header value Content-Type] contains "text") && ([info exists SECURE_CONN])}{
          STREAM::expression {@http://@https://@}
          STREAM::enable
       }
    }

    untested but this should get you started. Post back if you have a need for TCP level inspection and we can walk through that as well.

    • escman's avatar
      escman
      Icon for Cirrus rankCirrus

      Hi JRahm,

      what a privilege to receive your help, I'm a big fan of your videos on youtube hehe 🙂

      Unfortunately I can't use HTTP_REQUEST events (like HTTP::collect or HTTP::header) in this iRule because I already have a LTM policy associated with the virtual server that performs http request events, and when I add the iRule using HTTP events I get ERR_NOT_SUPPORTED errors (has_responded command bypass this irule), so I have to consider performing the HTTP inspection at the TCP layer.

      Now it's working as I expected (I had to rewrite the else { STREAM::disable } condition), however, I don't know if the iRules are well written.. any ideas on what I can improve in iRules?

      Virtual Server 443 Front-end:

      ## WAF FRONT-END VIRTUAL SERVER 443
      when HTTP_REQUEST {
      ## AVOID PREVIOUS CUSTOM_HEADER HEADER
      if {[HTTP::header exists CUSTOM_HEADER]}{
      HTTP::header remove "CUSTOM_HEADER"
      }
      HTTP::header insert "CUSTOM_HEADER" "SECURE_CONN"
      HTTP::header remove "Accept-Enconding"
      }

      Virtual Server 80 Front-end:

      ## WAF FRONT-END VIRTUAL SERVER 80
      when HTTP_REQUEST {
      ## AVOID PREVIOUS CUSTOM_HEADER HEADER
      if {[HTTP::header exists CUSTOM_HEADER]}{
      HTTP::header remove "CUSTOM_HEADER"
      }
      HTTP::header insert "CUSTOM_HEADER" "NOTSECURE_CONN"
      HTTP::header remove "Accept-Enconding"
      }

      Both requests are forward to Virtual Server 80 Back-end:

      ## LTM BACK-END VIRTUAL SERVER 80
      when CLIENT_ACCEPTED {
      TCP::collect
      }
      when CLIENT_DATA {
      set payload [TCP::payload]
      log local0. "Payload: $payload"
      if { $payload contains "SECURE_CONN" } {
      set SECURE_CONNBSEG 1
      log local0. "SECURE_CONN found - $SECURE_CONN"
      } 
      elseif { $payload contains "NOTSECURE_CONN" } {
      set NOTSECURE_CONN 1
      log local0. "NOTSECURE_CONN found - $NOTSECURE_CONN" }
      TCP::release
      }
      when HTTP_RESPONSE {
          if { $SECURE_CONN == 1 } {
          log local0. "Value SECURE = $SECURE_CONN"
          STREAM::expression {@http://domain.com@https://domain.com@}
          STREAM::enable
          }
          elseif { $NOTSECURE_CONN == 1 } {
          log local0. "Value NOT_SECURE = $NOTSECURE_CONN"
          STREAM::expression {@http://domain.com@http://domain.com@}
          STREAM::enable
          }
      }

      Thanks for helping 🙂

      • JRahm's avatar
        JRahm
        Icon for Admin rankAdmin

        Glad to assist, and thanks for that, totally my privilege to invest in the community!

        Have you looked at the POLICY::rules command? If you use the matched attribute with contains and your conflicting policy, you might be able to bypass that error and proceed with the more elegant HTTP solution.

        So maybe something like this would work?

        when HTTP_REQUEST {
            if { [POLICY::rules matched] contains "your conflicting executed policy rule" } {
                return
            } else {
                # ... your logic here in HTTP instead of TCP
            }
        }

        I've never used it personally but that's the way I'd try to make this work before attempting it at the TCP layer for two reasons: 1) TCP inspection is more expensive, especially since you have access to the HTTP since it's applied and 2) Division of logic into multiple layers complicates your workflow.

        Regarding the rules as written, I would just focus on the match instead of trying to handle both the match and unmatch condition. So on your front-end 80 vip, I'd just remove the custom header if exists and eliminate the last two lines that aren't brackets:

         

        ## WAF FRONT-END VIRTUAL SERVER 80
        when HTTP_REQUEST {
          ## AVOID PREVIOUS CUSTOM_HEADER HEADER
          if {[HTTP::header exists CUSTOM_HEADER]}{
            HTTP::header remove "CUSTOM_HEADER"
          }
        }

         

        and then on your backend something like:

         

        when RULE_INIT {
          set static::debug_secure_conn 1
        }
        when CLIENT_ACCEPTED {
          TCP::collect
        }
        when CLIENT_DATA {
          set payload [TCP::payload]
          if { $static::debug_secure_conn } {
            log local0. "Payload: $payload"
          }
          if { $payload contains "SECURE_CONN" } {
            set SECURE_CONNBSEG 1
            if { $static::debug_secure_conn } {
              log local0. "SECURE_CONN found - $SECURE_CONN"
            }
          } 
          TCP::release
        }
        when HTTP_REQUEST_RELEASE {
          STREAM::disable
        }
        when HTTP_RESPONSE {
          if { [info exists SECURE_CONNBSEG] } {
            if { $static::debug_secure_conn } {
              log local0. "Value SECURE = $SECURE_CONN"
            }
            STREAM::expression {@http://domain.com@https://domain.com@}
            STREAM::enable
          }
        }

         

        Not at all tested, but food for thought.