Skip to content

Commit 9efaa7a

Browse files
authored
Merge pull request #3851 from dondetir/feature/mi-http-security-hardening
httpd: harden MI/HTTP interface with safe default and Basic Auth
2 parents 89055ff + ed2c801 commit 9efaa7a

3 files changed

Lines changed: 137 additions & 2 deletions

File tree

modules/httpd/doc/httpd_admin.xml

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@
9696
requests.
9797
</para>
9898
<para>
99-
<emphasis>The default value is "*" </emphasis> (bind to all IPv6 and IPv4 interfaces).
99+
<emphasis>The default value is "127.0.0.1" </emphasis> (binds to loopback only).
100+
Use "*" to bind to all IPv6 and IPv4 interfaces.
100101
</para>
101102
<example>
102103
<title>Set <varname>ip</varname> parameter</title>
@@ -271,6 +272,71 @@ modparam("httpd", "tls_key_file", "/etc/opensips/tls/server.key")
271272
...
272273
modparam("httpd", "tls_ciphers", "SECURE256:+SECURE192:-VERS-ALL:+VERS-TLS1.2")
273274
...
275+
</programlisting>
276+
</example>
277+
</section>
278+
<section id="param_auth_realm" xreflabel="auth_realm">
279+
<title><varname>auth_realm</varname> (string)</title>
280+
<para>
281+
The realm string to be used for HTTP Basic Authentication
282+
challenges. Only takes effect when both
283+
<varname>auth_username</varname> and
284+
<varname>auth_password</varname> are set.
285+
</para>
286+
<para>
287+
<emphasis> The default value is "OpenSIPS MI".</emphasis>
288+
</para>
289+
<example>
290+
<title>Set <varname>auth_realm</varname> parameter</title>
291+
<programlisting format="linespecific">
292+
...
293+
modparam("httpd", "auth_realm", "OpenSIPS Management")
294+
...
295+
</programlisting>
296+
</example>
297+
</section>
298+
<section id="param_auth_username" xreflabel="auth_username">
299+
<title><varname>auth_username</varname> (string)</title>
300+
<para>
301+
The username for HTTP Basic Authentication. When set together
302+
with <varname>auth_password</varname>, all HTTP requests must
303+
present valid credentials. Requests without credentials or
304+
with incorrect credentials receive a 401 Unauthorized response.
305+
</para>
306+
<para>
307+
<emphasis> The default value is "" (authentication disabled).</emphasis>
308+
</para>
309+
<example>
310+
<title>Set <varname>auth_username</varname> parameter</title>
311+
<programlisting format="linespecific">
312+
...
313+
modparam("httpd", "auth_username", "admin")
314+
...
315+
</programlisting>
316+
</example>
317+
</section>
318+
<section id="param_auth_password" xreflabel="auth_password">
319+
<title><varname>auth_password</varname> (string)</title>
320+
<para>
321+
The password for HTTP Basic Authentication. Must be set
322+
together with <varname>auth_username</varname>.
323+
</para>
324+
<warning><para>
325+
When using HTTP Basic Authentication, it is strongly
326+
recommended to also enable TLS via
327+
<varname>tls_cert_file</varname> and
328+
<varname>tls_key_file</varname> to prevent credentials
329+
from being transmitted in plaintext.
330+
</para></warning>
331+
<para>
332+
<emphasis> The default value is "" (authentication disabled).</emphasis>
333+
</para>
334+
<example>
335+
<title>Set <varname>auth_password</varname> parameter</title>
336+
<programlisting format="linespecific">
337+
...
338+
modparam("httpd", "auth_password", "secretpass")
339+
...
274340
</programlisting>
275341
</example>
276342
</section>

modules/httpd/httpd.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,17 @@ static mi_response_t *mi_list_root_path(const mi_params_t *params,
5757
struct mi_handler *async_hdl);
5858

5959
int port = 8888;
60-
str ip = {NULL, 0};
60+
str ip = {"127.0.0.1", 9};
6161
str buffer = {NULL, 0};
6262
unsigned int hd_conn_timeout_s = 30;
6363
str tls_cert_file = {NULL, 0};
6464
str tls_key_file = {NULL, 0};
6565
str tls_ciphers = {"SECURE256:+SECURE192:-VERS-ALL:+VERS-TLS1.2", 45};
6666
int post_buf_size = DEFAULT_POST_BUF_SIZE;
6767
int receive_buf_size = DEFAULT_POST_BUF_SIZE;
68+
str auth_realm = {"OpenSIPS MI", 11};
69+
str auth_username = {NULL, 0};
70+
str auth_password = {NULL, 0};
6871
struct httpd_cb *httpd_cb_list = NULL;
6972

7073
char *httpd_receive_buff = NULL;
@@ -88,6 +91,9 @@ static const param_export_t params[] = {
8891
{"tls_cert_file", STR_PARAM, &tls_cert_file.s},
8992
{"tls_key_file", STR_PARAM, &tls_key_file.s},
9093
{"tls_ciphers", STR_PARAM, &tls_ciphers.s},
94+
{"auth_realm", STR_PARAM, &auth_realm.s},
95+
{"auth_username", STR_PARAM, &auth_username.s},
96+
{"auth_password", STR_PARAM, &auth_password.s},
9197
{NULL, 0, NULL}
9298
};
9399

@@ -180,6 +186,23 @@ static int mod_init(void)
180186
}
181187
}
182188

189+
if (auth_realm.s)
190+
auth_realm.len = strlen(auth_realm.s);
191+
if (auth_username.s)
192+
auth_username.len = strlen(auth_username.s);
193+
if (auth_password.s)
194+
auth_password.len = strlen(auth_password.s);
195+
196+
if ((auth_username.s && !auth_password.s) ||
197+
(!auth_username.s && auth_password.s)) {
198+
LM_ERR("both auth_username and auth_password must be set\n");
199+
return -1;
200+
}
201+
if (auth_username.s && auth_username.len == 0) {
202+
LM_ERR("auth_username cannot be empty\n");
203+
return -1;
204+
}
205+
183206
if (post_buf_size < MIN_POST_BUF_SIZE) {
184207
LM_ERR("post_buf_size should be bigger then %d\n",
185208
MIN_POST_BUF_SIZE);

modules/httpd/httpd_proc.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ extern int post_buf_size;
5959
extern str tls_cert_file;
6060
extern str tls_key_file;
6161
extern str tls_ciphers;
62+
extern str auth_realm;
63+
extern str auth_username;
64+
extern str auth_password;
6265
extern struct httpd_cb *httpd_cb_list;
6366
static union sockaddr_union httpd_server_info;
6467

@@ -71,6 +74,8 @@ static const str MI_HTTP_U_URL = str_init("<html><body>"
7174
"Unable to parse URL!</body></html>");
7275
static const str MI_HTTP_U_METHOD = str_init("<html><body>"
7376
"Unsupported HTTP request!</body></html>");
77+
static const str MI_HTTP_UNAUTH = str_init("<html><body>"
78+
"Unauthorized</body></html>");
7479

7580
static char * load_file(char * );
7681

@@ -464,6 +469,47 @@ MHD_RET answer_to_connection (void *cls, struct MHD_Connection *connection,
464469

465470
pr = *con_cls;
466471
if(pr == NULL){
472+
/* first call for this request -- check auth before allocating */
473+
if (auth_username.s) {
474+
char *user = NULL;
475+
char *pass = NULL;
476+
477+
user = MHD_basic_auth_get_username_password(connection,
478+
&pass);
479+
if (!user || !pass ||
480+
strlen(user) != auth_username.len ||
481+
strlen(pass) != auth_password.len ||
482+
memcmp(user, auth_username.s,
483+
auth_username.len) != 0 ||
484+
memcmp(pass, auth_password.s,
485+
auth_password.len) != 0) {
486+
LM_WARN("rejected unauthenticated HTTP request "
487+
"for %s\n", url);
488+
response = MHD_create_response_from_buffer(
489+
MI_HTTP_UNAUTH.len,
490+
(void *)MI_HTTP_UNAUTH.s,
491+
MHD_RESPMEM_PERSISTENT);
492+
ret = MHD_queue_basic_auth_fail_response(
493+
connection, auth_realm.s, response);
494+
MHD_destroy_response(response);
495+
#if MHD_VERSION >= 0x00093800
496+
if (user) MHD_free(user);
497+
if (pass) MHD_free(pass);
498+
#else
499+
if (user) free(user);
500+
if (pass) free(pass);
501+
#endif
502+
return ret;
503+
}
504+
#if MHD_VERSION >= 0x00093800
505+
MHD_free(user);
506+
MHD_free(pass);
507+
#else
508+
free(user);
509+
free(pass);
510+
#endif
511+
}
512+
467513
pr = pkg_malloc(sizeof(struct post_request));
468514
if(pr==NULL) {
469515
LM_ERR("oom while allocating post_request structure\n");

0 commit comments

Comments
 (0)