jetforce/README.md

241 lines
10 KiB
Markdown
Raw Normal View History

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
![Rocket Launch](logo.jpg)
2019-08-09 03:45:26 +02:00
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
```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
```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
## Usage
Use the ``--help`` flag to view command-line options:
```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:
-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:
--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)
--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)
--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
"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-19 07:43:18 +02:00
Jetforce also supports TLS client certificates (both self-signed and CA verified).
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-24 03:57:28 +02:00
flags. Connections validated by the CA will have the ``TLS_CLIENT_VERIFIED`` environment
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
### CGI Scripts
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
the request using environment variables:
2019-09-23 05:27:55 +02:00
2020-05-24 07:24:35 +02:00
| Variable Name | Description | Example |
| --- | --- | --- |
2020-05-24 07:44:26 +02:00
| GATEWAY_INTERFACE | CGI version (for compatability). | ``GCI/1.1`` |
2020-05-24 07:27:30 +02:00
| SERVER_PROTOCOL | The server protocol. | ``GEMINI`` |
| SERVER_SOFTWARE | The server version string. | ``jetforce/0.0.7`` |
2020-05-24 07:44:26 +02:00
| GEMINI_URL | Raw URL string from the request. | ``gemini://mozz.us/cgi-bin/example.cgi/hello?world``
2020-05-24 07:27:30 +02:00
| SCRIPT_NAME | The part of the URL's path that corresponds to the CGI script location. | ``/cgi-bin/example.cgi`` |
| PATH_INFO | The remainder of the URL's path after the CGI script location. | ``/hello`` |
| QUERY_STRING | The query string portion of the request URL. | ``world`` |
2020-05-24 07:44:26 +02:00
| HOSTNAME | Server hostname. | ``mozz.us`` |
| SERVER_NAME | Server hostname (alias for HOSTNAME). | ``mozz.us`` |
| REMOTE_ADDR | Client IP address. | ``10.10.0.2`` |
| REMOTE_HOST | Client IP address (alias for REMOTE_ADDR). | ``10.10.0.2`` |
| SERVER_PORT | Server port number. | ``1965`` |
| TLS_CIPHER | TLS cipher that was negotiated. | ``TLS_AES_256_GCM_SHA384``|
| TLS_VERSION | TLS version that was negotiated. | ``TLSv1.3`` |
2020-05-24 07:40:09 +02:00
Additional CGI variables will also be included when the connection uses a TLS client certificate:
| Variable Name | Description | Example |
| --- | --- | --- |
2020-05-24 07:44:26 +02:00
| AUTH_TYPE | Authentication type (for compatability). | ``CERTIFICATE`` |
2020-05-24 07:40:09 +02:00
| REMOTE_USER | The subject CommonName attribute, if provided. | ``michael123`` |
2020-05-24 07:44:26 +02:00
| TLS_CLIENT_HASH | A base64-encoded certificate fingerprint. | ``hjQftIC/4zPDQ1MNdav5nRQ39pM482xoTIgxtjyZOpY=`` |
| TLS_CLIENT_NOT_BEFORE | Certificate activation date. | ``2020-04-05T04:18:22Z`` |
| TLS_CLIENT_NOT_AFTER | Certificate expiration date. | ``2021-04-05T04:18:22Z`` |
| TLS_CLIENT_SERIAL_NUMBER | Certificate serial number. | ``73629018972631`` |
2020-05-24 07:45:01 +02:00
| TLS_CLIENT_VERIFIED | Was the certificate verified by OpenSSL? | ``0`` (verified) / ``1`` (not verified) |
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
additional modification by the server.
2019-08-24 07:05:45 +02:00
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
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.