2019-08-24 05:36:24 +02:00
|
|
|
# Jetforce
|
|
|
|
|
2019-08-28 04:55:05 +02:00
|
|
|
An experimental TCP server for the new, under development Gemini Protocol.
|
2020-05-19 07:00:56 +02:00
|
|
|
Learn more about Gemini [here](https://portal.mozz.us/).
|
2019-08-24 05:36:24 +02:00
|
|
|
|
2020-05-19 06:37:52 +02:00
|
|
|
![Rocket Launch](logo.jpg)
|
2019-08-09 03:45:26 +02:00
|
|
|
|
2020-05-31 23:26:03 +02:00
|
|
|
## Table of Contents
|
|
|
|
|
|
|
|
* [Features](#features)
|
|
|
|
* [Installation](#installation)
|
|
|
|
* [Usage](#usage)
|
|
|
|
* [Deployment](#deployment)
|
2020-05-31 23:29:35 +02:00
|
|
|
* [Releases](#releases)
|
2020-05-31 23:26:03 +02:00
|
|
|
* [License](#license)
|
|
|
|
|
2019-08-05 04:57:34 +02:00
|
|
|
## Features
|
|
|
|
|
2020-05-19 06:59:47 +02:00
|
|
|
- A built-in static file server with support for gemini directories and CGI scripts.
|
2020-05-24 06:45:01 +02:00
|
|
|
- A full framework for writing python applications that loosely mimics [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface).
|
2020-05-19 06:59:47 +02:00
|
|
|
- A lean, modern python codebase with type hints and black formatting.
|
2020-05-24 03:56:19 +02:00
|
|
|
- A solid foundation built on top of the [twisted](https://twistedmatrix.com/trac/) asynchronous networking engine.
|
2019-08-05 04:57:34 +02:00
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
2020-05-24 06:45:39 +02:00
|
|
|
Requires Python 3.7 or newer.
|
2019-08-05 04:57:34 +02:00
|
|
|
|
2020-05-24 06:45:39 +02:00
|
|
|
The latest stable release can be installed from [PyPI](https://pypi.org/project/Jetforce/):
|
2019-08-24 06:34:05 +02:00
|
|
|
|
2019-08-28 04:52:38 +02:00
|
|
|
```bash
|
2019-08-05 04:57:34 +02:00
|
|
|
$ pip install jetforce
|
|
|
|
```
|
|
|
|
|
2020-05-19 07:43:18 +02:00
|
|
|
Or, install from source:
|
2019-08-05 04:57:34 +02:00
|
|
|
|
2019-08-28 04:52:38 +02:00
|
|
|
```bash
|
2019-08-05 04:57:34 +02:00
|
|
|
$ git clone https://github.com/michael-lazar/jetforce
|
|
|
|
$ cd jetforce
|
2020-05-19 07:43:18 +02:00
|
|
|
$ python setup.py install
|
2019-08-05 04:57:34 +02:00
|
|
|
```
|
2019-08-24 06:34:05 +02:00
|
|
|
|
2020-06-08 20:18:19 +02:00
|
|
|
Or, install into a python virtual environment:
|
2020-06-08 20:17:33 +02:00
|
|
|
|
|
|
|
```bash
|
|
|
|
# Create a project directory somewhere
|
|
|
|
$ mkdir /opt/jetforce
|
|
|
|
|
2020-06-08 20:18:19 +02:00
|
|
|
# Activate a virtual environment and install jetforce
|
2020-06-08 20:17:33 +02:00
|
|
|
$ python3 -m virtualenv /opt/jetforce/venv
|
|
|
|
$ source /opt/jetforce/venv/bin/activate
|
|
|
|
$ pip install jetforce
|
|
|
|
|
|
|
|
# The jetforce launch script will be placed here
|
|
|
|
$ /opt/jetforce/venv/bin/jetforce
|
|
|
|
```
|
|
|
|
|
2019-08-24 06:34:05 +02:00
|
|
|
## Usage
|
|
|
|
|
|
|
|
Use the ``--help`` flag to view command-line options:
|
|
|
|
|
2019-08-28 04:52:38 +02:00
|
|
|
```bash
|
2019-08-24 06:34:05 +02:00
|
|
|
$ jetforce --help
|
2019-09-23 05:02:28 +02:00
|
|
|
usage: jetforce [-h] [-V] [--host HOST] [--port PORT] [--hostname HOSTNAME]
|
2019-08-30 05:35:29 +02:00
|
|
|
[--tls-certfile FILE] [--tls-keyfile FILE] [--tls-cafile FILE]
|
|
|
|
[--tls-capath DIR] [--dir DIR] [--cgi-dir DIR]
|
|
|
|
[--index-file FILE]
|
2019-08-24 06:34:05 +02:00
|
|
|
|
|
|
|
An Experimental Gemini Protocol Server
|
|
|
|
|
|
|
|
optional arguments:
|
2019-08-28 04:52:38 +02:00
|
|
|
-h, --help show this help message and exit
|
2019-09-23 05:02:28 +02:00
|
|
|
-V, --version show program's version number and exit
|
2020-05-24 06:47:51 +02:00
|
|
|
|
|
|
|
server configuration:
|
2019-08-28 04:52:38 +02:00
|
|
|
--host HOST Server address to bind to (default: 127.0.0.1)
|
|
|
|
--port PORT Server port to bind to (default: 1965)
|
2019-08-30 05:35:29 +02:00
|
|
|
--hostname HOSTNAME Server hostname (default: localhost)
|
2019-08-28 04:52:38 +02:00
|
|
|
--tls-certfile FILE Server TLS certificate file (default: None)
|
|
|
|
--tls-keyfile FILE Server TLS private key file (default: None)
|
2019-08-30 05:35:29 +02:00
|
|
|
--tls-cafile FILE A CA file to use for validating clients (default: None)
|
|
|
|
--tls-capath DIR A directory containing CA files for validating clients
|
|
|
|
(default: None)
|
2020-05-24 06:47:51 +02:00
|
|
|
|
|
|
|
fileserver configuration:
|
2019-08-30 05:35:29 +02:00
|
|
|
--dir DIR Root directory on the filesystem to serve (default:
|
|
|
|
/var/gemini)
|
2019-08-28 04:52:38 +02:00
|
|
|
--cgi-dir DIR CGI script directory, relative to the server's root
|
|
|
|
directory (default: cgi-bin)
|
|
|
|
--index-file FILE If a directory contains a file with this name, that
|
|
|
|
file will be served instead of auto-generating an index
|
|
|
|
page (default: index.gmi)
|
2019-08-24 06:34:05 +02:00
|
|
|
```
|
|
|
|
|
2019-09-23 05:27:55 +02:00
|
|
|
### Setting the ``hostname``
|
2019-08-30 05:35:29 +02:00
|
|
|
|
2019-09-23 05:38:09 +02:00
|
|
|
The server's hostname should be set to the *DNS* name that you expect to
|
|
|
|
receive traffic from. For example, if your jetforce server is running on
|
2019-10-19 00:35:42 +02:00
|
|
|
"gemini://cats.com", you should set the hostname to "cats.com". Any URLs
|
2019-09-23 05:46:56 +02:00
|
|
|
that do not match this hostname will be refused by the server, including
|
2019-09-23 05:47:15 +02:00
|
|
|
URLs that use a direct IP address such as "gemini://174.138.124.169".
|
2019-08-30 05:35:29 +02:00
|
|
|
|
2020-01-13 01:31:08 +01:00
|
|
|
### Setting the ``host``
|
|
|
|
|
2020-01-13 03:52:33 +01:00
|
|
|
The server's host should be set to the local socket that you want to
|
2020-01-13 01:31:08 +01:00
|
|
|
bind to:
|
|
|
|
|
2020-01-13 03:52:16 +01:00
|
|
|
- ``--host "127.0.0.1"`` - Accept local connections only
|
|
|
|
- ``--host "0.0.0.0"`` - Accept remote connections over IPv4
|
|
|
|
- ``--host "::"`` - Accept remote connections over IPv6
|
2020-01-13 01:31:08 +01:00
|
|
|
- ``--host ""`` - Accept remote connections over any interface (IPv4 + IPv6)
|
|
|
|
|
2019-08-24 06:34:05 +02:00
|
|
|
### TLS Certificates
|
|
|
|
|
|
|
|
The gemini specification *requires* that all connections be sent over TLS.
|
|
|
|
|
2019-08-30 05:35:29 +02:00
|
|
|
If you do not provide a TLS certificate file using the ``--tls-certfile`` flag,
|
|
|
|
jetforce will automatically generate a temporary cert for you to use. This is
|
|
|
|
great for making development easier, but before you expose your server to the
|
2020-05-19 07:43:18 +02:00
|
|
|
public internet you should setup something more permanent. You can generate
|
2019-08-30 05:35:29 +02:00
|
|
|
your own self-signed server certificate, or obtain one from a Certificate
|
|
|
|
Authority like [Let's Encrypt](https://letsencrypt.org).
|
2019-08-24 06:34:05 +02:00
|
|
|
|
2020-05-19 07:43:18 +02:00
|
|
|
Here's an example `openssl` command that you can use to generate a self-signed certificate:
|
2019-08-24 06:34:05 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
$ openssl req -newkey rsa:2048 -nodes -keyout {hostname}.key \
|
|
|
|
-nodes -x509 -out {hostname}.crt -subj "/CN={hostname}"
|
|
|
|
```
|
|
|
|
|
2020-05-30 20:39:31 +02:00
|
|
|
Jetforce also supports TLS client certificates (both self-signed and CA authorised).
|
2020-05-24 07:00:20 +02:00
|
|
|
Requests that are made with client certificates will include additional
|
|
|
|
CGI/environment variables with information about the TLS connection.
|
2020-05-19 07:43:18 +02:00
|
|
|
|
|
|
|
You can specify a CA for client validation with the ``--tls-cafile`` or ``--tls-capath``
|
2020-05-30 20:39:31 +02:00
|
|
|
flags. Connections validated by the CA will have the ``TLS_CLIENT_AUTHORISED`` environment
|
2020-05-24 03:57:28 +02:00
|
|
|
variable set to True. Instructions on how to generate CA's are outside of the scope of
|
|
|
|
this readme, but you can find many helpful tutorials
|
2020-03-09 03:46:11 +01:00
|
|
|
[online](https://www.makethenmakeinstall.com/2014/05/ssl-client-authentication-step-by-step/).
|
2019-08-30 05:35:29 +02:00
|
|
|
|
|
|
|
### Static Files
|
2019-08-24 20:38:57 +02:00
|
|
|
|
2020-05-24 07:02:28 +02:00
|
|
|
Jetforce will serve static files in the ``/var/gemini/`` directory by default.
|
|
|
|
Files ending with ***.gmi** will be interpreted as the *text/gemini* type. If
|
2020-05-24 07:00:20 +02:00
|
|
|
a directory is requested, jetforce will look for a file named **index.gmi** in that
|
|
|
|
directory to return. Otherwise, a directory file listing will be automatically
|
|
|
|
generated.
|
2019-08-24 06:54:53 +02:00
|
|
|
|
2020-06-04 19:36:57 +02:00
|
|
|
### Virtual Hosting
|
|
|
|
|
|
|
|
For the sake of keeping the command line arguments straightforward and easy
|
|
|
|
to understand, configuring virtual hosting is not supported via the command
|
|
|
|
line. However, it is readily available using only a few lines of python and a
|
|
|
|
custom launch script. Check out [examples/vhost.py](examples/vhost.py) for more
|
|
|
|
information.
|
|
|
|
|
|
|
|
Jetforce does not (yet) support virtual hosting at the TLS-layer using SNI.
|
|
|
|
This means that you cannot return different server TLS certificates for
|
|
|
|
different domains. The suggested workaround is to use a single certificate with
|
|
|
|
multiple ``subjectAltName`` attributes. There is also an
|
|
|
|
[sni_callback()](https://github.com/michael-lazar/jetforce/blob/9ac80a986c6ed8a62951c857315ca04b6d127c32/jetforce/tls.py#L140)
|
|
|
|
hook in the server codebase that can be subclassed to implement custom TLS
|
|
|
|
behavior.
|
|
|
|
|
2020-05-29 20:14:52 +02:00
|
|
|
### CGI
|
2019-08-28 04:52:38 +02:00
|
|
|
|
2019-09-23 05:40:31 +02:00
|
|
|
Jetforce supports a simplified version of CGI scripting. It doesn't
|
|
|
|
exactly follow the [RFC 3875](https://tools.ietf.org/html/rfc3875)
|
2019-09-23 05:40:57 +02:00
|
|
|
specification for CGI, but it gets the job done for the purposes of Gemini.
|
2019-09-23 05:27:55 +02:00
|
|
|
|
|
|
|
Any executable file placed in the server's ``cgi-bin/`` directory will be
|
|
|
|
considered a CGI script. When a CGI script is requested by a gemini client,
|
2019-09-23 05:41:27 +02:00
|
|
|
the jetforce server will execute the script and pass along information about
|
2020-05-29 20:14:52 +02:00
|
|
|
the request using environment variables.
|
2020-05-24 07:40:09 +02:00
|
|
|
|
2019-09-23 05:44:56 +02:00
|
|
|
The CGI script must then write the gemini response to the *stdout* stream.
|
2019-09-23 05:44:33 +02:00
|
|
|
This includes the status code and meta string on the first line, and the
|
|
|
|
optional response body on subsequent lines. The bytes generated by the
|
2019-09-23 05:45:24 +02:00
|
|
|
CGI script will be forwarded *verbatim* to the gemini client, without any
|
2020-03-12 04:26:47 +01:00
|
|
|
additional modification by the server.
|
2019-08-24 07:05:45 +02:00
|
|
|
|
2020-05-29 20:14:52 +02:00
|
|
|
#### CGI Environment Variables
|
|
|
|
|
|
|
|
<dl>
|
|
|
|
<dt>GATEWAY_INTERFACE</dt>
|
|
|
|
<dd>
|
|
|
|
CGI version (for compatability with RFC 3785).<br>
|
|
|
|
<em>Example: "GCI/1.1"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>SERVER_PROTOCOL</dt>
|
|
|
|
<dd>
|
|
|
|
The server protocol.<br>
|
|
|
|
<em>Example: "GEMINI"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>SERVER_SOFTWARE</dt>
|
|
|
|
<dd>
|
|
|
|
The server name and version.<br>
|
|
|
|
<em>Example: "jetforce/0.0.7"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>GEMINI_URL</dt>
|
|
|
|
<dd>
|
|
|
|
The entire URL that was requested by the client.<br>
|
|
|
|
<em>Example: "gemini://mozz.us/cgi-bin/example.cgi/hello?world"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>SCRIPT_NAME</dt>
|
|
|
|
<dd>
|
|
|
|
The part of the URL's path that corresponds to the CGI script location.<br>
|
|
|
|
<em>Example: "/cgi-bin/example.cgi"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>PATH_INFO</dt>
|
|
|
|
<dd>
|
|
|
|
The remainder of the URL's path after the SCRIPT_NAME.<br>
|
|
|
|
<em>Example: "/hello"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>QUERY_STRING</dt>
|
|
|
|
<dd>
|
|
|
|
The query string portion of the request URL.<br>
|
|
|
|
<em>Example: "world"</em>
|
|
|
|
</dd>
|
|
|
|
|
2020-05-29 20:16:58 +02:00
|
|
|
<dt>SERVER_NAME / HOSTNAME</dt>
|
2020-05-29 20:14:52 +02:00
|
|
|
<dd>
|
|
|
|
The server hostname.<br>
|
|
|
|
<em>Example: "mozz.us"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>SERVER_PORT</dt>
|
|
|
|
<dd>
|
|
|
|
The server port number.<br>
|
|
|
|
<em>Example: "1965"</em>
|
|
|
|
</dd>
|
|
|
|
|
2020-05-29 20:16:58 +02:00
|
|
|
<dt>REMOTE_HOST / REMOTE_ADDR</dt>
|
2020-05-29 20:14:52 +02:00
|
|
|
<dd>
|
|
|
|
The client's IP address.<br>
|
|
|
|
<em>Example: "10.10.0.2"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CIPHER</dt>
|
|
|
|
<dd>
|
|
|
|
The negotiated TLS cipher<br>
|
|
|
|
<em>Example: "TLS_AES_256_GCM_SHA384"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_VERSION</dt>
|
|
|
|
<dd>
|
|
|
|
The negotiated TLS version.<br>
|
|
|
|
<em>Example: "TLSv1.3"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
|
|
#### CGI Environment Variables - Authenticated
|
|
|
|
|
|
|
|
Additional CGI variables will be included only when the client connection uses a TLS client certificate:
|
|
|
|
|
|
|
|
<dl>
|
|
|
|
|
|
|
|
<dt>AUTH_TYPE</dt>
|
|
|
|
<dd>
|
|
|
|
Authentication type (for compatability with RFC 3785).<br>
|
|
|
|
<em>Example: "CERTIFICATE"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>REMOTE_USER</dt>
|
|
|
|
<dd>
|
|
|
|
The certificate's subject CommonName attribute, if provided.<br>
|
|
|
|
<em>Example: "mozz123"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CLIENT_HASH</dt>
|
|
|
|
<dd>
|
|
|
|
A base64-encoded fingerprint that can be used to uniquely identify the certificate.<br>
|
|
|
|
<em>Example: "hjQftIC/4zPDQ1MNdav5nRQ39pM482xoTIgxtjyZOpY="</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CLIENT_NOT_BEFORE</dt>
|
|
|
|
<dd>
|
|
|
|
The certificate's activation date.<br>
|
|
|
|
<em>Example: "2020-04-05T04:18:22Z"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CLIENT_NOT_AFTER</dt>
|
|
|
|
<dd>
|
|
|
|
The certificate's activation date.<br>
|
|
|
|
<em>Example: "2021-04-05T04:18:22Z"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CLIENT_SERIAL_NUMBER</dt>
|
|
|
|
<dd>
|
|
|
|
The certificate's serial number.<br>
|
|
|
|
<em>Example: "73629018972631"</em>
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
<dt>TLS_CLIENT_VERIFIED</dt>
|
|
|
|
<dd>
|
2020-05-31 06:21:09 +02:00
|
|
|
Was the certificate deemed trusted by the server's CA certificate store.<br>
|
|
|
|
<em>0 (not authorised) / 1 (authorised)</em>
|
2020-05-29 20:14:52 +02:00
|
|
|
</dd>
|
|
|
|
|
|
|
|
</dl>
|
|
|
|
|
2019-09-23 06:44:44 +02:00
|
|
|
## Deployment
|
2019-09-23 06:44:02 +02:00
|
|
|
|
|
|
|
Jetforce is intended to be run behind a process manager that handles
|
|
|
|
*daemonizing* the script, redirecting output to system logs, etc. I prefer
|
|
|
|
to use [systemd](https://www.freedesktop.org/wiki/Software/systemd/) for this
|
|
|
|
because it's installed on my operating system and easy to set up.
|
|
|
|
|
|
|
|
Here's how I configure my server over at **gemini://mozz.us**:
|
|
|
|
|
|
|
|
```
|
|
|
|
# /etc/systemd/system/jetforce.service
|
|
|
|
[Unit]
|
|
|
|
Description=Jetforce Server
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
Type=simple
|
|
|
|
Restart=always
|
|
|
|
RestartSec=5
|
|
|
|
Environment="PYTHONUNBUFFERED=1"
|
|
|
|
ExecStart=/usr/local/bin/jetforce \
|
|
|
|
--host 0.0.0.0 \
|
|
|
|
--port 1965 \
|
|
|
|
--hostname mozz.us \
|
|
|
|
--dir /var/gemini \
|
|
|
|
--tls-certfile /etc/letsencrypt/live/mozz.us/fullchain.pem \
|
|
|
|
--tls-keyfile /etc/letsencrypt/live/mozz.us/privkey.pem \
|
|
|
|
--tls-cafile /etc/pki/tls/jetforce_client/ca.cer
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
WantedBy=default.target
|
|
|
|
```
|
|
|
|
|
|
|
|
- ``--host 0.0.0.0`` allows the server to accept external connections from any
|
|
|
|
IP address over IPv4.
|
|
|
|
- ``PYTHONUNBUFFERED=1`` disables buffering `stderr` and is sometimes necessary
|
|
|
|
for logging to work.
|
|
|
|
- ``--tls-certfile`` and ``--tls-keyfile`` point to my WWW server's Let's Encrypt
|
|
|
|
certificate chain.
|
|
|
|
- ``--tls-cafile`` points to a self-signed CA that I created in order to test
|
|
|
|
accepting client TLS connections.
|
|
|
|
|
|
|
|
With this service installed, I can start and stop the server using
|
|
|
|
|
|
|
|
```
|
|
|
|
systemctl start jetforce
|
|
|
|
systemctl stop jetforce
|
|
|
|
```
|
|
|
|
|
|
|
|
And I can view the server logs using
|
|
|
|
|
|
|
|
```
|
|
|
|
journalctl -u jetforce -f
|
|
|
|
```
|
|
|
|
|
2019-09-23 07:04:51 +02:00
|
|
|
*WARNING*
|
|
|
|
|
2020-05-24 07:00:20 +02:00
|
|
|
You are exposing a server to the internet. You (yes you!) are responsible for
|
|
|
|
securing your server and setting up appropriate access permissions. This likely means
|
|
|
|
*not* running jetforce as the root user. Security best practices are outside of the
|
|
|
|
scope of this document and largely depend on your individual threat model.
|
2019-09-23 07:04:51 +02:00
|
|
|
|
2020-05-31 23:29:35 +02:00
|
|
|
## Releases
|
|
|
|
|
|
|
|
To view the project's release history, see the [CHANGELOG](CHANGELOG.md) file.
|
2019-09-23 06:44:02 +02:00
|
|
|
|
2019-08-24 07:05:45 +02:00
|
|
|
## License
|
|
|
|
|
|
|
|
This project is licensed under the [Floodgap Free Software License](https://www.floodgap.com/software/ffsl/license.html).
|
|
|
|
|
|
|
|
> The Floodgap Free Software License (FFSL) has one overriding mandate: that software
|
|
|
|
> using it, or derivative works based on software that uses it, must be free. By free
|
|
|
|
> we mean simply "free as in beer" -- you may put your work into open or closed source
|
|
|
|
> packages as you see fit, whether or not you choose to release your changes or updates
|
|
|
|
> publicly, but you must not ask any fee for it.
|