From: Alberto Leiva Popper Date: Mon, 25 Mar 2019 07:55:44 +0000 (-0600) Subject: Add documentation draft X-Git-Tag: v0.0.2~57 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=40601186c5117630a730db60f328906d7561fe08;p=thirdparty%2FFORT-validator.git Add documentation draft --- diff --git a/.gitignore b/.gitignore index cf21d8aa..b9aa85d0 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,7 @@ test-driver # Temporal files *~ tmp +docs/_site # Unwanted manure shat by imbecile OSs .DS_Store* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..8b090981 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Fort's website + +This directory contains the source files of Fort's site and documentation. + +On account on not being finished, Github does not automatically compile+serve this yet. + +You can compile the documentation yourself by running [Jekyll](http://jekyllrb.com/) right here. + +```bash +$ jekyll build +``` + +Have a look at your generated files in the new `_site/` directory. (Start at `index.html`.) diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000..fb06919d --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,8 @@ +markdown: kramdown +baseurl: /fort +defaults: + - + scope: + path: "" # an empty string here means all files in the project + values: + layout: "default" diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 00000000..8d7616ba --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,14 @@ + + + + + {{ page.title }} + + + + + +{{ content }} + + + diff --git a/docs/css/screen.css b/docs/css/screen.css new file mode 100644 index 00000000..431d3725 --- /dev/null +++ b/docs/css/screen.css @@ -0,0 +1,22 @@ +code { + background-color: #f8f8f8; +} +pre code { + display: block; + overflow: auto; + padding: 1em; +} +pre code.terminal { + color: lightgray; + background-color: black; +} + +.language-bash > .c { + color: green; +} +.language-bash > .nt { + color: purple; +} +.language-bash > .s2 { + color: blue; +} diff --git a/docs/doc/index.md b/docs/doc/index.md new file mode 100644 index 00000000..0be75e19 --- /dev/null +++ b/docs/doc/index.md @@ -0,0 +1,10 @@ +--- +--- + +# Documentation Index + +1. [Introduction to RPKI](intro-rpki.html) +2. [Introduction to Fort](intro-fort.html) +3. [Compilation and Installation](installation.html) +4. [Validator usage](validator.html) +5. [RTR Server usage](rtr-server.html) diff --git a/docs/doc/installation.md b/docs/doc/installation.md new file mode 100644 index 00000000..0524135c --- /dev/null +++ b/docs/doc/installation.md @@ -0,0 +1,66 @@ +--- +--- + +# Compilation and Installation + +## Index + +1. [Install dependencies](#install-dependencies) + 1. [libcrypto](#libcrypto) + 2. [tomlc99](#tomlc99) + 3. [rsync](#rsync) +2. [Install Fort](#install-fort) + 1. [libcmscodec](#libcmscodec) + 2. [Validator](#validator) + 3. [RTR Server](#rtr-server) + +## Install dependencies + +### libcrypto + +Either [LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/) + +### tomlc99 + +[tomlc99](https://github.com/cktan/tomlc99) + +### libcmscodec + +{% highlight bash %} +git clone https://github.com/ydahhrk/libcmscodec.git +cd libcmscodec +./autogen.sh +./configure +make +sudo make install +{% endhighlight %} + +### rsync + +[rsync](http://rsync.samba.org/) + +## Install Fort + +There are no packages just yet. + +### Validator + +{% highlight bash %} +git clone https://github.com/ydahhrk/rpki-validator.git +cd rpki-validator +./autogen.sh +./configure +make +sudo make install +{% endhighlight %} + +### RTR-Server + +{% highlight bash %} +git clone https://github.com/ydahhrk/rtr-server.git +cd rtr-server +./autogen.sh +./configure +make +sudo make install +{% endhighlight %} diff --git a/docs/doc/intro-fort.md b/docs/doc/intro-fort.md new file mode 100644 index 00000000..bbc4e1c1 --- /dev/null +++ b/docs/doc/intro-fort.md @@ -0,0 +1,61 @@ +--- +--- + +# Introduction to FORT + +## Design + +FORT is an RPKI Relying Party. It is a service that performs the validation of the entire RPKI repository, and which prepares the ROAs for easy access by your routers. + +![../img/design.svg](../img/design.svg) + +It is currently made out of two separate binaries. One of them is a command line application that can be used to perform manual validations, and a server that takes the output of the former and communicates it to the routers. + +So the idea is to routinely run the command line application in a cron job, and serve its output through the server. + +## Standards Compliance + +Further information can be found in the subsections below. + +| RFC | Implemented | +|----------------------------------------------------------------------------|-------------| +| [3779](https://tools.ietf.org/html/rfc3779) (IP & AS Extensions) | 100% | +| [6350](https://tools.ietf.org/html/rfc6350) (vCard) | 0% | +| [6482](https://tools.ietf.org/html/rfc6482) (ROA) | 100% | +| [6486](https://tools.ietf.org/html/rfc6486) (Manifests) | 75% | +| [6487](https://tools.ietf.org/html/rfc6487) (Resource Certificates & CRLs) | 100% | +| [6488](https://tools.ietf.org/html/rfc6488) (Signed Objects) | 90% | +| [6493](https://tools.ietf.org/html/rfc6493) (Ghostbusters) | 100% | +| [7318](https://tools.ietf.org/html/rfc7318) (Policy Qualifiers) | 100% | +| [7730](https://tools.ietf.org/html/rfc7730) (TALs) | 100% | +| [7935](https://tools.ietf.org/html/rfc7935) (RPKI algorithms) | 100% | +| [8182](https://tools.ietf.org/html/rfc8182) (RRDP) | 0% | +| [8209](https://tools.ietf.org/html/rfc8209) (BGPSec Certificates) | 0% | +| [8360](https://tools.ietf.org/html/rfc8360) (Validation Reconsidered) | 100% | + +### RFC 6350 (vCard) + +The vCard format is only used by Ghostbusters records. 6350 defines the basic vCard format, while 6493 defines additional requirements for Ghostbusters-specific vCard. + +The specific validations have been implemented, while the basic ones have not. + +### RFC 6486 (Manifests) + +This RFC states a bunch of rules that allow some level of tolerance to missing, invalid or stale manifests. Here's an example: + +> signed objects (...) issued by the entity that has published the stale manifest (...) SHOULD be viewed as somewhat suspect, but MAY be used by the RP as per local policy. + +These constitute the approximate missing 25%. + +### RFC 6488 (Signed Objects) + +6488 mandates that all signed objects must be DER-encoded. Fort's current parser cannot tell the difference between BER and DER. + +### RFC 8182 (RRDP) + +RRDP is a protocol intended to replace RSYNC in the RPKI. Fort only implements RSYNC, currently. + +## TO-DO + +- Reach full 100% RFC compliance. +- Maybe a few optimizations, marked as `TODO` in the code. diff --git a/docs/doc/intro-rpki.md b/docs/doc/intro-rpki.md new file mode 100644 index 00000000..db72a68f --- /dev/null +++ b/docs/doc/intro-rpki.md @@ -0,0 +1,35 @@ +--- +title: Introduction to RPKI +--- + +# {{ page.title }} + +## Problem Statement + +Routing, having been conceived near the inception of networking, was hardly designed with security as a primary concern. As a result, routing protocols (in their vanilla forms) are vulnerable to [several attacks](https://tools.ietf.org/html/rfc4593#section-4). + +## Solution + +The RPKI (Resource Public Key Infrastructure) is a PKI (Public Key Infrastructure) that deals with Internet Resources. + +> ### PKI +> +> TODO + +> ### Internet Resources +> +> In this context, "resource" refers to IP Addresses and AS numbers. + +Basically, the idea is that one should be able to verify the origin of a route by following a chain of criptographically-signed certificates rooted at one of the [RIRs](https://en.wikipedia.org/wiki/Regional_Internet_registry): + +![../img/chain.svg](../img/chain.svg) + +The end result is a _Route Origin Attestation_ (ROA), a digitally signed object that provides a means of verifying that an IP address block holder has authorized an Autonomous System (AS) to originate routes to its address block or one of its children's. + +So the whole infrastructure functions like a tree-shaped trust network (one for each RIR) in which authorities (_Certificate Authority_--CA) attest to their resource suballocations: + +![../img/tree.svg](../img/tree.svg) + +In the RPKI, all of these files are required to be publicly-available, so anyone can verify them. + +This is, however, too much work for a router, so the validation work is deferred to a trusted _Relying Party_ (RP). That's where [FORT](intro-fort.html) comes in. diff --git a/docs/doc/rtr-server.md b/docs/doc/rtr-server.md new file mode 100644 index 00000000..39960599 --- /dev/null +++ b/docs/doc/rtr-server.md @@ -0,0 +1,5 @@ +--- +--- + +# + diff --git a/docs/doc/validator.md b/docs/doc/validator.md new file mode 100644 index 00000000..b4901638 --- /dev/null +++ b/docs/doc/validator.md @@ -0,0 +1,352 @@ +--- +command: rpki_validator +--- + +# Validator Usage + +## Index + +1. [Syntax](#syntax) +2. [Arguments](#arguments) + 1. [`--help`](#--help) + 2. [`--usage`](#--usage) + 3. [`--version`](#--version) + 4. [`--tal`](#--tal) + 5. [`--local-repository`](#--local-repository) + 6. [`--roa-output-file`](#--roa-output-file) + 7. [`--sync-strategy`](#--sync-strategy) + 1. [`off`](#off) + 2. [`strict`](#strict) + 3. [`root`](#root) + 4. [`root-except-ta`](#root-except-ta) + 8. [`--maximum-certificate-depth`](#--maximum-certificate-depth) + 9. [`--shuffle-uris`](#--shuffle-uris) + 10. [`--color-output`](#--color-output) + 11. [`--output-file-name-format`](#--output-file-name-format) + 12. [`--configuration-file`](#--configuration-file) + 13. [`rsync.program`](#rsyncprogram) + 14. [`rsync.arguments-recursive`](#rsyncarguments-recursive) + 15. [`rsync.arguments-flat`](#rsyncarguments-flat) + +## Syntax + +``` +{{ page.command }} + [--help] + [--usage] + [--version] + [--configuration-file=] + [--local-repository=] + [--sync-strategy=off|strict|root|root-except-ta] + [--maximum-certificate-depth=] + [--tal=] + [--shuffle-uris] + [--color-output] + [--output-file-name-format=global-url|local-path|file-name] + [--roa-output-file=] +``` + +If an argument is declared more than once, the last one takes precedence: + +{% highlight bash %} +$ {{ page.command }} --tal="foo" # tal is "foo" +$ {{ page.command }} --tal="foo" --tal="bar" # tal is "bar" +$ {{ page.command }} --tal="foo" --tal="bar" --tal="qux" # tal is "qux" +{% endhighlight %} + + +## Arguments + +### `--help` + +- Type: None +- Availability: `argv` only. + +Prints medium-sized usage message. + +{% highlight bash %} +$ {{ page.command }} --help +Usage: {{ page.command }} + [--help] + (Give this help list) + [--usage] + (Give a short usage message) + [--version] + (Print program version) + ... + [--output-file-name-format=global-url|local-path|file-name] + (File name variant to print during debug/error messages) + [--roa-output-file=] + (File where the valid ROAs will be dumped.) +{% endhighlight %} + +The slightly larger usage message is `man {{ page.command }}` and the large usage message is this documentation. + +### `--usage` + +- Type: None +- Availability: `argv` only. + +Prints small-sized help message. + +{% highlight bash %} +$ {{ page.command }} --usage +Usage: {{ page.command }} + [--help] + [--usage] + [--version] + ... + [--output-file-name-format=global-url|local-path|file-name] + [--roa-output-file=] +{% endhighlight %} + +### `--version` + +- Type: None +- Availability: `argv` only. + +Prints small-sized usage message. + +{% highlight bash %} +$ {{ page.command }} --version +0.0.1 +{% endhighlight %} + +### `--tal` + +- Type: String (Path to file) +- Availability: `argv` and TOML + +Path to the _Trust Anchor Locator_ (TAL). + +The TAL is a file that points to the _Trust Anchor_ (TA). (The TA is the self-signed certificate that serves as the root of the tree you want validated.) + +The reason why you provide a TAL instead of a TA is to allow the TA to be updated without needing to redistribute it. + +Whichever registry serves as root of the tree you want to validate is responsible for providing you with its TAL. FORT currently ships with the TALs of most of the five RIRs. + +> TODO state where they are and which is the missing one. + +the TAL file format has been standardized in [RFC 7730](https://tools.ietf.org/html/rfc7730). It is a text file that contains a list of URLs (which serve as alternate access methods for the TA), followed by a blank line, followed by the Base64-encoded public key of the TA. + +Just for completeness sake, here's an example on what a typical TAL looks like: + +``` +rsync://rpki.example.com/repository/root-ca.cer + +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsqS+PDB1kArJlBTHeYCu +4anCWv8DzE8fHHexlGBm4TQBWC0IhNVbpUFg7SOp/7VddcGWyPZQRfdpQi4fdaGu +d6JJcGRECibaoc0Gs+d2mNyFJ1XXNppLMr5WH3iaL86r00jAnGJiCiNWzz7Rwyvy +UH0Z4lO12h+z0Zau7ekJ2Oz9to+VcWjHzV4y6gcK1MTlM6fMhKOzQxEA3TeDFgXo +SMiU+kLHI3dJhv4nJpjc0F+8+6hokIbF0p79yaCgyk0IGz7W3oSPa13KLN6mIPs6 +4/UUJU5DDQvdq5T9FRF0I1mdtLToLSBnDCkTAAC6486UYV1j1Yzv1+DWJHSmiLna +LQIDAQAB +``` + +### `--local-repository` + +- Type: String (Path to directory) +- Availability: `argv` and TOML +- Default: `/tmp/fort/repository` + +> TODO I just came up with the default value. Commit it. + +Path to the directory where FORT will store a local cache of the repository. + +RPKI repositories are typically accessed by way of [rsync](https://en.wikipedia.org/wiki/Rsync). During the validation cycle, FORT will literally invoke an `rsync` command (see `rsync.program` and `rsync.arguments`), which will download the files into `--local-repository`, and validate the result. + +Because rsync uses delta encoding, keeping this cache around significantly speeds up subsequent validation cycles. + +### `--roa-output-file` + +- Type: String (Path to file) +- Availability: `argv` and TOML +- Default: `NULL` + +Path to a file where FORT will dump successfully validated ROAs (in CSV format). If `NULL`, the ROAs will be printed in standard error. + +This file is meant to be consumed by the beta version of the RTR Server. (In subsequent releases, this will no longer be required.) + +### `--sync-strategy` + +- Type: Enumeration (`off`, `strict`, `root`, `root-except-ta`) +- Availability: `argv` and TOML +- Default: `root` + +rsync synchronization strategy. Commands the way rsync URLs are approached during downloads. + +#### `off` + +Skips all rsyncs. (Validate the existing cache repository pointed by `--local-repository`.) + +#### `strict` + +rsyncs every repository publication point separately. Only skips publication points that have already been downloaded during the current validation cycle. (Assuming each synchronization is recursive.) + +For example, suppose the validator gets certificates whose caRepository access methods (in their Subject Information Access extensions) point to the following publication points: + +1. `rsync://rpki.example.com/foo/bar/` +2. `rsync://rpki.example.com/foo/qux/` +3. `rsync://rpki.example.com/foo/bar/` +4. `rsync://rpki.example.com/foo/corge/grault/` +5. `rsync://rpki.example.com/foo/corge/` +6. `rsync://rpki.example.com/foo/corge/waldo/` + +A validator following the `strict` strategy would download `bar`, download `qux`, skip `bar`, download `corge/grault`, download `corge` and skip `corge/waldo`. + +Though this strategy is the only "strictly" correct one, it is also extremely slow. Its usage is _not_ recommended, unless your repository contains lots of spam files, awkward permissions or can't be found in a repository rooted in a URL that follows the pattern `rsync://.+/.+/`. + +#### `root` + +For each publication point found, guess the root of its repository and rsync that instead. Then skip +any subsequent children of said root. + +(To guess the root of a repository, the validator counts four slashes, and prunes the rest of the URL.) + +Reusing the caRepository URLs from the `strict` strategy (above) as example, a validator following the `root` strategy would download `rsync://rpki.example.com/foo`, and then skip everything else. + +Assuming that the repository is specifically structured to be found within as few roots as possible, and they contain minimal RPKI-unrelated noise files, this is the fastest synchronization strategy. At time of writing, this is true for all the current official repositories. + +#### `root-except-ta` + +Synchronizes the root certificate (the one pointed by the TAL) in `strict` mode, and once it's validated, synchronizes the rest of the repository in `root` mode. + +Useful if you want `root`, but the root certificate is separated from the rest of the repository. Also useful if you don't want the validator to download the entire repository without first confirming the integrity and legitimacy of the root certificate. + +### `--maximum-certificate-depth` + +### `--shuffle-uris` + +- Availability: `argv` and TOML + +If enabled, FORT will access TAL URLs in random order. This is meant for load balancing. If disabled, FORT will access TAL URLs in sequential order. + +(Regardless of this flag, FORT will stop iterating through the URLs as soon as it finds one that yields a successful traversal.) + +Of course, this is only relevant if the TAL lists more than one URL. + +### `--color-output` + +- Availability: `argv` and TOML + +If enabled, the logging output will contain ANSI color codes. Meant for human consumption. + +
$ {{ page.command }} --color-output (...)
+DBG: Manifest '62gPOPXWxxu0sQa4vQZYUBLaMbY.mft' {
+INF: rpkiManifest registered. Its nid is 1061.
+WRN: H2jRmyC2M.mft: The signature algorithm has parameters.
+ERR: H2jRmyC2M.mft: Certificate validation failed: certificate has expired
+CRT: Programming error: Array size is 1 but array is NULL.
+
+ +### `--output-file-name-format` + +- Type: Enumeration (`global-url`, `local-path`, `file-name`) +- Availability: `argv` and TOML +- Default: `global-url` + +Decides which version of file names should be printed during most debug/error messages. + +Suppose a certificate was downloaded from `rsync://rpki.example.com/foo/bar/baz.cer` into the local cache `repository/`: + +- `global-url`: Will print the certificate's name as `rsync://rpki.example.com/foo/bar/baz.cer`. +- `local-path`: Will print the certificate's name as `repository/rpki.example.com/foo/bar/baz.cer`. +- `file-name`: Will print the certificate's name as `baz.cer`. + +{% highlight bash %} +$ {{ page.command }} --output-file-name-format global-url --local-repository tmp/repository/ (...) +ERR: rsync://rpki.afrinic.net/repository/arin/uHxadfPZV0E6uZhkaUbUVB1RFFU.mft: Certificate validation failed: certificate has expired +$ {{ page.command }} --output-file-name-format local-path --local-repository tmp/repository/ (...) +ERR: tmp/repository/rpki.afrinic.net/repository/arin/uHxadfPZV0E6uZhkaUbUVB1RFFU.mft: Certificate validation failed: certificate has expired +$ {{ page.command }} --output-file-name-format file-name --local-repository tmp/repository/ (...) +ERR: uHxadfPZV0E6uZhkaUbUVB1RFFU.mft: Certificate validation failed: certificate has expired +{% endhighlight %} + +### `--configuration-file` + +- Type: String (Path to file) +- Availability: `argv` + +Path to a TOML file from which additional configuration will be read. + +The configuration options are mostly the same as the ones from the `argv` interface. Here's a full configuration file example: + +
[root]
+local-repository = "/tmp/fort/repository"
+sync-strategy = "root"
+maximum-certificate-depth = 32
+
+[tal]
+tal = "/tmp/fort/example.tal"
+shuffle-uris = true
+
+[rsync]
+program = "rsync"
+arguments-recursive = [ "--recursive", "--times", "$REMOTE", "$LOCAL" ]
+arguments-flat = [ "--times", "$REMOTE", "$LOCAL" ]
+
+[output]
+color-output = true
+output-file-name-format = "file-name"
+roa-output-file = "/tmp/fort/roas.csv"
+
+ +The file acts as a collection of equivalent `argv` arguments; precedence is not modified: + +{% highlight bash %} +$ cat cfg.toml +[tal] +tal = "bar" +$ {{ page.command }} --tal="foo" # tal is "foo" +$ {{ page.command }} --tal="foo" --configuration-file="cfg.toml" # tal is "bar" +$ {{ page.command }} --tal="foo" --configuration-file="cfg.toml" --tal="qux" # tal is "qux" + +$ cat a.toml +[root] +local-repository = "a" +sync-strategy = "root" +maximum-certificate-depth = 1 + +$ cat b.toml +[root] +sync-strategy = "strict" +maximum-certificate-depth = 2 + +$ cat c.toml +[root] +maximum-certificate-depth = 4 + +$ {{ page.command }} \ + --configuration-file="a.toml" \ + --configuration-file="b.toml" \ + --configuration-file="c.toml" +$ # local-repository is "a", sync-strategy is "strict" and maximum-certificate-depth is 4 +{% endhighlight %} + +### rsync.program + +- Type: String +- Availability: TOML +- Default: `"rsync"` + +Name of the program needed to invoke an rsync file transfer. + +### rsync.arguments-recursive + +- Type: String array +- Availability: TOML +- Default: `[ "--recursive", "--delete", "--times", "--contimeout=20", "$REMOTE", "$LOCAL" ]` + +Arguments needed by [`rsync.program`](#rsyncprogram) to perform a recursive rsync. + +FORT will replace `"$REMOTE"` with the remote URL it needs to download, and `"$LOCAL"` with the target local directory where the file is supposed to be dropped. + +### rsync.arguments-flat + +- Type: String array +- Availability: TOML +- Default: `[ "--times", "--contimeout=20", "$REMOTE", "$LOCAL" ]` + +Arguments needed by [`rsync.program`](#rsyncprogram) to perform a single-file rsync. + +FORT will replace `"$REMOTE"` with the remote URL it needs to download, and `"$LOCAL"` with the target local directory where the file is supposed to be dropped. diff --git a/docs/img/chain.svg b/docs/img/chain.svg new file mode 100644 index 00000000..6bc7589c --- /dev/null +++ b/docs/img/chain.svg @@ -0,0 +1,1026 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + me + Certificate + + + + + + + ROA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lacnic + lacnic + + potato + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tomato + + + + + + + carrot + HelloMy name is LACNIC.I own- IPv4 0.0.0.0/0- IPv6 ::/0- AS 0-4294967295Sincerely, + + HelloMy name is Potato.I own- IPv4 192.0.2.0/24- IPv6 2001:db8::/96- AS 60000-70000Here's LACNIC'ssignature, proving it: + HelloMy name is Tomato.I own- IPv4 192.0.2.0/26- AS 64000-64100Here's Potato'ssignature, proving it: + HelloMy name is Carrot.I own- IPv4 192.0.2.0/30- AS 64050Here's Tomato'ssignature, proving it: + 64050can originateroutes to192.0.2.0/30 + + diff --git a/docs/img/design.svg b/docs/img/design.svg new file mode 100644 index 00000000..422393b3 --- /dev/null +++ b/docs/img/design.svg @@ -0,0 +1,731 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Validator + RTRServer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/tree.svg b/docs/img/tree.svg new file mode 100644 index 00000000..6deb9602 --- /dev/null +++ b/docs/img/tree.svg @@ -0,0 +1,757 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..7ed79c77 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ +--- +--- + +# Home + +## Introduction + +The documentation can be found [here](doc/index.html). + +## Status + +The first release of Fort is planned to be released later this month.