20 Lines or Less: Security Headers and DNS

What could you do with your code in 20 Lines or Less?

That's the question we like to ask from, for, and of (feel free to insert your favorite preposition here) the DevCentral community, and every time we do, we go looking to find cool new examples that show just how flexible and powerful iRules can be without getting in over your head. Thus was born the 20LoL (20 Lines or Less) series many moons ago. Over the years we've highlighted hundreds of iRules examples, all of which do downright cool things in less than 21 lines of code.

Security Headers

At RSA a couple weeks ago I presented on how to secure your clients with regards to their experience on your web applications. Scott Helme created the SecurityHeaders.io site where you can test your apps to see what is and isn’t supported currently. I can’t stress enough how important it is to read carefully and test thoroughly before implementing some of these headers, you can dos yourself fairly easily. That said, putting in the work to protect your users is a worthy effort, and it’s a pretty easy implementation with iRules, or with an LTM policy. You can test CSP and HPKP using the -Report-Only trailer to those headers at another site of Scott's called Report-URI.io.

when RULE_INIT {
  set static::fqdn_pin1 "X3pGTSOuJeEVw989IJ/cEtXUEmy52zs1TZQrU06KUKg="
  set static::fqdn_pin2 "MHJYVThihUrJcxW6wcqyOISTXIsInsdj3xK8QrZbHec="
  set static::max_age 15552000
}
when HTTP_REQUEST {
  HTTP::respond 301 Location "https://[HTTP::host][HTTP::uri]"
}
when HTTP_RESPONSE {
  #HSTS
  HTTP::header insert Strict-Transport-Security "max-age=$static::max_age; includeSubDomains"
  #HPKP
  HTTP::header insert Public-Key-Pins "pin-sha256=\"$static::fqdn_pin1\" max-age=$static::max_age; includeSubDomains"
  #X-XSS-Protection
  HTTP::header insert X-XSS-Protection "1; mode=block"
  #X-Frame-Options
  HTTP::header insert X-Frame-Options "DENY"
  #X-Content-Type-Options
  HTTP::header insert X-Content-Type-Options "nosniff"
  #CSP
  HTTP::header insert Content-Security-Policy "default-src https://devcentral.f5.com/s:443"
  #CSP for IE
  HTTP::header insert X-Content-Security-Policy "default-src https://devcentral.f5.com/s:443"
}

Redirect the NXDOMAIN Response

We haven't featured too many DNS-based iRules, but we are starting to see an uptick in their presence in the community. The result of the conversation between dearsanky and Kai Wilke is a short but sweet sample redirecting non-existent domain queries

when DNS_RESPONSE {
  if { [DNS::header rcode] eq "NXDOMAIN" } {
    DNS::header rcode NOERROR
    DNS::answer insert "[DNS::question name]. 60 [DNS::question class] [DNS::question type] 1.1.1.1"
  }
}

DNS Recursion & Blacklisting

Another more complex DNS sample, also a result of an exchange between dearsanky and Kai Wilke. This iRule blocks queries not configured in the admin_datagroup data-group as well as looks for blacklisted ip’s and blocks those as well. Before the DNS namespace was added to iRules (note: license required if you don’t have GTM/BIG-IP DNS) this was an exercise in binary scans and formats, but is made far simpler with clear commands.

when RULE_INIT {
 set static::blacklist_reply_IPV4 "10.10.10.10"
 set static::blacklist_ttl "100"
}
when DNS_REQUEST {
  set Blacklist_Match 0
  set Blacklist_Type ""
  set domain_name [DNS::question name]
  if {[DNS::header "rd"] == 1 } {
    if { not [class match [IP::client_addr] eq "admin_datagroup" ] } {
      DNS::drop
    } elseif { [string tolower [class match $domain_name eq Blacklist_Class]] } {
        set Blacklist_Match 1
        DNS::return
    }
  }
}
when DNS_RESPONSE {
  if { $Blacklist_Match } {
    switch [DNS::question type] {
      "A" {
        DNS::answer clear
        DNS::answer insert "[DNS::question name]. $static::blacklist_ttl [DNS::question class] [DNS::question type] $static::blacklist_reply_IPV4"
        DNS::header ra "1"
      }
      default { DNS::last_act reject } }
    }
  }
}

And there it is, another edition of 20 Lines or Less, the power of iRules on full display. Happy coding out there, and I look forward to seeing what excellent contributions might make the next edition!

Published Mar 14, 2016
Version 1.0
  • JG's avatar
    JG
    Icon for Cumulonimbus rankCumulonimbus
    Interesting. One question: Is "DNS::answer clear" actually required here?
  • Scott provides report-uri.io which allows reporting for CSP and HPKP violations, which could be added with only a couple of extra lines. https://report-uri.io/ Alas my no-sniff rule is rather more complex thanks to Jive software. As is every CSP I've ever done that is useful. I'm doing something similar for Wordpress, but have different ages for HSTS and HPKP which I think makes sense as HPKP may well want to be shorter than HSTS, and well the less said about trying to apply CSP to WordPress sensibly the better.
  • Thanks Simon, added a reference to report-uri.io in the article. Jie, inserting an answer doesn't clear the other answers in the response, so that's why it's there.
  • Thank you very much Jason! The last one is such a gem! I have been looking for something like this for a long time.
  • Thanks Jason for including one (two!) of my solutions in the 20LinesOrLess series... 😉

    @Jie: The

    DNS::answer clear
    is required in this case to sanitize the existing DNS answer, to make sure that only the blacklist response is send to the client. If you skip the
    DNS::answer clear
    , then it may become a RR DNS response. But you could also directly DNS respond to the request using the DNS_REQUEST event (not implemented in this specific iRule). In this case you don't need to
    DNS::answer clear
    the answer, since you would build the answer completely from the scratch...

    Cheers, Kai