diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-11-14 13:52:41 +0100 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-11-14 17:38:45 +0100 |
commit | c8bbfc9672b8c45800c47a9432904426809bee9b (patch) | |
tree | bb20c02c9d3896e1bb76839306c735ee85e46b98 /doc | |
parent | 825bfda4c13d74938feff48a5fb8663dc1ab4f67 (diff) | |
download | qvisqve-c8bbfc9672b8c45800c47a9432904426809bee9b.tar.gz |
Add: start of an arch doc
Diffstat (limited to 'doc')
-rw-r--r-- | doc/arch.mdwn | 183 | ||||
-rwxr-xr-x | doc/build.py | 95 | ||||
-rwxr-xr-x | doc/build.sh | 43 | ||||
-rw-r--r-- | doc/salami.css | 96 |
4 files changed, 417 insertions, 0 deletions
diff --git a/doc/arch.mdwn b/doc/arch.mdwn new file mode 100644 index 0000000..6b9eaea --- /dev/null +++ b/doc/arch.mdwn @@ -0,0 +1,183 @@ +--- +title: "Architecture of Salami, an authorization server" +author: QvarnLabs Ab +date: work-in-progress +... + +# Introduction + +**What.** Salami is an authorization server. At this stage, it is an +[OAuth2][] authorization server, but later on it will grow into a +provider for [OpenID Connect][]. Salami controls access to a web API: +the API client authenticates itself to Salami, gets an access token +from Salami, and gives the access token to the API server. This +de-couples the acts of authentication and authorization and resource +access, which simplifies the individual software components. + +[OAuth2]: https://en.wikipedia.org/wiki/OAuth +[OpenID Connect]: https://en.wikipedia.org/wiki/OpenID_Connect + +**Why.** Salami is not the first server of its kind. We wrote Salami +because we wanted something we liked: + +* is fully free, open source software +* has a simple, clear architecture and implementation +* has a chance of being accepted into the Debian distribution +* is simple and fast to install, and configure +* is simple, transparent to operate +* scles to many requests per second, many identities +* is flexible and easily extensible, as far as authentication methods + go +* is robust and reliable +* is stable + +**Web, not enterprise.** We operate in a "web environment, which is +different from a typical enterprise or large organisation environment. +We do not care about "single sign-on" across unrelated services, for +example. + +**Why not an alternative.** We've used [Gluu][], but for our uses it +is cumbersome, and not simple. + +[Gluu]: https://www.gluu.org/ + +**Usage driven development.** We develop Salami mainly based on the +needs of actual users, not to complete feature comparsion matrices. + +## Glossary + +* access token + +# Current state + +**Currently, Salami does not exist.** We have just started developing +it. The first development phase of Salami aims to produce an OAuth2 +authorization server that supports the client credential grant only. +This means only the API client authenticates itself to Salami, but not +the actual end-user. This is so that we can use Salami with the +[Qvarn][] server and have the Qvarn API tests pass. + +[Qvarn]: http://qvarn.org/ + +Further, the first development phase aims for a simplistic OAuth2 +server. For example, the list of API clients will be hardcoded in the +configuration file. This is acceptable to get development started, but +soon after that we will add features such as dynamically registering +clients. + +## Vision for the future + +In the longer term, we aim for Salami to be an OpenID provider using +the OpenID Connect protocol. This includes being able to have the +end-user authenticate and authorize use of resources. We will make it +simple and flexible to provide authentication methods (such as +username/password, client-side certificates, and U2F tokens). + +Eventually, we aim to have Salami certified as a compliant OpenID +Connect implementation. + +We also intend to make Salami be flexible, easy, and secure. + +# Known problems and things to solve later + +The main problem of Salami at this stage is that it doesn't exist. All +other problems can be derived from that. + +We don't have a good way of rotating the token signing keys. + +# Requirements + +For the first development phase of Salami, our acceptance criteria +are: + +* setting up a Salami instance is simple: the software should be + provided as a Debian package installable on Debian 9 (stretch), and + a corresponding Ansible playbook that configures the instance + +* configuring a Qvarn instance to use the Salami instance is simple: + the Ansible playbook for Qvarn should be updated to work with Salami + instead of Gluu + +* the Qvarn API tests pass + +Once we have that working, we will add more requirements. + +## Non-requirements + +At this stage, we do not care about speed, portability, or other such +qualities. We care about security enough that we want to avoid any +glaringly obvious security issues, but, for example, we don't care +about storing API client secrets in an encrypted fashion. + +# Architecture overview + +Salami provides an HTTP API interface for authentication. The only +relevant endpoint is `/token` and to use it, the client must +authenticate itself using its "client id" and "client secret" using +HTTP Basic Authentication. A successful response will have the access +token in its JSON body. + +A request: + + POST /token HTTP/1.1 + Host: salami.example.com + Authorization: Basic c2FsYW1pOnBhc3N3b3Jk + Content-Type: application/x-www-form-urlencoded + + grant_type=client_credentials?scope=version + +A successful response: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "access_token": "CAFEF00D", + "token_type": "bearer", + "expires_in": 3600, + } + +The client should extract the access token (`CAFEF00D`) and add it to +any requests it makes to the resource server: + + Authorization: Bearer CAFEF00D + +The client does not need to understand the token, it merely copies it +into requests it makes. + +## Components + +Salami consists of several components + +@startuml +title Salami components +component [API client] as client +node "Salami" { + component [Haproxy] as haproxy + component [Salami\nbackend] as backend + component [Salami\nconfiguration] as config +} + +client -> haproxy : 2. https +haproxy -> backend : 3. http +backend <- config : 1. read at startup +haproxy <- backend : 4. access token +client <- haproxy : 5. access token +@enduml + +**haproxy** is a load balancer. For Salami we use it to provide TLS +for communication with the client. + +The **backend** implements the actual Salami HTTP endpoints and +creates and returns access tokens to the client. + +The **configuration** lists the API clients (the id, the secret, and +any scopes the client is allowed to have), as well as the RSA keys +used to sign the access token. The API provider (Qvarn) will be +configured to know the public RSA key so that it can verify that an +access token has been created by Salami. + +The client and haproxy use TLS. haproxy and the backend use plain +HTTP, but they will be deployed in an environment where the plain text +communication cannot be overheard, such as via the `localhost` +interface (haproxy and backend would run on the same host). diff --git a/doc/build.py b/doc/build.py new file mode 100755 index 0000000..5818cb7 --- /dev/null +++ b/doc/build.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# +# Copyright 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + + +import os +import sys +import tempfile + +import cliapp + + +def parse_markdown(f, tmpdir): + lines = f.readlines() + result = [] + uml_filenames = [] + while lines: + prefix, uml, lines = extract_uml(lines) + result.extend(prefix) + if uml: + filename = create_uml_file(tmpdir, uml) + uml_filenames.append(filename) + result.extend(include(uml, filename)) + return result, uml_filenames + + +def extract_uml(lines): + start = find_line(lines, '@startuml\n') + if start is None: + return lines, None, [] + + end = find_line(lines, '@enduml\n') + if end is None: + return lines, None, [] + + return lines[:start], lines[start:end+1], lines[end+1:] + + +def find_line(lines, pattern): + for i, line in enumerate(lines): + if line.startswith(pattern): + return i + return None + + +def create_uml_file(tmpdir, lines): + text = ''.join(lines) + fd, filename = tempfile.mkstemp(dir=tmpdir) + os.write(fd, text.encode()) + os.close(fd) + return filename + + +def include(uml, filename): + title = get_title(uml) + return [ + '![{}]({}.png)\\ \n'.format(title, filename), + '\n', + ] + + +def get_title(uml): + i = find_line(uml, 'title ') + if i is not None: + return uml[i].split(' ', 1)[1] + + +def plantuml(filename): + cliapp.runcmd(['plantuml', '-tpng', '-output', '.', filename]) + + +def main(): + tmpdir = sys.argv[1] + lines, filenames = parse_markdown(sys.stdin, tmpdir) + sys.stdout.write(''.join(lines)) + for filename in filenames: + plantuml(filename) + + +if __name__ == '__main__': + main() diff --git a/doc/build.sh b/doc/build.sh new file mode 100755 index 0000000..418fa26 --- /dev/null +++ b/doc/build.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Copyright 2017 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +set -eu + +export DISPLAY= + +output="$(dirname "$1")/$(basename "$1" .mdwn)" + +tmp="$(mktemp -d)" + +clean() +{ + rm -rf "$tmp" +} + +trap clean EXIT + +cat -- "$@" | ./build.py "$tmp" > "$tmp/foo.mdwn" +pandoc --toc \ + -V documentclass:report \ + -Vgeometry:a4paper \ + -Vfontsize:12pt \ + -Vmainfont:FreeSerif \ + -Vsansfont:FreeSans \ + -Vmonofont:FreeMonoBold \ + -Vgeometry:"top=2cm, bottom=2.5cm, left=2cm, right=1cm" \ + --chapters \ + -o "$output.pdf" "$tmp/foo.mdwn" +pandoc --toc -o "$output.html" --number-sections --self-contained -H salami.css "$tmp/foo.mdwn" diff --git a/doc/salami.css b/doc/salami.css new file mode 100644 index 0000000..86f806e --- /dev/null +++ b/doc/salami.css @@ -0,0 +1,96 @@ +<!-- +Copyright 2017 Lars Wirzenius + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +--> + +<style> +html { + background: white; + font-family: serif; + margin-left: 3em; + margin-right: 2em; + margin-top: 2em; +} + +form#searchform { + font-family: monospace; + text-align: right; +} + +div.actions { + font-family: monospace; + text-align: right; +} + +div.actions ul, div.actions li { + display: inline; +} + +div.pageheader { + font-family: monospace; + margin-bottom: 2em; +} + +div.pageheader span.title { + display: block; + font-size: 200%; + font-weight: bold; + font-family: sans-serif; + margin-top: 0.5em; +} + +div#pagebody { +} + +div.pagefooter { + font-family: monospace; + margin-top: 3em; +} + +div#pagebody { +} + +div#TOC ul { + list-style: none; +} + +h1, h2, h3, h4, h5, h6 { + font-family: sans-serif; + font-weight: bold; + margin-top: 2em; +} + +h1 { + font-size: 150%; +} + +h2 { + font-size: 120%; +} + +h3 { + font-size: 100%; +} + +ul li, ol li { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +pre { + margin-left: 4em; +} + +</style> |