Welcome to ngx_mruby Wiki
※ hello world simple benchmark, see details of blog entry.
Documents
Section titled “Documents”- Install
- Test
- Directives
- Class and Method
- Use Case
- Release notes: read “Behavior changes” before upgrading
- Trust boundary: what ngx_mruby trusts, what it treats as client input, and what a script is responsible for
Branches and releases
Section titled “Branches and releases”- Branch strategy: what
master,nextandv2.xhold, and what to pin to stay on 2.x - Promoting v3 to master: the procedure, for maintainers
What’s ngx_mruby
Section titled “What’s ngx_mruby”ngx_mruby is A Fast and Memory-Efficient Web Server Extension Mechanism Using Scripting Language mruby for nginx.
- ngx_mruby is to provide an alternative to lua-nginx-module or mod_mruby of Apache httpd.
- Unified Ruby Code between Apache(mod_mruby), nginx(ngx_mruby) and other Web server software(plan) for Web server extensions.
- You can implement nginx modules by Ruby scripts on nginx!
- You can implement some Web server software extensions by same Ruby code (as possible)
- Supported nginx main-line and stable-line
- Benchmark between ngx_mruby(19423.42 req/sec) and lua-nginx-module(13894.30 req/sec)
# location /proxy {# mruby_set $backend "/path/to/proxy.rb";# proxy_pass http://$backend;# }
backends = [ "test1", "test2", "test3",]
r = Redis.new "192.168.12.251", 6379r.get backends[rand(backends.length)]This example needs the Redis class of mruby-redis, and a running Redis server. The default build_config.rb builds mruby-redis again since 2.7.2; 2.7.1 left it out.
- see examples
- Sample of Unified Ruby Code between Apache(mod_mruby) and nginx(ngx_mruby) for Web server extensions
- You can implement some Web server software extensions by same Ruby code (as possible)
# Unified Ruby Code between Apache(mod_mruby) and nginx(ngx_mruby)# for Web server extensions.## Apache httpd.conf by mod_mruby## <Location /mruby># mrubyHandlerMiddle "/path/to/unified_hello.rb"# </Location>## nginx nginx.conf by ngx_mruby## location /mruby {# mruby_content_handler "/path/to/unified_hello.rb";# }#
Server = get_server_class
Server::rputs "Hello #{Server::module_name}/#{Server::module_version} world!"# mod_mruby => "Hello mod_mruby/0.9.3 world!"# ngx_mruby => "Hello ngx_mruby/0.0.1 world!"Abstract
Section titled “Abstract”As the increase of large-scale and complex Web services, not only a development of Web applications but also an implementation of Web server extensions is required in many cases. The Web server extensions were mainly implemented in C language because of fast and memory-efficient behavior, but extension methods using scripting language are proposed with consideration of maintainability and productivity. However, if the existing methods primarily intended to enhance not the implementation of Web applications but the implementation of internal processing of the Web server, the problem remains in terms of speed, memory-efficiency and safety. Therefore, we propose a fast and memory-efficient Web server extension mechanism using scripting language. We design the architecture that a server process creates the region to save the state of the interpreter at the server process startup, and multiple scripts share the region in order to process fast when the script is called as internal processing from a Web server process. The server process frees the global variables table, the exception flag and the byte-code which cause the increase of memory usage mainly, in order to reduce the memory usage and extend safety by preventing interference between each scripts because of sharing the region. We implement the mechanism that can extend the internal processing of nginx easily by Ruby scripts using nginx and the embeddable scripting language mruby. It’s called “ngx_mruby”.
What a worker keeps between requests
Section titled “What a worker keeps between requests”The abstract above describes the design of the paper. ngx_mruby today does not free the global variables table between scripts. nginx creates one mrb_state for the http {} block (and one for the stream {} block) while it reads the configuration, and runs the init code (mruby_init, mruby_stream_init) and the server configuration code (mruby_server_context_handler_code) in it before it starts the workers, in the master process. Each worker process gets its own copy when nginx forks it, and runs all the other mruby code of the block in that copy: the init_worker and exit_worker code and every handler and filter, in every server and location, for every client. A reload or a restart starts new workers with a new state.
Global variables, constants, instance variables of main (the self of handler code), methods defined with def, classes and modules with their constants, and what Userdata holds (it keeps its values in a global variable) stay in the worker from one request to the next. What one request leaves behind is seen by every later request that the same worker handles, also when the code raised an exception after it made the change. Each worker has its own copy, so one worker does not see the values of another. After each run, ngx_mruby clears the exception (mrb->exc); a script file without cache is read and compiled again for the next run, and inline code and cached scripts keep the byte-code compiled at startup. The garbage collector frees the objects that nothing refers to any more. Local variables do not stay: each run of handler code starts without them.
Do not keep the data of one request in global variables, constants, instance variables of main or Userdata, since the next request of the worker, from any client, sees it. Use local variables, and read and write the request through Nginx::Request, Nginx::Var, Nginx::Headers_in and Nginx::Headers_out (in stream {}, where code runs per session, Nginx::Stream::Connection). To pass data from one handler to a later handler of the same request, declare an nginx variable with set $name ""; and read and write it through Nginx::Var, or set the Userdata value in every request before it is read. A set in the location runs after mruby_rewrite_handler and empties the variable again, so declare a variable that a rewrite handler writes with set in the server {} block. Keep in globals and constants only what all requests of the worker are meant to share, such as settings that the init or init_worker code prepares. Nginx.remove_global_variable removes a global variable.
Trust boundary
Section titled “Trust boundary”ngx_mruby runs Ruby code that the operator of the nginx server writes, and it treats what a client sends as input to that code. It is not a sandbox: it does not confine the code that the configuration loads. This section says what each side of the boundary is, what ngx_mruby is responsible for, what the operator’s code is responsible for, and how this maps onto the scope of SECURITY.md.
The trusted side: the configuration and the code it loads
Section titled “The trusted side: the configuration and the code it loads”- The nginx configuration and all the Ruby code that it loads are trusted: the script files that the directives name (
mruby_content_handler /path/to/handler.rband the others), the inline code of the*_codedirectives, and the gems thatbuild_config.rbbuilds into the module. ngx_mruby compiles and runs this code as it is, and does not check what the code does. - With
mruby_add_handler on, the request URI chooses which file runs: ngx_mruby maps the URI to a path under the location’s root and runs that file as a content handler. Every file that the location matches under that root is code of this kind. - The init code (
mruby_init,mruby_init_code,mruby_stream_init,mruby_stream_init_code) and the server configuration code (mruby_server_context_handler_code,mruby_stream_server_context_code) run in the master process while nginx reads the configuration, with the privileges that nginx was started with (root, when nginx is started as root). All the other code (init_worker and exit_worker code, every handler and filter, the SSL handshake handlers, the stream handlers) runs in the worker processes, with the privileges of the workers (the user of theuserdirective, when the master runs as root). - All the code of an
http {}block runs in onemrb_stateper worker (and one for thestream {}block), shared by every server, location and client of that block (What a worker keeps between requests). The code of one location can read and change the global variables, constants, classes and methods that the code of another server or location defined. ngx_mruby does not separate the scripts of one block from each other. - The default build gives this code mruby’s full-core gems and the gems of
build_config.rb. Among them areevalandinstance_eval(mruby-eval), files,IO.popenand backquotes, which run shell commands (mruby-io), sockets (mruby-socket),sleep(mruby-sleep),Process(iij/mruby-process),ENVandDir. A script can do what the user of its process can do.
Whoever can change the nginx configuration, a script that it loads, a directory that holds such a script, or build_config.rb can therefore run any code as the workers, and through the init code as the master. A setup that takes nginx configuration or Ruby code from parties that the operator does not trust, such as a multi-tenant service that runs a snippet per customer, gives each of those parties that power over the whole nginx and every other party’s traffic. ngx_mruby has no means to confine such code, and does not support this use.
The untrusted side: what a client sends
Section titled “The untrusted side: what a client sends”Everything that a client sends is untrusted input: the request line, the URI and its arguments, the headers and cookies, the request body, and the values that nginx derives from them. Scripts read these values through Nginx::Request (uri, unparsed_uri, args, uri_args, post_args, method, headers_in, get_body, user and the others), Nginx::Headers_in, and Nginx::Var (for example $http_*, $arg_*, $cookie_* and $request_uri). The server name that a client sends in the TLS handshake (Nginx::SSL#servername) is untrusted in the same way. A response from an upstream server, also one that a subrequest or an output filter reads, is as trustworthy as that upstream server.
What ngx_mruby is responsible for
Section titled “What ngx_mruby is responsible for”- nginx parses the request before ngx_mruby sees it. The C code of ngx_mruby (
src/) and its bundled gems (mrbgems/) hand the request’s values to the script as Ruby strings and other objects, and take the values that the script passes back as data. A configuration that uses the directives and methods as they are documented is not meant to make ngx_mruby read or write memory outside its buffers, end or hang the worker, or give one request the data of another, whatever the client sends and also when the script passes client input on to a method of ngx_mruby. Where it does, that is a defect of ngx_mruby, also when the script’s own call, and no client value, triggers it. - 2.7.1 and 2.7.2 fixed defects of this kind in calls that the configuration’s own code makes; in some of them a client value had no part. In 2.7.0 a body filter that made a body written with
Nginx.rputsorNginx.echoshorter sent bytes from beyond the end of the new body, and request-scoped methods called outside a request could use the data of another request (2.7.1 release notes, entries 2 and 8, and the advisory GHSA-h7fw-wf49-8rv5). In 2.7.1Nginx::Varread memory outside the name or the arguments for an empty name, a name made only of=, and a setter without its value, andNginx::Var#setchanged the String or Symbol passed as the name (2.7.2 release notes, entries 1 and 2). The accessor now checks the name and the arguments before it reads them, and#setlooks the variable up by a lowercased copy of the name, whatever value the script passes, also one that it took from the client.
What the operator’s code is responsible for
Section titled “What the operator’s code is responsible for”ngx_mruby runs what the script asks for. What follows from passing client input on is the script’s responsibility.
-
Validate client input before it reaches anything that acts on it:
evalorinstance_eval, a shell command (backquotes,IO.popen,Process), a file path (File,Dir,Nginx::SSL#certificate=and#certificate_key=), the name of an nginx variable (Nginx::Varwith a name built at run time,Nginx::Var#set), an upstream address (mruby_set $backend,Nginx::Upstream#server=), a redirect target or a response header. Compare the input with a list of allowed values, or parse it into the type that the code expects, and reject what does not match. For example, a handshake handler that builds the path of a certificate from the server name lets the client choose the path; look the name up in a table instead, so that only names with a certificate reach a path:http {mruby_init_code 'CERT_DIRS = {"example.com" => "/etc/nginx/certs/example.com","www.example.com" => "/etc/nginx/certs/www.example.com",}';server {listen 443 ssl;ssl_certificate /etc/nginx/certs/default.crt;ssl_certificate_key /etc/nginx/certs/default.key;mruby_ssl_handshake_handler_code 'ssl = Nginx::SSL.newdir = CERT_DIRS[ssl.servername]if dirssl.certificate = "#{dir}/cert.pem"ssl.certificate_key = "#{dir}/key.pem"end';}}A handshake whose code sets no certificate uses the server’s
ssl_certificateandssl_certificate_key(Nginx::SSL ).certificate=andcertificate_key=make the worker read the files during the handshake, which blocks it (see the next item). To avoid the read, load the PEM data in the init code and set it withcertificate_data=andcertificate_key_data=. -
Keep the data of one request out of global variables, constants, instance variables of
mainandUserdata: the next request of the worker, from any client, sees it (What a worker keeps between requests). -
Avoid blocking calls on the request path. A worker serves many clients at once, and while a script waits in
sleep, a socket, a file, a shell command,Processor a Redis call, the worker serves no other client. A client that can make a script wait can therefore slow down every client of that worker. UseNginx::Async.sleepandNginx::Async::HTTP.sub_requestwhere their documentation says that they work, and addcacheto the script directives, setmruby_cache on, or use inline code, so that a script file is not read from disk on every request. -
Keep the nginx configuration, the scripts, the directories that hold them and
build_config.rbwritable only by the operator. Withmruby_add_handler on, keep only handler scripts under that root, and do not store files that clients upload there.
How this maps onto SECURITY.md
Section titled “How this maps onto SECURITY.md”Report privately, as SECURITY.md describes, a defect of ngx_mruby (src/, mrbgems/) that makes ngx_mruby read or write memory outside its buffers, end or hang the worker, or reveal or change the data of another request, in a configuration that uses the directives and methods as they are documented, in particular when input a client controls triggers it. This includes a script that passes client input on to a method of ngx_mruby, and a documented call of the script that triggers the defect without any client value. Defects of bundled or third-party components go to their upstream projects as well, as SECURITY.md says. If you are not sure whether a defect is in scope, report it privately.
Outside the scope of SECURITY.md is what follows from the trusted side doing what it was written to do: a script that passes client input to eval, a shell command, a file path or another of the uses above; data of one request that a script keeps in a global and a later request sees; a blocking call in a script that stalls its worker; anyone who can change the configuration, a script or build_config.rb running code; and a setup that takes nginx configuration or Ruby code from parties that the operator does not trust.
License
Section titled “License”under the MIT License:
