From: Willy Tarreau Date: Mon, 26 Oct 2026 16:07:13 +0000 (+0100) Subject: MINOR: http: introduce the notion of http-hardening flags and sections X-Git-Tag: v3.5-dev11~36 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=cd8f136da1fc0dc4cf7d537a65b55fc22255719d;p=haproxy.git MINOR: http: introduce the notion of http-hardening flags and sections The goal here will be to declare http-hardening sections which enumerate controls that can be relaxed or enforced. The control names will be stored in a table just like the http-violations, associated with a mask. They will possibly permit to have names for sets that cover multiple bits. For now only 32 bits are planned, covering both front/back and req/resp. This should be sufficient for a start and we'll see once the limit is reached what's the best way to proceed. One difficulty here is to reason in terms of what is not permitted. So the way to think about it is to associate each verb to an action to be performed, such as reject-xxx, delete-xxx, adjust-xxx, block-xxx. --- diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index 4a003f81e..787ed3271 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -24,6 +24,7 @@ #define _HAPROXY_HTTP_T_H #include +#include #include #include @@ -233,6 +234,37 @@ struct http_violation { enum http_dir dir; }; +/* HTTP hardening masks. A profile is a 32-bit bitmap of such masks, shared by + * both directions so that header processing doesn't depend on the side. A bit + * set means the corresponding hardening check is enabled. + */ +#define HTTP_HRD_NONE 0x00000000 +/* insert new masks below this line */ +/* insert new masks above this line */ +#define HTTP_HRD_ALL 0xFFFFFFFF + +/* default policy for a new profile: permissive */ +#define HTTP_HRD_DEFAULT HTTP_HRD_NONE + +/* a hardening keyword */ +struct http_hardening_kw { + const char *name; + uint32_t mask; + enum http_dir dir; +}; + +/* An HTTP hardening profile, as declared in an "http-hardening" section and + * referenced by "bind" and "server" lines. Profiles are indexed by in a + * global tree. By default nothing is hardened, and the field lists the + * extra hardening checks. + */ +struct http_hdng_prf { + struct ceb_node name_node; /* indexes in http_hardening_by_name */ + char *name; /* profile name */ + uint32_t mask; /* enabled HTTP_HRD_* masks */ + int line; /* line of the declaration */ + char *file; /* file of the declaration */ +}; #endif /* _HAPROXY_HTTP_T_H */ diff --git a/include/haproxy/http.h b/include/haproxy/http.h index 5bb15a787..fcfa7846e 100644 --- a/include/haproxy/http.h +++ b/include/haproxy/http.h @@ -35,10 +35,13 @@ extern const struct ist http_known_methods[HTTP_METH_OTHER]; extern const uint8_t http_char_classes[256]; extern long http_err_status_codes[512 / sizeof(long)]; extern long http_fail_status_codes[512 / sizeof(long)]; +extern const struct http_hardening_kw http_hardening_kws[]; enum http_meth_t find_http_meth(const char *str, const int len); int http_get_status_idx(unsigned int status); const char *http_get_reason(unsigned int status); +struct http_hdng_prf *http_hardening_lookup(const char *name); +struct http_hdng_prf *http_hardening_resolve(const char *name, char **err); void http_status_add_range(long *array, uint low, uint high); void http_status_del_range(long *array, uint low, uint high); struct ist http_get_host_port(const struct ist host); diff --git a/src/http.c b/src/http.c index 33b5116dd..5f28142ad 100644 --- a/src/http.c +++ b/src/http.c @@ -11,9 +11,12 @@ */ #include +#include #include #include +#include #include +#include #include /* It is about twice as fast on recent architectures to lookup a byte in a @@ -1761,6 +1764,94 @@ static int http_parse_acceptable_http_violations(char **args, int section_type, return ret; } +/* list of known HTTP hardening options */ +const struct http_hardening_kw http_hardening_kws[] = { + /* insert new hardening options below this line */ + /* insert new hardening options above this line */ + { "all", HTTP_HRD_ALL, HTTP_DIR_BOTH }, + { NULL, 0 } +}; + +/* Declared profiles are indexed by name in . They must + * be declared before being referenced, like for "defaults" sections. + */ +static struct ceb_root *http_hardening_by_name; + +/* the profile being filled by the "http-hardening" section parser */ +static struct http_hdng_prf *curr_http_hdng_prf; + +/* Returns the declared profile named , or NULL if it doesn't exist. + */ +struct http_hdng_prf *http_hardening_lookup(const char *name) +{ + struct http_hdng_prf *prf; + + prf = cebuis_item_lookup(&http_hardening_by_name, name_node, name, name, struct http_hdng_prf); + return prf; +} + +/* Allocates a new profile named and returns it without inserting it + * anywhere. The new profile has no hardening option enabled. NULL is returned + * on allocation failure. + */ +static struct http_hdng_prf *http_hardening_new(const char *name) +{ + struct http_hdng_prf *prf; + + prf = calloc(1, sizeof(*prf)); + if (!prf) + return NULL; + + /* by default no hardening is performed */ + prf->mask = HTTP_HRD_DEFAULT; + prf->name = strdup(name); + if (!prf->name) { + free(prf); + return NULL; + } + return prf; +} + +/* Finds the profile named for a reference made by a "bind" or "server" + * line. The profile must have been declared before. Returns the profile on + * success, or NULL with an error message in that must be freed by the + * caller. + */ +struct http_hdng_prf *http_hardening_resolve(const char *name, char **err) +{ + struct http_hdng_prf *prf; + + if (strcmp(name, "auto") == 0) { + memprintf(err, "http-hardening profile name 'auto' is reserved"); + return NULL; + } + + prf = http_hardening_lookup(name); + if (!prf) + memprintf(err, "unknown http-hardening profile '%s' (it must be declared before)", name); + return prf; +} + +/* releases a profile, except the static ones whose file name is still freed */ +static void http_hardening_free(struct http_hdng_prf *prf) +{ + ha_free(&prf->file); + free(prf->name); + free(prf); +} + +static void http_hardening_deinit() +{ + struct http_hdng_prf *prf; + + while ((prf = cebuis_item_first(&http_hardening_by_name, name_node, name, struct http_hdng_prf))) { + cebuis_item_delete(&http_hardening_by_name, name_node, name, prf); + http_hardening_free(prf); + } +} + +REGISTER_POST_DEINIT(http_hardening_deinit); + /* initialize the required structures and arrays */ static void _http_init() {