From 704eaa5e632f9ab29cef75253774f9e0284fca34 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 12 Mar 2020 14:43:32 +0000 Subject: [PATCH] ss: allow streamtype policy overlays Make the policy load apis public with an extra argument that says if you want the JSON to overlay on an existing policy rather than replace it. Teach the stream type parser stuff to realize it already has an entry for the stream type and to modify that rather than create a second one, allowing overlays to modify stream types. Add --force-portal and --force-no-internet flags to minimal-secure-streams and use the new policy overlay stuff to force the policy for captive portal detection to feel that there is one or that there's no internet. --- .../libwebsockets/lws-secure-streams-policy.h | 12 ++++ lib/core/context.c | 2 +- lib/secure-streams/README.md | 39 ++++++++++++ lib/secure-streams/policy.c | 63 ++++++++++++++++--- .../private-lib-secure-streams.h | 10 --- .../system/fetch-policy/fetch-policy.c | 2 +- .../minimal-secure-streams/README.md | 2 + .../minimal-secure-streams.c | 58 ++++++++++++----- 8 files changed, 152 insertions(+), 36 deletions(-) diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index f64fd80d2..02122fff6 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -247,3 +247,15 @@ typedef struct lws_ss_policy { uint8_t client_cert; /**< which client cert to apply 0 = none, 1+ = cc 0+ */ } lws_ss_policy_t; + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse_begin(struct lws_context *context, int overlay); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse_abandon(struct lws_context *context); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len); + +LWS_VISIBLE LWS_EXTERN int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay); diff --git a/lib/core/context.c b/lib/core/context.c index ced00eb3a..be5e70d87 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -828,7 +828,7 @@ lws_create_context(const struct lws_context_creation_info *info) assert(lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)); - if (lws_ss_policy_parse_begin(context)) + if (lws_ss_policy_parse_begin(context, 0)) goto bail; n = lws_ss_policy_parse(context, diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index 59ccf319c..cde5aa67b 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -369,6 +369,45 @@ The secure-streams-proxy minimal example shows how this is done and fetches its real policy from warmcat.com at startup using the built-in one. +## Applying streamtype policy overlays + +This is intended for modifying policies at runtime for testing, eg, to +force error paths to be taken. After the main policy is processed, you +may parse additional, usually smaller policy fragments on top of it. + +Where streamtype names in the new fragment already exist in the current +parsed policy, the settings in the fragment are applied over the parsed +policy, overriding settings. There's a simple api to enable this by +giving it the override JSON in one string + +``` +int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay); +``` + +but there are also other apis available that can statefully process +larger overlay fragments if needed. + +An example overlay fragment looks like this + +``` + { "s": [{ "captive_portal_detect": { + "endpoint": "google.com", + "http_url": "/", + "port": 80 + }}]} +``` + +ie the overlay fragment completely follows the structure of the main policy, +just misses out anything it doesn't override. + +Currently ONLY streamtypes may be overridden. + +You can see an example of this in use in `minimal-secure-streams` example +where `--force-portal` and `--force-no-internet` options cause the captive +portal detect streamtype to be overridden to force the requested kind of +outcome. + ## Captive Portal Detection If the policy contains a streamtype `captive_portal_detect` then the diff --git a/lib/secure-streams/policy.c b/lib/secure-streams/policy.c index d35c003cf..d12f6f801 100644 --- a/lib/secure-streams/policy.c +++ b/lib/secure-streams/policy.c @@ -277,6 +277,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) char **pp, dotstar[32], *q; lws_ss_trust_store_t *ts; lws_ss_metadata_t *pmd; + lws_ss_policy_t *p2; lws_retry_bo_t *b; size_t inl, outl; lws_ss_x509_t *x; @@ -336,11 +337,39 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } if (reason == LEJPCB_PAIR_NAME && n != -1 && n != LTY_TRUSTSTORE) { + + p2 = NULL; + if (n == LTY_POLICY) { + /* + * We want to allow for the possibility of overlays... + * eg, we come later with a JSON snippet that overrides + * select streamtype members of a streamtype that was + * already defined + */ + p2 = (lws_ss_policy_t *)a->context->pss_policies; + + while (p2) { + if (!strncmp(p2->streamtype, + ctx->path + ctx->st[ctx->sp].p, + ctx->path_match_len - + ctx->st[ctx->sp].p)) { + lwsl_info("%s: overriding s[] %s\n", + __func__, p2->streamtype); + break; + } + + p2 = p2->next; + } + } + /* - * We do the pointers always as .b, all of the participating - * structs begin with .next and .name + * We do the pointers always as .b union member, all of the + * participating structs begin with .next and .name the same */ - a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN); + if (p2) /* we may be overriding existing streamtype... */ + a->curr[n].b = (backoff_t *)p2; + else + a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN); if (!a->curr[n].b) goto oom; @@ -352,11 +381,15 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } a->count = 0; - a->curr[n].b->next = a->heads[n].b; - a->heads[n].b = a->curr[n].b; - pp = (char **)&a->curr[n].b->name; + if (!p2) { + a->curr[n].b->next = a->heads[n].b; + a->heads[n].b = a->curr[n].b; + pp = (char **)&a->curr[n].b->name; - goto string1; + goto string1; + } + + return 0; /* overriding */ } if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) @@ -762,7 +795,7 @@ oom: } int -lws_ss_policy_parse_begin(struct lws_context *context) +lws_ss_policy_parse_begin(struct lws_context *context, int overlay) { struct policy_cb_args *args; char *p; @@ -773,6 +806,12 @@ lws_ss_policy_parse_begin(struct lws_context *context) return 1; } + if (overlay) + /* continue to use the existing lwsac */ + args->ac = context->ac_policy; + else + /* we don't want to see any old policy */ + context->pss_policies = NULL; context->pol_args = args; args->context = context; @@ -818,6 +857,14 @@ lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len) return m; } +int +lws_ss_policy_overlay(struct lws_context *context, const char *overlay) +{ + lws_ss_policy_parse_begin(context, 1); + return lws_ss_policy_parse(context, (const uint8_t *)overlay, + strlen(overlay)); +} + int lws_ss_policy_set(struct lws_context *context, const char *name) { diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index fc8b88e84..90dd44587 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -272,19 +272,9 @@ lws_ss_destroy_dll(struct lws_dll2 *d, void *user); int lws_sspc_destroy_dll(struct lws_dll2 *d, void *user); - -int -lws_ss_policy_parse_begin(struct lws_context *context); - -int -lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len); - int lws_ss_policy_set(struct lws_context *context, const char *name); -int -lws_ss_policy_parse_abandon(struct lws_context *context); - int lws_ss_sys_fetch_policy(struct lws_context *context); diff --git a/lib/secure-streams/system/fetch-policy/fetch-policy.c b/lib/secure-streams/system/fetch-policy/fetch-policy.c index bd0a3e446..ace853b37 100644 --- a/lib/secure-streams/system/fetch-policy/fetch-policy.c +++ b/lib/secure-streams/system/fetch-policy/fetch-policy.c @@ -45,7 +45,7 @@ ss_fetch_policy_rx(void *userobj, const uint8_t *buf, size_t len, int flags) struct lws_context *context = (struct lws_context *)m->opaque_data; if (flags & LWSSS_FLAG_SOM) { - if (lws_ss_policy_parse_begin(context)) + if (lws_ss_policy_parse_begin(context, 0)) return 1; m->partway = 1; } diff --git a/minimal-examples/secure-streams/minimal-secure-streams/README.md b/minimal-examples/secure-streams/minimal-secure-streams/README.md index 80a1c6928..cb9b3b360 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/README.md +++ b/minimal-examples/secure-streams/minimal-secure-streams/README.md @@ -23,6 +23,8 @@ Commandline option|Meaning -d |Debug verbosity in decimal, eg, -d15 -f| Force connecting to the wrong endpoint to check backoff retry flow -p| Run as proxy server for clients to connect to over unix domain socket +--force-portal|Force the SS Captive Portal Detection to feel it's behind a portal +--force-no-internet|Force the SS Captive Portal Detection to feel it can't reach the internet ``` [2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d] [-f] diff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c index 2c5a27869..bdd429741 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c +++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -37,7 +37,8 @@ */ // #define VIA_LOCALHOST_SOCKS -static int interrupted, bad = 1; +static int interrupted, bad = 1, force_cpd_fail_portal, + force_cpd_fail_no_internet; static lws_state_notify_link_t nl; /* @@ -184,24 +185,9 @@ static const char * const default_ss_policy = * every DHCP acquisition */ "\"captive_portal_detect\": {" -#if 1 - /* this does the actual test */ "\"endpoint\": \"connectivitycheck.android.com\"," "\"http_url\": \"generate_204\"," "\"port\": 80," -#endif -#if 0 - /* this looks like a captive portal due to redirect */ - "\"endpoint\": \"google.com\"," - "\"http_url\": \"/\"," - "\"port\": 80," -#endif -#if 0 - /* this looks like no internet */ - "\"endpoint\": \"warmcat.com\"," - "\"http_url\": \"/\"," - "\"port\": 999," -#endif "\"protocol\": \"h1\"," "\"http_method\": \"GET\"," "\"opportunistic\": true," @@ -309,6 +295,38 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, * state wait while we trigger the dependent action. */ switch (target) { + + case LWS_SYSTATE_INITIALIZED: /* overlay on the hardcoded policy */ + case LWS_SYSTATE_POLICY_VALID: /* overlay on the loaded policy */ + + if (target != current) + break; + + if (force_cpd_fail_portal) + + /* this makes it look like we're behind a captive portal + * because the overriden address does a redirect */ + + lws_ss_policy_overlay(context, + "{\"s\": [{\"captive_portal_detect\": {" + "\"endpoint\": \"google.com\"," + "\"http_url\": \"/\"," + "\"port\": 80" + "}}]}"); + + if (force_cpd_fail_no_internet) + + /* this looks like no internet, because the overridden + * port doesn't have anything that will connect to us */ + + lws_ss_policy_overlay(context, + "{\"s\": [{\"captive_portal_detect\": {" + "\"endpoint\": \"warmcat.com\"," + "\"http_url\": \"/\"," + "\"port\": 999" + "}}]}"); + break; + case LWS_SYSTATE_REGISTERED: size = lws_system_blob_get_size(ab); if (size) @@ -372,6 +390,14 @@ int main(int argc, const char **argv) lwsl_user("LWS secure streams test client [-d]\n"); + /* these options are mutually exclusive if given */ + + if (lws_cmdline_option(argc, argv, "--force-portal")) + force_cpd_fail_portal = 1; + + if (lws_cmdline_option(argc, argv, "--force-no-internet")) + force_cpd_fail_no_internet = 1; + info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; #if defined(LWS_SS_USE_SSPC)