MEDIUM: checks: Implement LDAP check using tcp-check rules

A shared tcp-check ruleset is now created to support LDAP check. This way no
extra memory is used if several backends use a LDAP check.

The following sequance is used :

    tcp-check send-binary "300C020101600702010304008000"

    tcp-check expect rbinary "^30" min-recv 14 \
        on-error "Not LDAPv3 protocol"

    tcp-check expect custom

The last expect rule relies on a custom function to check the LDAP server reply.
This commit is contained in:
Christopher Faulet 2020-04-03 23:13:50 +02:00
parent f2b3be5c27
commit 1997ecaa0c
6 changed files with 158 additions and 68 deletions

View File

@ -177,7 +177,6 @@
#define DEF_AGENT_RISETIME 1
#define DEF_CHECK_REQ "OPTIONS / HTTP/1.0\r\n"
#define DEF_CHECK_PATH ""
#define DEF_LDAP_CHECK_REQ "\x30\x0c\x02\x01\x01\x60\x07\x02\x01\x03\x04\x00\x80\x00"
#define DEF_HANA_ONERR HANA_ONERR_FAILCHK

View File

@ -79,6 +79,8 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
const char *file, int line);
int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
const char *file, int line);
int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
const char *file, int line);
#endif /* _PROTO_CHECKS_H */

View File

