]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: http_ext: implement rfc7239_{nn,np} converters
authorAurelien DARRAGON <adarragon@haproxy.com>
Mon, 14 Oct 2024 17:49:32 +0000 (19:49 +0200)
committerAurelien DARRAGON <adarragon@haproxy.com>
Thu, 17 Oct 2024 15:24:58 +0000 (17:24 +0200)
"option forwarded" provides a convenient way to automatically insert
rfc7239 forwarded header to requests sent to servers.

On the other hand, manually crafting the header is quite complicated due
to specific formatting rules that must be followed as per rfc7239.
However, sometimes it may be necessary to craft the header manually, for
instance if it has to be conditional or based on parameters that "option
forwarded" doesn't provide. To ease this task, in this patch we implement
rfc7239_nn and rfc7239_np which are respectively meant to craft nodename:
nodeport values, specifically intended to manually build rfc7239 'for'
and 'by' header fields while ensuring rfc7239 compliancy.

Example:
  # build RFC-compliant 7239 header:
  http-request set-var-fmt(txn.forwarded) "for=\"%[ipv6(::1),rfc7239_nn]:%[str(8888),rfc7239_np]\";host=\"haproxy.org\";proto=http"
  # check RFC-compliancy:
  http-request set-var(txn.test) "var(txn.forwarded),debug(ok,stderr),rfc7239_is_valid,debug(ok,stderr)"
  #  stderr output:
  #    [debug] ok: type=str <for="[::1]:_8888";host="haproxy.org";proto=http>
  #    [debug] ok: type=bool <1>

See documentation for more info and examples.

doc/configuration.txt
src/http_ext.c

index fd882e9b080673adf6886e7065e0cba56395f70b..5e619a7fe87f6c28af089e40b79b8fa3092d333f 100644 (file)
@@ -19760,6 +19760,8 @@ rfc7239_field(field)                               string       string
 rfc7239_is_valid                                   string       boolean
 rfc7239_n2nn                                       string       address / str
 rfc7239_n2np                                       string       integer / str
+rfc7239_nn                                         address/str  string
+rfc7239_np                                         integer/str  string
 rtrim(chars)                                       string       string
 sdbm([avalanche])                                  binary       integer
 secure_memcmp(var)                                 string       boolean
@@ -20838,6 +20840,52 @@ rfc7239_n2np
     #input: "_name:_port"
     #  output: "_port" (string)
 
+rfc7239_nn
+  Converts provided address / string input into RFC7239-compliant node name.
+  It may be used to manually build 'for' or 'by' 7239 header fields.
+
+  When provided input is string, it will be automatically prefixed with '_'
+  char to represent obfuscated identifier. String must comply with RFC7239
+  charset. If string is empty, it will be converter to "unknown" identifier.
+
+  Example:
+    #input: ipv6(ab:cd:ff:ff:ff:ff:ff:ff)
+    #  output: "[ab:cd:ff:ff:ff:ff:ff:ff]"
+    #input: str(test)
+    #  output: "_test"
+    #input: str()
+    #  output: "unknown"
+
+  See also: "rfc7239_np"
+
+rfc7239_np
+  Converts provided unsigned integer / string input into RFC7239-compliant node
+  port. It may be used to manually build 'for' or 'by' 7239 header fields.
+
+  When provided input is string, it will be automatically prefixed with '_'
+  char to represent obfuscated identifier. String must comply with RFC7239
+  charset and cannot be empty.
+
+  Example:
+    #input: int(12)
+    #  output: "12"
+    #input: str(test)
+    #  output: "_test"
+
+    # build 'for' forwarded header field
+    http-request set-var-fmt(txn.test) "for=\"%[ipv6(::1),rfc7239_nn]:%[int(8080),rfc7239_np]\";"
+    #  output: "for=\"[::1]:8080\";"
+
+    # build RFC-compliant 7239 header:
+    http-request set-var-fmt(txn.forwarded) "for=\"%[ipv6(::1),rfc7239_nn]:%[str(8888),rfc7239_np]\";host=\"haproxy.org\";proto=http"
+    # check RFC-compliancy:
+    http-request set-var(txn.test) "var(txn.forwarded),debug(test,stderr),rfc7239_is_valid,debug(test,stderr)"
+    #  stderr output:
+    #    [debug] test: type=str <for="[::1]:_8888";host="haproxy.org";proto=http>
+    #    [debug] test: type=bool <1>
+
+  See also: "rfc7239_nn"
+
 rtrim(<chars>)
   Skips any characters from <chars> from the end of the string representation
   of the input sample.
