If set, will call a given handler with the prototype of
`void body_chunk(struct http_request *req, const void *data, size_t len);`
for each chunk of the received HTTP body, allowing a developer to handle
it in their own way.
The incoming body is still being handled and retained in the same way
as before (in a kore_buf or temporary file).
While here, allow HTTP_STATUS_CONTINUE to work via http_response() and
make the handling of incoming HTTP header data a bit better.
Introduce an on_headers callback for routes, allowing one to inspect
the headers before the request is processed further.
Additionall,
Add a new way of obtaining HTTP headers. Much like http_argument_get_*()
functions, these new APIs allow you to fetch the data of an HTTP header
as a specified C type.
The new APIs are:
* http_request_header_int16()
* http_request_header_uint16()
* http_request_header_int32()
* http_request_header_uint32()
* http_request_header_int64()
* http_request_header_uint64()
* http_request_header_float()
* http_request_header_double()
Should make it easier to operate in HTTP header data in a safe way.
No need to always roll your own string to int conversion functions.
Routes are now configured in a context per route:
route /path {
handler handler_name
methods get post head
validate qs:get id v_id
}
All route related configurations are per-route, allowing multiple
routes for the same path (for different methods).
The param context is removed and merged into the route context now
so that you use the validate keyword to specify what needs validating.
This is the same as http_response() except it will automatically
close the connection after the response is sent.
This is a bit easier than setting CONN_CLOSE_EMPTY yourself manually.
Starting with the privsep config, this commit changes the following:
- Removes the root, runas, keymgr_root, keymgr_runas, acme_root and
acme_runas configuration options.
Instead these are now configured via a privsep configuration context:
privsep worker {
root /tmp
runas nobody
}
This is also configurable via Python using the new kore.privsep() method:
kore.privsep("worker", root="/tmp", runas="nobody", skip=["chroot"])
Tied into this we also better handle worker startup:
- Per worker process, wait until it signalled it is ready.
- If a worker fails at startup, display its last log lines more clearly.
- Don't start acme process if no domain requires acme.
- Remove each process its individual startup log message in favour
of a generalized one that displays its PID, root and user.
- At startup, log the kore version and built-ins in a nicer way.
- The worker processes now check things they need to start running
before signaling they are ready (such as access to CA certs for
TLS client authentication).
Before each worker process would either directly print to stdout if
Kore was running in foreground mode, or syslog otherwise.
With this commit the workers will submit their log messages to the
parent process who will either put it onto stdout or syslog.
This change in completely under the hood and users shouldn't care about it.
1) Add @kore.route as a decorator for Python.
This decorator can be used on non-class methods to automatically
declare their route and parameters.
Takes the same arguments as the kore.domain.route function that
exists today.
Provides a nice clean way of setting up Kore if you dont want
a whole class based approach.
2) Remove the requirement for the name for kore.server() and the
kore.domain(attach=) keywords.
Instead of no name was given, the name "default" is used in both
places resulting in less boilerplating.
3) Allow multiple routes to be defined for the same URI as long
as the methods are different. So you can have one method for GET /
and another for POST /.
All changes combined condense the initial experience of getting
a Kore Python app up and running:
eg:
import kore
kore.server(ip="127.0.0.1", port="8888", tls=False)
kore.domain("*")
@kore.route("/", methods=["get"])
async def index(req):
req.response(200, b'get method')
@kore.route("/", methods=["post"])
async def index_post(req)
req.response(200, b'post method')
- Kore now only supports OpenSSL 1.1.1 and LibreSSL 3.x.
- Revise the default TLS ciphersuites.
- Kore now carries ffdhe4096.pem and installs it under PREFIX/share/kore.
- Kore its tls_dhparam config setting defaults to the path mentioned above
so you no longer have to set it.
- Try harder to mark integers as KORE_JSON_TYPE_INTEGER, especially if
they fit in the internal representation of one (int64_t).
- Move error codes into the JSON code itself, rather then requiring
a kore_json data structure. This allows the JSON API to relay errors
such as "item not found" or "type mismatch" properly when looking at items.
- When asking for a KORE_JSON_TYPE_INTEGER_U64 and a KORE_JSON_TYPE_INTEGER
was found with the same name, check if it could be returned properly and do
so if possible.
- Make sure tls-alpn01 works even if the underlying SSL library ends up
calling the ALPN callback *before* the SNI extension was parsed and
the correct domain was selected.
LibreSSL still does this, and older OpenSSL did too I believe, however
OpenSSL grew a clue and always makes sure SNI is called first.
Yes, TLS extensions have no fixed order but it still makes sense to
notify applications using your library of the SNI extension first
before anything else almost.
Oh well.
Add 2 new types:
KORE_JSON_TYPE_INTEGER
signed integer type, internally stored as s64.
KORE_JSON_TYPE_INTEGER_U64
unsigned integer type, internally stored as u64.
Kore JSON parser will prefer marking integers as INTEGER_U64 if it
was unsigned and did not have fractions.
This handles the default option parsing in Kore and should be called
by single_binary=yes builds in kore_parent_configure() unless they
want to handle their own argument parsing.
- Remove the edge trigger io hacks we had in place.
- Use level triggered io for the libcurl fds instead.
- Batch all curl events together and process them at the end
of our worker event loop.
If a coroutine is killed from another coroutine and the killed coroutine
was waiting on a kore.lock() object, it would have been incorrectly
woken up again once said lock was released.
This would cause a Python exception that a generator was already
running and a crash due to the pool element already being freed.
Track the active locking operation per coroutine so we can remove
the coroutine if it is killed, fixing the problem.
This method allows you to set a Python object and obtain it
by calling the method again without any arguments.
eg:
foo = SomeClass()
kore.app(foo)
foo = kore.app()
- Fix the curl-extract-opt.sh generation script to work on newer
curl releases as the header changed slightly.
- Use the correct handles when calling curl_easy_setopt() inside
of our setopt functions exported via Python.
- Add a curl.setbody() method, allowing a body to be sent to be set.
(eg when sending mail via SMTP).
- Regen of our python_curlopt.h from 7.71.1
Inside the domain contexts a 'redirect' rule will allow you to redirect
a request to another URI.
Ex:
Redirect all requests with a 301 to example.com
redirect ^/.*$ 301 https://example.com
Using capture groups
redirect ^/account/(.*)$ 301 https://example.com/account/$1
Adding the query string in the mix
redirect ^/(.*)$ 301 https://example.com/$1?$qs
Kore already exposed parts of this via the kore.httpclient() method but
this commit takes it a bit further and exposes the libcurl interface
completely (including the setopt options).
tldr:
handle = kore.curl("ftp://ftp.eu.openbsd.org/pub/OpenBSD/README")
handle.setopt(kore.CURLOPT_TIMEOUT, 5)
data = await handle.run()
print("%s" % data.decode())
These changes improve the constraint kore had with client authentication and
multiple domains.
- Add kore_x509_subject_name() which will return a C string containing
the x509 subject name in full (in utf8).
- Log TLS errors if client authentication was turned on, will help debug
issues with client authentication in the future.
- If SNI was present in the TLS handshake, check it against the host specified
in the HTTP request and send a 421 in case they mismatch.
- Throw a 403 if client authentication was enabled but no client certificate
was specified.
A new acme process is created that communicates with the acme servers.
This process does not hold any of your private keys (no account keys,
no domain keys etc).
Whenever the acme process requires a signed payload it will ask the keymgr
process to do the signing with the relevant keys.
This process is also sandboxed with pledge+unveil on OpenBSD and seccomp
syscall filtering on Linux.
The implementation only supports the tls-alpn-01 challenge. This means that
you do not need to open additional ports on your machine.
http-01 and dns-01 are currently not supported (no wildcard support).
A new configuration option "acme_provider" is available and can be set
to the acme server its directory. By default this will point to the
live letsencrypt environment:
https://acme-v02.api.letsencrypt.org/directory
The acme process can be controlled via the following config options:
- acme_root (where the acme process will chroot/chdir into).
- acme_runas (the user the acme process will run as).
If none are set, the values from 'root' and 'runas' are taken.
If you want to turn on acme for domains you do it as follows:
domain kore.io {
acme yes
}
You do not need to specify certkey/certfile anymore, if they are present
still
they will be overwritten by the acme system.
The keymgr will store all certificates and keys under its root
(keymgr_root), the account key is stored as "/account-key.pem" and all
obtained certificates go under "certificates/<domain>/fullchain.pem" while
keys go under "certificates/<domain>/key.pem".
Kore will automatically renew certificates if they will expire in 7 days
or less.
If set to "yes" then Kore will trace its child processes and properly
notify you of seccomp violations while still allowing the syscalls.
This can be very useful when running Kore on new platforms that have
not been properly tested with seccomp, allowing me to adjust the default
policies as we move further.
We actually woke up the coroutine that originally spawned the process
when we reap it, but another coroutine may have taken over the object.
This mimics how we do things for the pysock_op things.
Now everything that has the "newer" OpenSSL API (1.1.x) is hidden
behind a KORE_OPENSSL_NEWER_API define. Tone down minimum libressl
version again to 2.7.5.
Allow JSON to be constructed via kore_json_create_item and its
handy macro family:
- kore_json_create_object()
- kore_json_create_array()
- kore_json_create_string()
- kore_json_create_number()
- kore_json_create_literal().
Adds kore_json_item_tobuf() to convert a JSON item into a string
representation in a kore_buf data structure.
Renames the kore_json_get* family to kore_json_find* instead.
Allows for quite clean code:
struct kore_buf buf;
struct kore_json_item *root;
root = kore_json_create_object(NULL, NULL);
kore_json_create_string(root, "hello", "world");
kore_json_create_number(root, "value", 2.241);
kore_buf_init(&buf, 128);
kore_json_item_tobuf(root, &buf);
kore_json_item_free(root);
kore_buf_cleanup(&buf);
In cases where a request is immediately completed in libcurl its multi
handle and no additional i/o is happening a coro can get stuck waiting
to be run.
Prevent this by lowering netwait from KORE_WAIT_INFINITE if there
are pending python coroutines.
Changes kore_curl_init() to take a flag parameter, much like pgsql api
in which you specify KORE_CURL_ASYNC or KORE_CURL_SYNC.
If KORE_CURL_ASYNC is specified, Kore will behave as before.
If Kore_CURL_SYNC is specified, Kore will execute the libcurl immediately
and return once it has been completed.
Mostly compliant, ignores \uXXXX in strings for now.
New API functions:
void kore_json_init(struct kore_json *json, const u_int8_t *data, size_t len);
- Prepares JSON data for parsing.
int kore_json_parse(struct kore_json *json)
- Parses the JSON data prepared via kore_json_init. Returns KORE_RESULT_ERROR
if parsing failed or KORE_RESULT_OK if it succeeded.
struct kore_json_item *kore_json_get(struct kore_json *json, const char *path,
int type);
- Try to find the object matching a given search patch and type.
eg, given a JSON structure of:
{
"reasons": {
"strings": [
"first reason",
"second"
]
}
}
one can obtain the second element in the reasons.strings array via:
item = kore_json_get(json, "reasons/strings[0]", KORE_JSON_TYPE_STRING);
Returns NULL if the item was not found or a type mismatch was hit,
otherwise will return the item of that type.
The kore_json_item data structure has a data member that contains the
relevant bits depending on the type:
KORE_JSON_TYPE_ARRAY, KORE_JSON_TYPE_OBJECT:
the data.items member is valid.
KORE_JSON_TYPE_STRING:
the data.string member is valid.
KORE_JSON_TYPE_NUMBER:
the data.number member is valid.
KORE_JSON_TYPE_LITERAL:
the data.literal member is valid.
void kore_json_cleanup(struct kore_json *json);
- Cleanup any resources
const char *kore_json_strerror(struct kore_json *json);
- Return pointer to human readable error string.
This allows you to send Python objects that can be run through pickle
to other worker processes.
If your application implements koreapp.onmsg() you will be able to receive
these objects.