@ -316,6 +316,7 @@ struct tcpcheck_rule {
#define TCPCHK_RULES_REDIS_CHK 0x00000020
#define TCPCHK_RULES_SMTP_CHK 0x00000030
#define TCPCHK_RULES_MYSQL_CHK 0x00000050
#define TCPCHK_RULES_LDAP_CHK 0x00000060
#define TCPCHK_RULES_SSL3_CHK 0x00000070
/* A list of tcp-check vars, to be registered before executing a ruleset */

View File

@ -171,9 +171,7 @@ enum PR_SRV_STATE_FILE {
#define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */
/* unused: 0x10000000..0x30000000 */
#define PR_O2_HTTP_CHK 0x40000000 /* use HTTP 'OPTIONS' method to check server health */
/* unused 0x50000000 */
#define PR_O2_LDAP_CHK 0x60000000 /* use LDAP check for server health */
/* unused: 0x70000000 */
/* unused 0x50000000..0x70000000 */
#define PR_O2_LB_AGENT_CHK 0x80000000 /* use a TCP connection to obtain a metric of server health */
#define PR_O2_TCPCHK_CHK 0x90000000 /* use TCPCHK check for server health */
#define PR_O2_EXT_CHK 0xA0000000 /* use external command for server health */

View File

@ -2422,16 +2422,8 @@ stats_error_parsing:
goto out;
}
else if (!strcmp(args[1], "ldap-check")) {
/* use LDAP request to check servers' health */
free(curproxy->check_req);
curproxy->check_req = NULL;
curproxy->options2 &= ~PR_O2_CHK_ANY;
curproxy->options2 |= PR_O2_LDAP_CHK;
curproxy->check_req = malloc(sizeof(DEF_LDAP_CHECK_REQ) - 1);
memcpy(curproxy->check_req, DEF_LDAP_CHECK_REQ, sizeof(DEF_LDAP_CHECK_REQ) - 1);
curproxy->check_len = sizeof(DEF_LDAP_CHECK_REQ) - 1;
if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
err_code |= proxy_parse_ldap_check_opt(args, 0, curproxy, &defproxy, file, linenum);
if (err_code & ERR_FATAL)
goto out;
}
else if (!strcmp(args[1], "spop-check")) {

View File

@ -857,7 +857,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
struct task *t = check->task;
char *desc;
int done;
unsigned short msglen;
if (unlikely(check->result == CHK_RES_FAILED))
goto out_wakeup;
@ -1156,59 +1155,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
break;
}
case PR_O2_LDAP_CHK:
if (!done && b_data(&check->bi) < 14)
goto wait_more_data;
/* Check if the server speaks LDAP (ASN.1/BER)
* http://en.wikipedia.org/wiki/Basic_Encoding_Rules
* http://tools.ietf.org/html/rfc4511
*/
/* http://tools.ietf.org/html/rfc4511#section-4.1.1
* LDAPMessage: 0x30: SEQUENCE
*/
if ((b_data(&check->bi) < 14) || (*(b_head(&check->bi)) != '\x30')) {
set_server_check_status(check, HCHK_STATUS_L7RSP, "Not LDAPv3 protocol");
}
else {
/* size of LDAPMessage */
msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
/* http://tools.ietf.org/html/rfc4511#section-4.2.2
* messageID: 0x02 0x01 0x01: INTEGER 1
* protocolOp: 0x61: bindResponse
*/
if ((msglen > 2) ||
(memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
set_server_check_status(check, HCHK_STATUS_L7RSP, "Not LDAPv3 protocol");
goto out_wakeup;
}
/* size of bindResponse */
msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
/* http://tools.ietf.org/html/rfc4511#section-4.1.9
* ldapResult: 0x0a 0x01: ENUMERATION
*/
if ((msglen > 4) ||
(memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
set_server_check_status(check, HCHK_STATUS_L7RSP, "Not LDAPv3 protocol");
goto out_wakeup;
}
/* http://tools.ietf.org/html/rfc4511#section-4.1.9
* resultCode
*/
check->code = *(b_head(&check->bi) + msglen + 9);
if (check->code) {
set_server_check_status(check, HCHK_STATUS_L7STS, "See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
} else {
set_server_check_status(check, HCHK_STATUS_L7OKD, "Success");
}
}
break;
case PR_O2_SPOP_CHK: {
unsigned int framesz;
char err[HCHK_DESC_LEN];
@ -2770,6 +2716,71 @@ static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, stru
return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
}
static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
{
enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
enum healthcheck_status status;
struct buffer *msg = NULL;
struct ist desc = ist(NULL);
unsigned short msglen = 0;
/* Check if the server speaks LDAP (ASN.1/BER)
* http://en.wikipedia.org/wiki/Basic_Encoding_Rules
* http://tools.ietf.org/html/rfc4511
*/
/* size of LDAPMessage */
msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
/* http://tools.ietf.org/html/rfc4511#section-4.2.2
* messageID: 0x02 0x01 0x01: INTEGER 1
* protocolOp: 0x61: bindResponse
*/
if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
status = HCHK_STATUS_L7RSP;
desc = ist("Not LDAPv3 protocol");
goto error;
}
/* size of bindResponse */
msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
/* http://tools.ietf.org/html/rfc4511#section-4.1.9
* ldapResult: 0x0a 0x01: ENUMERATION
*/
if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
status = HCHK_STATUS_L7RSP;
desc = ist("Not LDAPv3 protocol");
goto error;
}
/* http://tools.ietf.org/html/rfc4511#section-4.1.9
* resultCode
*/
check->code = *(b_head(&check->bi) + msglen + 9);
if (check->code) {
status = HCHK_STATUS_L7STS;
desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
goto error;
}
set_server_check_status(check, HCHK_STATUS_L7OKD, "Success");
out:
free_trash_chunk(msg);
return ret;
error:
ret = TCPCHK_EVAL_STOP;
msg = alloc_trash_chunk();
if (msg)
tcpcheck_onerror_message(msg, check, rule, 0, desc);
set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
goto out;
wait_more_data:
ret = TCPCHK_EVAL_WAIT;
goto out;
}
/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
* to wait and -1 to stop the check. */
@ -5763,6 +5774,93 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
goto out;
}
int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
const char *file, int line)
{
static char *ldap_req = "300C020101600702010304008000";
struct tcpcheck_ruleset *rs = NULL;
struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
struct tcpcheck_rule *chk;
char *errmsg = NULL;
int err_code = 0;
if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
err_code |= ERR_WARN;
if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
goto out;
if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
file, line);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
curpx->options2 &= ~PR_O2_CHK_ANY;
curpx->options2 |= PR_O2_TCPCHK_CHK;
free_tcpcheck_vars(&rules->preset_vars);
rules->list = NULL;
rules->flags = 0;
rs = tcpcheck_ruleset_lookup("*ldap-check");
if (rs)
goto ruleset_found;
rs = tcpcheck_ruleset_create("*ldap-check");
if (rs == NULL) {
ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
goto error;
}
chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
1, curpx, &rs->rules, file, line, &errmsg);
if (!chk) {
ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
goto error;
}
chk->index = 0;
LIST_ADDQ(&rs->rules, &chk->list);
chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
"min-recv", "14",
"on-error", "Not LDAPv3 protocol",
""},
1, curpx, &rs->rules, file, line, &errmsg);
if (!chk) {
ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
goto error;
}
chk->index = 1;
LIST_ADDQ(&rs->rules, &chk->list);
chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
1, curpx, &rs->rules, file, line, &errmsg);
if (!chk) {
ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
goto error;
}
chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
chk->index = 2;
LIST_ADDQ(&rs->rules, &chk->list);
LIST_ADDQ(&tcpchecks_list, &rs->list);
ruleset_found:
rules->list = &rs->rules;
rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_LDAP_CHK);
out:
free(errmsg);
return err_code;
error:
tcpcheck_ruleset_release(rs);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },