Implement mruby_ssl_handshake_handler() #205

Merged
merged 7 commits into from Aug 24, 2016
@@ -29,6 +29,7 @@
conf.gem :github => 'iij/mruby-io'
conf.gem :github => 'iij/mruby-socket'
conf.gem :github => 'iij/mruby-pack'
+ conf.gem :github => 'iij/mruby-env'
# include the default GEMs
conf.gembox 'full-core'
@@ -96,6 +96,7 @@ static char *ngx_http_mruby_init_worker_inline(ngx_conf_t *cf, ngx_command_t *cm
static char *ngx_http_mruby_exit_worker_phase(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_mruby_exit_worker_inline(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
#if (NGX_HTTP_SSL)
+static char *ngx_http_mruby_ssl_handshake_phase(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_mruby_ssl_handshake_inline(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
#endif /* NGX_HTTP_SSL */
@@ -170,6 +171,9 @@ static ngx_command_t ngx_http_mruby_commands[] = {
#if (NGX_HTTP_SSL)
/* server config */
+ {ngx_string("mruby_ssl_handshake_handler"), NGX_HTTP_SRV_CONF | NGX_CONF_TAKE12, ngx_http_mruby_ssl_handshake_phase,
+ NGX_HTTP_SRV_CONF_OFFSET, 0, NULL},
+
{ngx_string("mruby_ssl_handshake_handler_code"), NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1,
ngx_http_mruby_ssl_handshake_inline, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL},
@@ -356,6 +360,7 @@ static void ngx_http_mruby_srv_conf_cleanup(void *data)
ngx_http_mruby_srv_conf_t *mscf = data;
NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mscf->state->mrb, mscf->ssl_handshake_code);
+ NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mscf->state->mrb, mscf->ssl_handshake_inline_code);
}
static void *ngx_http_mruby_create_srv_conf(ngx_conf_t *cf)
@@ -383,6 +388,7 @@ static void *ngx_http_mruby_create_srv_conf(ngx_conf_t *cf)
mscf->cert_data.len = 0;
mscf->cert_key_data.len = 0;
mscf->ssl_handshake_code = NGX_CONF_UNSET_PTR;
+ mscf->ssl_handshake_inline_code = NGX_CONF_UNSET_PTR;
cln->handler = ngx_http_mruby_srv_conf_cleanup;
cln->data = mscf;
@@ -399,8 +405,9 @@ static char *ngx_http_mruby_merge_srv_conf(ngx_conf_t *cf, void *parent, void *c
ngx_http_ssl_srv_conf_t *sscf;
NGX_MRUBY_MERGE_CODE(conf->ssl_handshake_code, prev->ssl_handshake_code);
+ NGX_MRUBY_MERGE_CODE(conf->ssl_handshake_inline_code, prev->ssl_handshake_inline_code);
- if (conf->ssl_handshake_code != NGX_CONF_UNSET_PTR) {
+ if (conf->ssl_handshake_code != NGX_CONF_UNSET_PTR || conf->ssl_handshake_inline_code != NGX_CONF_UNSET_PTR) {
sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module);
if (sscf == NULL || sscf->ssl.ctx == NULL) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0, MODULE_NAME " : no ssl configured for the server");
@@ -1033,7 +1040,7 @@ static ngx_int_t ngx_http_mruby_shared_state_compile(ngx_conf_t *cf, ngx_mrb_sta
#if (NGX_HTTP_SSL)
-static char *ngx_http_mruby_ssl_handshake_inline(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+static char *ngx_http_mruby_ssl_handshake_phase(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_mruby_srv_conf_t *mscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_mruby_module);
ngx_http_mruby_main_conf_t *mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_mruby_module);
@@ -1050,13 +1057,56 @@ static char *ngx_http_mruby_ssl_handshake_inline(ngx_conf_t *cf, ngx_command_t *
value = cf->args->elts;
- code = ngx_http_mruby_mrb_code_from_string(cf->pool, &value[1]);
+ code = ngx_http_mruby_mrb_code_from_file(cf->pool, &value[1]);
if (code == NGX_CONF_UNSET_PTR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, MODULE_NAME " : mruby_ssl_handshake_phase mrb_file(%s) open failed",
+ value[1].data);
return NGX_CONF_ERROR;
}
+ if (cf->args->nelts == 3) {
+ if (ngx_strcmp(value[2].data, "cache") == 0) {
+ code->cache = ON;
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\", vaild parameter is only \"cache\"",
+ &value[2]);
+ return NGX_CONF_ERROR;
+ }
+ }
mscf->ssl_handshake_code = code;
rc = ngx_http_mruby_shared_state_compile(cf, mscf->state, code);
if (rc != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, MODULE_NAME " : mruby_ssl_handshake_phase mrb_file(%s) open failed",
+ value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+static char *ngx_http_mruby_ssl_handshake_inline(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_mruby_srv_conf_t *mscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_mruby_module);
+ ngx_http_mruby_main_conf_t *mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_mruby_module);
+ ngx_str_t *value;
+ ngx_mrb_code_t *code;
+ ngx_int_t rc;
+
+ if (mscf->ssl_handshake_inline_code != NGX_CONF_UNSET_PTR) {
+ return "is duplicated";
+ }
+
+ /* share mrb_state of preinit */
+ mscf->state = mmcf->state;
+
+ value = cf->args->elts;
+
+ code = ngx_http_mruby_mrb_code_from_string(cf->pool, &value[1]);
+ if (code == NGX_CONF_UNSET_PTR) {
+ return NGX_CONF_ERROR;
+ }
+ mscf->ssl_handshake_inline_code = code;
+ rc = ngx_http_mruby_shared_state_compile(cf, mscf->state, code);
+ if (rc != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, MODULE_NAME " : mruby_ssl_handshake_inline mrb_string(%s) load failed",
value[1].data);
return NGX_CONF_ERROR;
@@ -2465,7 +2515,7 @@ static int ngx_http_mruby_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data)
}
mscf->connection = c;
- if (mscf->ssl_handshake_code == NGX_CONF_UNSET_PTR) {
+ if (mscf->ssl_handshake_code == NGX_CONF_UNSET_PTR && mscf->ssl_handshake_inline_code == NGX_CONF_UNSET_PTR) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, MODULE_NAME " : mruby ssl handler: unexpected error, mruby code not found");
return 1;
}
@@ -2474,7 +2524,16 @@ static int ngx_http_mruby_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data)
mrb = mscf->state->mrb;
mrb->ud = mscf;
ai = mrb_gc_arena_save(mrb);
- mrb_run(mrb, mscf->ssl_handshake_code->proc, mrb_top_self(mrb));
+ if (mscf->ssl_handshake_code != NGX_CONF_UNSET_PTR) {
+ if (!mscf->ssl_handshake_code->cache) {
+ NGX_MRUBY_STATE_REINIT_IF_NOT_CACHED(0, mscf->state, mscf->ssl_handshake_code,
+ ngx_http_mruby_state_reinit_from_file);
+ }
+ mrb_run(mrb, mscf->ssl_handshake_code->proc, mrb_top_self(mrb));
+ }
+ if (mscf->ssl_handshake_inline_code != NGX_CONF_UNSET_PTR) {
+ mrb_run(mrb, mscf->ssl_handshake_inline_code->proc, mrb_top_self(mrb));
+ }
if (mrb->exc) {
struct RString *str;
@@ -2487,11 +2546,13 @@ static int ngx_http_mruby_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data)
MODULE_NAME " : mrb_run failed: return 500 HTTP status code to client: error: %s", err_out);
}
NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mrb, mscf->ssl_handshake_code);
+ NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mrb, mscf->ssl_handshake_inline_code);
ngx_mrb_state_clean(NULL, mscf->state);
mrb_gc_arena_restore(mrb, ai);
return 0;
}
NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mrb, mscf->ssl_handshake_code);
+ NGX_MRUBY_CODE_MRBC_CONTEXT_FREE(mrb, mscf->ssl_handshake_inline_code);
ngx_mrb_state_clean(NULL, mscf->state);
mrb_gc_arena_restore(mrb, ai);
@@ -56,6 +56,7 @@ extern ngx_module_t ngx_http_mruby_module;
typedef struct {
ngx_mrb_state_t *state;
ngx_mrb_code_t *ssl_handshake_code;
+ ngx_mrb_code_t *ssl_handshake_inline_code;
ngx_str_t *servername;
ngx_str_t cert_path;
ngx_str_t cert_key_path;
View
@@ -114,6 +114,7 @@ if [ -n "$BUILD_DYNAMIC_MODULE" ]; then
fi
cp -pr test/html/* ${NGINX_INSTALL_DIR}/html/.
+sed -e "s|__NGXDOCROOT__|${NGINX_INSTALL_DIR}/html/|g" test/html/set_ssl_cert_and_key.rb > ${NGINX_INSTALL_DIR}/html/set_ssl_cert_and_key.rb
echo "====================================="
echo ""
@@ -29,6 +29,7 @@
conf.gem :github => 'iij/mruby-io'
conf.gem :github => 'iij/mruby-socket'
conf.gem :github => 'iij/mruby-pack'
+ conf.gem :github => 'iij/mruby-env'
# include the default GEMs
conf.gembox 'full-core'
View
@@ -64,6 +64,36 @@ http {
}
server {
+ listen 58085 ssl;
+ server_name _;
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+ ssl_certificate __NGXDOCROOT__/dummy.crt;
+ ssl_certificate_key __NGXDOCROOT__/dummy.key;
+
+ mruby_ssl_handshake_handler build/nginx/html/set_ssl_cert_and_key.rb;
+
+ location / {
+ mruby_content_handler_code "Nginx.rputs 'ssl test ok'";
+ }
+ }
+
+ server {
+ listen 58086 ssl;
+ server_name _;
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+ ssl_certificate __NGXDOCROOT__/dummy.crt;
+ ssl_certificate_key __NGXDOCROOT__/dummy.key;
+
+ mruby_ssl_handshake_handler build/nginx/html/set_ssl_cert_and_key.rb cache;
+
+ location / {
+ mruby_content_handler_code "Nginx.rputs 'ssl test ok'";
+ }
+ }
+
+ server {
listen 58081;
server_name localhost;
@@ -0,0 +1,3 @@
+ssl = Nginx::SSL.new
+ssl.certificate = "__NGXDOCROOT__/#{ssl.servername}.crt"
+ssl.certificate_key = "__NGXDOCROOT__/#{ssl.servername}.key"
View
@@ -412,6 +412,47 @@ def base_ssl(port)
t.assert_equal "", res
end
+dir = ENV['BUILD_DYNAMIC_MODULE'] == 'TRUE' ? 'build_dynamic' : 'build'
+fname = File.expand_path("../../../#{dir}/nginx/html/set_ssl_cert_and_key.rb", __FILE__)
+
+t.assert('ngx_mruby - ssl certificate changing - reading handler from file without caching') do
+ res = `curl -k #{base_ssl(58085) + '/'}`
+ t.assert_equal 'ssl test ok', res
+
+ content = File.read(fname).gsub('#{ssl.servername}', 'localhost')
+ File.open(fname, 'w') { |f| f.puts content }
+
+ cmd_l = "openssl s_client -servername localhost -connect localhost:58085 < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/://' | awk '{print (res = $6 - res)}' | tail -n 1"
+ cmd_h = "openssl s_client -servername hogehoge -connect 127.0.0.1:58085 < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/://' | awk '{print (res = $6 - res)}' | tail -n 1"
+ t.assert_equal "1", `#{cmd_l}`.chomp
+ t.assert_equal "1", `#{cmd_h}`.chomp
+
+ content = File.read(fname).gsub('localhost', '#{ssl.servername}')
+ File.open(fname, 'w') { |f| f.puts content }
+
+ t.assert_equal "1", `#{cmd_l}`.chomp
+ t.assert_equal "", `#{cmd_h}`.chomp
+end
+
+t.assert('ngx_mruby - ssl certificate changing - reading handler from file with caching') do
+ res = `curl -k #{base_ssl(58086) + '/'}`
+ t.assert_equal 'ssl test ok', res
+
+ content = File.read(fname).gsub('#{ssl.servername}', 'localhost')
+ File.open(fname, 'w') { |f| f.puts content }
+
+ cmd_l = "openssl s_client -servername localhost -connect localhost:58086 < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/://' | awk '{print (res = $6 - res)}' | tail -n 1"
+ cmd_h = "openssl s_client -servername hogehoge -connect 127.0.0.1:58086 < /dev/null 2> /dev/null | openssl x509 -text | grep Not"
+ t.assert_equal "1", `#{cmd_l}`.chomp
+ t.assert_equal "", `#{cmd_h}`.chomp
+
+ content = File.read(fname).gsub('localhost', '#{ssl.servername}')
+ File.open(fname, 'w') { |f| f.puts content }
+
+ t.assert_equal "1", `#{cmd_l}`.chomp
+ t.assert_equal "", `#{cmd_h}`.chomp
+end
+
t.assert('ngx_mruby - issue_172', 'location /issue_172') do
res = HttpRequest.new.get base + '/issue_172/index.html'
expect_content = 'hello world'.upcase