index 3367e3884cd5301b992d6b03cdf62601fadd01e3..15c6683e9413449cca905e953e9e6f8d17c5b743 100644 (file)
@@ -1881,12 +1881,118 @@ static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, voi
        return 1;
 }
 
+/*
+ * input: ipv4 address, ipv6 address or str (empty string will result in
+ * "unknown" indentifier, else string will be translated to _obfs
+ * indentifier, prefixed by '_'. Must comply with RFC7239 charset)
+ *
+ * output: rfc7239-compliant forwarded header nodename
+ */
+static int sample_conv_7239_nn(const struct arg *args, struct sample *smp, void *private)
+{
+       struct buffer *trash = get_trash_chunk();
+
+       switch (smp->data.type) {
+               case SMP_T_IPV4:
+               {
+                       unsigned char *pn = (unsigned char *)&(smp->data.u.ipv4);
+
+                       chunk_printf(trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
+                       break;
+               }
+               case SMP_T_IPV6:
+                       _7239_print_ip6(trash, &smp->data.u.ipv6, 1);
+                       break;
+               case SMP_T_STR:
+ case_str:
+               {
+                       struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
+
+                       if (!istlen(validate_n)) {
+                               // empty -> unknown
+                               chunk_printf(trash, "unknown");
+                               break;
+                       }
+
+                       if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
+                               return 0; /* invalid input */
+                       // output with '_' prefix
+                       chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
+                       break;
+               }
+               default:
+               {
+                       if (sample_casts[smp->data.type][SMP_T_STR] &&
+                            sample_casts[smp->data.type][SMP_T_STR](smp))
+                               goto case_str;
+                       return 0; /* unexpected */
+               }
+
+       }
+
+       smp->data.u.str = *trash;
+       smp->data.type = SMP_T_STR;
+       smp->flags &= ~SMP_F_CONST;
+
+       return 1;
+}
+
+/*
+ * input: unsigned integer or str (string will be translated to _obfs
+ * indentifier, prefixed by '_'. Must comply with RFC7239 charset)
+ *
+ * output: rfc7239-compliant forwarded header nodeport
+ */
+static int sample_conv_7239_np(const struct arg *args, struct sample *smp, void *private)
+{
+       struct buffer *trash = get_trash_chunk();
+
+       switch (smp->data.type) {
+               case SMP_T_SINT:
+               {
+                       chunk_printf(trash, "%u", (unsigned int)smp->data.u.sint);
+                       break;
+               }
+               case SMP_T_STR:
+ case_str:
+               {
+                       struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
+
+                       if (!istlen(validate_n))
+                               return 0;
+
+                       if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
+                               return 0; /* invalid input */
+                       // output with '_' prefix
+                       chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
+                       break;
+               }
+               default:
+               {
+                       if (sample_casts[smp->data.type][SMP_T_STR] &&
+                            sample_casts[smp->data.type][SMP_T_STR](smp))
+                               goto case_str;
+                       return 0; /* unexpected */
+               }
+
+       }
+
+       smp->data.u.str = *trash;
+       smp->data.type = SMP_T_STR;
+       smp->flags &= ~SMP_F_CONST;
+
+       return 1;
+
+}
+
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "rfc7239_is_valid",  sample_conv_7239_valid,   0,                NULL,   SMP_T_STR,  SMP_T_BOOL},
        { "rfc7239_field",     sample_conv_7239_field,   ARG1(1,STR),      NULL,   SMP_T_STR,  SMP_T_STR},
        { "rfc7239_n2nn",      sample_conv_7239_n2nn,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
        { "rfc7239_n2np",      sample_conv_7239_n2np,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
+       { "rfc7239_nn",        sample_conv_7239_nn,      0,                NULL,   SMP_T_ANY,  SMP_T_STR},
+       { "rfc7239_np",        sample_conv_7239_np,      0,                NULL,   SMP_T_ANY,  SMP_T_STR},
        { NULL, NULL, 0, 0, 0 },
 }};