Commit 9e80daa3 authored by ale's avatar ale

Add more detailed description of authentication workflows

The step-by-step descriptions include graphs.
parent 94b3eba3
Pipeline #2647 passed with stage
in 4 minutes and 1 second
DOTS = $(wildcard *.dot)
SVGS = $(DOTS:%.dot=%.svg)
all: $(SVGS)
%.svg: %.dot
dot -Tsvg -o$@ $<
digraph simple_no2fa {
{
rank=min
user [color=gray]
}
{
DB [shape=cylinder]
}
user -> dovecot [label="password/ASP"]
dovecot -> "auth-server" [label=PAM]
"auth-server" -> DB
dovecot -> keylookupd
keylookupd -> DB
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: simple_no2fa Pages: 1 -->
<svg width="230pt" height="291pt"
viewBox="0.00 0.00 230.04 291.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 287)">
<title>simple_no2fa</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-287 226.041,-287 226.041,4 -4,4"/>
<!-- user -->
<g id="node1" class="node"><title>user</title>
<ellipse fill="none" stroke="gray" cx="109.046" cy="-265" rx="27" ry="18"/>
<text text-anchor="middle" x="109.046" y="-261.3" font-family="Times,serif" font-size="14.00">user</text>
</g>
<!-- dovecot -->
<g id="node3" class="node"><title>dovecot</title>
<ellipse fill="none" stroke="black" cx="109.046" cy="-178" rx="38.1938" ry="18"/>
<text text-anchor="middle" x="109.046" y="-174.3" font-family="Times,serif" font-size="14.00">dovecot</text>
</g>
<!-- user&#45;&gt;dovecot -->
<g id="edge1" class="edge"><title>user&#45;&gt;dovecot</title>
<path fill="none" stroke="black" d="M109.046,-246.799C109.046,-235.163 109.046,-219.548 109.046,-206.237"/>
<polygon fill="black" stroke="black" points="112.546,-206.175 109.046,-196.175 105.546,-206.175 112.546,-206.175"/>
<text text-anchor="middle" x="149.046" y="-217.8" font-family="Times,serif" font-size="14.00">password/ASP</text>
</g>
<!-- DB -->
<g id="node2" class="node"><title>DB</title>
<polygon fill="none" stroke="black" points="148.046,-36 94.0456,-36 94.0456,-0 148.046,-0 148.046,-36"/>
<text text-anchor="middle" x="121.046" y="-14.3" font-family="Times,serif" font-size="14.00">DB</text>
</g>
<!-- auth&#45;server -->
<g id="node4" class="node"><title>auth&#45;server</title>
<ellipse fill="none" stroke="black" cx="50.0456" cy="-91" rx="50.0912" ry="18"/>
<text text-anchor="middle" x="50.0456" y="-87.3" font-family="Times,serif" font-size="14.00">auth&#45;server</text>
</g>
<!-- dovecot&#45;&gt;auth&#45;server -->
<g id="edge2" class="edge"><title>dovecot&#45;&gt;auth&#45;server</title>
<path fill="none" stroke="black" d="M97.6698,-160.611C89.0733,-148.226 77.1245,-131.012 67.3462,-116.925"/>
<polygon fill="black" stroke="black" points="70.1652,-114.848 61.5877,-108.629 64.4147,-118.839 70.1652,-114.848"/>
<text text-anchor="middle" x="99.5456" y="-130.8" font-family="Times,serif" font-size="14.00">PAM</text>
</g>
<!-- keylookupd -->
<g id="node5" class="node"><title>keylookupd</title>
<ellipse fill="none" stroke="black" cx="170.046" cy="-91" rx="51.9908" ry="18"/>
<text text-anchor="middle" x="170.046" y="-87.3" font-family="Times,serif" font-size="14.00">keylookupd</text>
</g>
<!-- dovecot&#45;&gt;keylookupd -->
<g id="edge4" class="edge"><title>dovecot&#45;&gt;keylookupd</title>
<path fill="none" stroke="black" d="M120.807,-160.611C129.695,-148.226 142.049,-131.012 152.158,-116.925"/>
<polygon fill="black" stroke="black" points="155.125,-118.794 158.112,-108.629 149.438,-114.712 155.125,-118.794"/>
</g>
<!-- auth&#45;server&#45;&gt;DB -->
<g id="edge3" class="edge"><title>auth&#45;server&#45;&gt;DB</title>
<path fill="none" stroke="black" d="M66.1566,-73.889C75.2433,-64.8023 86.7749,-53.2707 96.9204,-43.1251"/>
<polygon fill="black" stroke="black" points="99.412,-45.5833 104.008,-36.0373 94.4623,-40.6335 99.412,-45.5833"/>
</g>
<!-- keylookupd&#45;&gt;DB -->
<g id="edge5" class="edge"><title>keylookupd&#45;&gt;DB</title>
<path fill="none" stroke="black" d="M158.433,-73.174C152.54,-64.6343 145.255,-54.0791 138.686,-44.5607"/>
<polygon fill="black" stroke="black" points="141.476,-42.4407 132.915,-36.1984 135.714,-46.4167 141.476,-42.4407"/>
</g>
</g>
</svg>
digraph sso {
{
rank=min
user [color=gray]
}
user -> roundcube
roundcube -> IP [label="sso:rc"]
IP -> roundcube [label="sso:dovecot"]
roundcube -> dovecot
dovecot -> keylookupd
keylookupd -> keystore
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: sso Pages: 1 -->
<svg width="196pt" height="350pt"
viewBox="0.00 0.00 196.00 350.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 346)">
<title>sso</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-346 191.995,-346 191.995,4 -4,4"/>
<!-- user -->
<g id="node1" class="node"><title>user</title>
<ellipse fill="none" stroke="gray" cx="57" cy="-324" rx="27" ry="18"/>
<text text-anchor="middle" x="57" y="-320.3" font-family="Times,serif" font-size="14.00">user</text>
</g>
<!-- roundcube -->
<g id="node2" class="node"><title>roundcube</title>
<ellipse fill="none" stroke="black" cx="57" cy="-251" rx="48.1917" ry="18"/>
<text text-anchor="middle" x="57" y="-247.3" font-family="Times,serif" font-size="14.00">roundcube</text>
</g>
<!-- user&#45;&gt;roundcube -->
<g id="edge1" class="edge"><title>user&#45;&gt;roundcube</title>
<path fill="none" stroke="black" d="M57,-305.813C57,-297.789 57,-288.047 57,-279.069"/>
<polygon fill="black" stroke="black" points="60.5001,-279.029 57,-269.029 53.5001,-279.029 60.5001,-279.029"/>
</g>
<!-- IP -->
<g id="node3" class="node"><title>IP</title>
<ellipse fill="none" stroke="black" cx="27" cy="-164" rx="27" ry="18"/>
<text text-anchor="middle" x="27" y="-160.3" font-family="Times,serif" font-size="14.00">IP</text>
</g>
<!-- roundcube&#45;&gt;IP -->
<g id="edge2" class="edge"><title>roundcube&#45;&gt;IP</title>
<path fill="none" stroke="black" d="M26.088,-237.117C16.9959,-231.701 8.12175,-224.423 3,-215 -1.75914,-206.244 1.25288,-196.395 6.53025,-187.769"/>
<polygon fill="black" stroke="black" points="9.37888,-189.802 12.3588,-179.635 3.68898,-185.725 9.37888,-189.802"/>
<text text-anchor="middle" x="19" y="-203.8" font-family="Times,serif" font-size="14.00">sso:rc</text>
</g>
<!-- dovecot -->
<g id="node4" class="node"><title>dovecot</title>
<ellipse fill="none" stroke="black" cx="136" cy="-164" rx="38.1938" ry="18"/>
<text text-anchor="middle" x="136" y="-160.3" font-family="Times,serif" font-size="14.00">dovecot</text>
</g>
<!-- roundcube&#45;&gt;dovecot -->
<g id="edge4" class="edge"><title>roundcube&#45;&gt;dovecot</title>
<path fill="none" stroke="black" d="M85.0109,-236.28C94.4523,-230.658 104.488,-223.466 112,-215 118.063,-208.167 122.929,-199.531 126.652,-191.368"/>
<polygon fill="black" stroke="black" points="129.882,-192.714 130.5,-182.137 123.421,-190.02 129.882,-192.714"/>
</g>
<!-- IP&#45;&gt;roundcube -->
<g id="edge3" class="edge"><title>IP&#45;&gt;roundcube</title>
<path fill="none" stroke="black" d="M32.8577,-181.597C37.0092,-193.36 42.6711,-209.402 47.4684,-222.994"/>
<polygon fill="black" stroke="black" points="44.3003,-224.534 50.9291,-232.799 50.9013,-222.204 44.3003,-224.534"/>
<text text-anchor="middle" x="76" y="-203.8" font-family="Times,serif" font-size="14.00">sso:dovecot</text>
</g>
<!-- keylookupd -->
<g id="node5" class="node"><title>keylookupd</title>
<ellipse fill="none" stroke="black" cx="136" cy="-91" rx="51.9908" ry="18"/>
<text text-anchor="middle" x="136" y="-87.3" font-family="Times,serif" font-size="14.00">keylookupd</text>
</g>
<!-- dovecot&#45;&gt;keylookupd -->
<g id="edge5" class="edge"><title>dovecot&#45;&gt;keylookupd</title>
<path fill="none" stroke="black" d="M136,-145.813C136,-137.789 136,-128.047 136,-119.069"/>
<polygon fill="black" stroke="black" points="139.5,-119.029 136,-109.029 132.5,-119.029 139.5,-119.029"/>
</g>
<!-- keystore -->
<g id="node6" class="node"><title>keystore</title>
<ellipse fill="none" stroke="black" cx="136" cy="-18" rx="40.0939" ry="18"/>
<text text-anchor="middle" x="136" y="-14.3" font-family="Times,serif" font-size="14.00">keystore</text>
</g>
<!-- keylookupd&#45;&gt;keystore -->
<g id="edge6" class="edge"><title>keylookupd&#45;&gt;keystore</title>
<path fill="none" stroke="black" d="M136,-72.8129C136,-64.7895 136,-55.0475 136,-46.0691"/>
<polygon fill="black" stroke="black" points="139.5,-46.0288 136,-36.0288 132.5,-46.0289 139.5,-46.0288"/>
</g>
</g>
</svg>
......@@ -160,3 +160,85 @@ and keeps it around until the login expires.
the key storage service, includes a dedicated Dovecot dict proxy
interface (Dovecot is the primary use case for the encrypted secrets
feature).
# Authentication workflows
In this section we try to document step-by-step the various
authentication workflows, to illustrate the interactions between the
various authentication-related services described above.
## Single sign-on
![SSO workflow](sso.svg)
1. The first time a user connects to *service1*, it is redirected to
the IP (*identity provider*).
2. The IP handles the authentication UX (form with username and
password, OTP, U2F, etc).
3. The IP verifies the credentials with the authentication server.
4. The authentication server verifies the credentials against what is
in the database. If the credentials are good but incomplete
(i.e. we have the right password but no 2FA), go back to step 2 and
ask for the second factor.
5. The IP uses the user password to unlock the user's key by calling
the keystore service.
6. The keystore fetches the key from the database, decrypts it, and
caches it in memory.
When the SSO token for *service1* expires, and the user is once again
redirected back to the IP, the identity provider can skip the
authentication process (if it recognizes the user) and simply create a
new valid token straight away. This process is transparent to the user
(well, for GET requests at least).
## Non-HTTP service login
Let's take *dovecot* as an example of a non-HTTP service.
![Non-HTTP login workflow](auth.svg)
1. The user connects to dovecot using an IMAP client. The client sends
credentials that look like a password, which are either:
* the user's primary password (for users without 2FA), or
* a service-specific password valid for the *dovecot* service
2. Dovecot verifies the credentials with the authentication server,
using PAM (dovecot supports many ways to plug in a custom
authentication protocol, PAM is just one of them).
3. The authentication server verifies the credentials against what is
in the database. There is no support for "incomplete" credentials
here because the IMAP protocol is not conversational.
4. Dovecot fetches the (decrypted) user encryption key from
keylookupd, sending it the credentials used in the IMAP login. It
will keep this key in memory for the duration of the IMAP
connection.
5. Keylookupd fetches the (encrypted) user encryption key from the
database and decrypts it using the credentials.
## Third-party service authentication
Let's examine a more complex interaction, where a HTTP-based service
(*roundcube*, a webmail application) needs to access internally a
different service (dovecot, in order to read the user's email).
![Third-party authentication workflow](auth2.svg)
1. The user has a valid SSO token for the *roundcube* service, and
connects to the roundcube web application.
2. Roundcube exchanges the user SSO token for another one that is
valid for the *dovecot* service, by using the "exchange" API of the
IP (identity provider).
3. The IP verifies that the roundcube SSO token is valid, and that the
roundcube -\> dovecot transition is authorized (via a
whitelist). It signs a new token for the same user with the new
service "dovecot".
4. Roundcube talks to dovecot and logs in on behalf of the user
providing this new SSO token as the password.
5. Dovecot verifies that the username and SSO token are valid (using
*pam_sso*), and retrieves the (decrypted) user encryption key from
the keystore.
6. The keystore already has the (decrypted) user encryption key cached
in memory because at some point in time *before* accessing the
roundcube web application, the user has logged in to the IP, which
has unlocked the key in keystore (see the "single sign-on" workflow
description above, step 6).
digraph sso {
{
rank=min
user [color=gray]
}
{
DB [shape=cylinder]
}
user -> IP [label="login"]
user -> service1 [label="unauth"]
service1 -> user [label="redir"]
user -> service1 [label="token"]
user -> service2 [label="token"]
IP -> user [label="token"]
IP -> "auth-server"
"auth-server" -> DB
IP -> keystore
keystore -> DB
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: sso Pages: 1 -->
<svg width="374pt" height="277pt"
viewBox="0.00 0.00 374.34 277.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 273)">
<title>sso</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-273 370.342,-273 370.342,4 -4,4"/>
<!-- user -->
<g id="node1" class="node"><title>user</title>
<ellipse fill="none" stroke="gray" cx="193.046" cy="-251" rx="27" ry="18"/>
<text text-anchor="middle" x="193.046" y="-247.3" font-family="Times,serif" font-size="14.00">user</text>
</g>
<!-- IP -->
<g id="node3" class="node"><title>IP</title>
<ellipse fill="none" stroke="black" cx="83.0456" cy="-164" rx="27" ry="18"/>
<text text-anchor="middle" x="83.0456" y="-160.3" font-family="Times,serif" font-size="14.00">IP</text>
</g>
<!-- user&#45;&gt;IP -->
<g id="edge1" class="edge"><title>user&#45;&gt;IP</title>
<path fill="none" stroke="black" d="M166.653,-246.909C131.465,-242.184 72.0895,-231.889 59.0456,-215 52.712,-206.8 55.9437,-196.659 61.8182,-187.657"/>
<polygon fill="black" stroke="black" points="64.7707,-189.557 68.0344,-179.477 59.1973,-185.322 64.7707,-189.557"/>
<text text-anchor="middle" x="73.0456" y="-203.8" font-family="Times,serif" font-size="14.00">login</text>
</g>
<!-- service1 -->
<g id="node4" class="node"><title>service1</title>
<ellipse fill="none" stroke="black" cx="223.046" cy="-164" rx="40.0939" ry="18"/>
<text text-anchor="middle" x="223.046" y="-160.3" font-family="Times,serif" font-size="14.00">service1</text>
</g>
<!-- user&#45;&gt;service1 -->
<g id="edge2" class="edge"><title>user&#45;&gt;service1</title>
<path fill="none" stroke="black" d="M176.72,-236.452C166.688,-226.412 156.994,-212.382 164.046,-200 168.494,-192.19 175.447,-185.942 183.041,-181.016"/>
<polygon fill="black" stroke="black" points="185.188,-183.82 192.139,-175.824 181.719,-177.74 185.188,-183.82"/>
<text text-anchor="middle" x="182.546" y="-203.8" font-family="Times,serif" font-size="14.00">unauth</text>
</g>
<!-- user&#45;&gt;service1 -->
<g id="edge4" class="edge"><title>user&#45;&gt;service1</title>
<path fill="none" stroke="black" d="M198.973,-233.207C203.136,-221.409 208.794,-205.378 213.579,-191.822"/>
<polygon fill="black" stroke="black" points="217,-192.644 217.028,-182.049 210.399,-190.314 217,-192.644"/>
<text text-anchor="middle" x="225.546" y="-203.8" font-family="Times,serif" font-size="14.00">token</text>
</g>
<!-- service2 -->
<g id="node5" class="node"><title>service2</title>
<ellipse fill="none" stroke="black" cx="326.046" cy="-164" rx="40.0939" ry="18"/>
<text text-anchor="middle" x="326.046" y="-160.3" font-family="Times,serif" font-size="14.00">service2</text>
</g>
<!-- user&#45;&gt;service2 -->
<g id="edge5" class="edge"><title>user&#45;&gt;service2</title>
<path fill="none" stroke="black" d="M218.75,-244.726C239.033,-239.627 267.274,-230.367 288.046,-215 296.986,-208.386 304.834,-199.105 311.042,-190.332"/>
<polygon fill="black" stroke="black" points="314.062,-192.112 316.682,-181.846 308.232,-188.237 314.062,-192.112"/>
<text text-anchor="middle" x="318.546" y="-203.8" font-family="Times,serif" font-size="14.00">token</text>
</g>
<!-- DB -->
<g id="node2" class="node"><title>DB</title>
<polygon fill="none" stroke="black" points="141.046,-36 87.0456,-36 87.0456,-0 141.046,-0 141.046,-36"/>
<text text-anchor="middle" x="114.046" y="-14.3" font-family="Times,serif" font-size="14.00">DB</text>
</g>
<!-- IP&#45;&gt;user -->
<g id="edge6" class="edge"><title>IP&#45;&gt;user</title>
<path fill="none" stroke="black" d="M94.4902,-180.773C102.714,-191.354 114.522,-205.111 127.046,-215 137.463,-223.226 150.1,-230.468 161.516,-236.199"/>
<polygon fill="black" stroke="black" points="160.215,-239.458 170.743,-240.646 163.254,-233.152 160.215,-239.458"/>
<text text-anchor="middle" x="142.546" y="-203.8" font-family="Times,serif" font-size="14.00">token</text>
</g>
<!-- auth&#45;server -->
<g id="node6" class="node"><title>auth&#45;server</title>
<ellipse fill="none" stroke="black" cx="50.0456" cy="-91" rx="50.0912" ry="18"/>
<text text-anchor="middle" x="50.0456" y="-87.3" font-family="Times,serif" font-size="14.00">auth&#45;server</text>
</g>
<!-- IP&#45;&gt;auth&#45;server -->
<g id="edge7" class="edge"><title>IP&#45;&gt;auth&#45;server</title>
<path fill="none" stroke="black" d="M75.3918,-146.533C71.4504,-138.053 66.5531,-127.516 62.1169,-117.972"/>
<polygon fill="black" stroke="black" points="65.2759,-116.464 57.887,-108.871 58.928,-119.415 65.2759,-116.464"/>
</g>
<!-- keystore -->
<g id="node7" class="node"><title>keystore</title>
<ellipse fill="none" stroke="black" cx="158.046" cy="-91" rx="40.0939" ry="18"/>
<text text-anchor="middle" x="158.046" y="-87.3" font-family="Times,serif" font-size="14.00">keystore</text>
</g>
<!-- IP&#45;&gt;keystore -->
<g id="edge9" class="edge"><title>IP&#45;&gt;keystore</title>
<path fill="none" stroke="black" d="M98.2229,-148.632C108.546,-138.86 122.417,-125.728 134.203,-114.571"/>
<polygon fill="black" stroke="black" points="136.733,-116.995 141.589,-107.579 131.921,-111.912 136.733,-116.995"/>
</g>
<!-- service1&#45;&gt;user -->
<g id="edge3" class="edge"><title>service1&#45;&gt;user</title>
<path fill="none" stroke="black" d="M242.665,-180.118C252.914,-189.933 262.008,-203.074 255.046,-215 248.716,-225.841 237.65,-233.589 226.603,-238.992"/>
<polygon fill="black" stroke="black" points="225.196,-235.787 217.431,-242.995 227.996,-242.203 225.196,-235.787"/>
<text text-anchor="middle" x="271.046" y="-203.8" font-family="Times,serif" font-size="14.00">redir</text>
</g>
<!-- auth&#45;server&#45;&gt;DB -->
<g id="edge8" class="edge"><title>auth&#45;server&#45;&gt;DB</title>
<path fill="none" stroke="black" d="M64.8893,-73.5327C72.8146,-64.7406 82.7332,-53.7371 91.5814,-43.9212"/>
<polygon fill="black" stroke="black" points="94.2937,-46.1397 98.3894,-36.3686 89.0942,-41.4529 94.2937,-46.1397"/>
</g>
<!-- keystore&#45;&gt;DB -->
<g id="edge10" class="edge"><title>keystore&#45;&gt;DB</title>
<path fill="none" stroke="black" d="M147.84,-73.5327C142.608,-65.0888 136.111,-54.6052 130.216,-45.0928"/>
<polygon fill="black" stroke="black" points="133.052,-43.025 124.809,-36.3686 127.102,-46.7124 133.052,-43.025"/>
</g>
</g>
</svg>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment