status-im / infra-office-legacy

Infrastructure for cloud office services
0 stars 0 forks source link

Configure WebAuthN for Keycloak #3

Closed jakubgs closed 2 years ago

jakubgs commented 3 years ago

We want to use WebAuthN standard for Authentication with KeyCloak.

This task is required to complete integrations with other services:

jakubgs commented 3 years ago

Some links:

jakubgs commented 3 years ago

Serhan stated in #1 that:

I don't want to use a traditional username/password + 2FA for the login/register process. I want to use WebAuthN with Yubikey for login/register + TOTP 2FA with Mobile App.

Which means that he wants passwordless WebAuthN, as described here: https://github.com/keycloak/keycloak-documentation/blob/master/server_admin/topics/authentication/webauthn.adoc#passwordless-webauthn-together-with-two-factor

jakubgs commented 3 years ago

There is configuration available in the Authentication section, which is mostly empty:

image

Most of the fields are well described in the docs: https://w3c.github.io/webauthn/#dictionary-makecredentialoptions As well as Keycloak docs: https://github.com/keycloak/keycloak-documentation/blob/master/server_admin/topics/authentication/webauthn.adoc#managing-policy

jakubgs commented 3 years ago

After configuring the WebAuthn Policy section I added the requirement to my Google user:

image

And then when I logged in I got prompted for registration of my WebAuthn device:

image

jakubgs commented 3 years ago

The default flow does not involve WebAuthn yet:

image

jakubgs commented 3 years ago

You cannot modify the default flows like the Browser one so it has to be copied and adjusted:

image

jakubgs commented 3 years ago

Nice, looks like I broke login:

image

jakubgs commented 3 years ago

I can see my copied flow in the DB:

keycloak=# SELECT id,alias,provider_id,top_level FROM authentication_flow WHERE alias LIKE '%WebAuthn%';
                  id                  |         alias          | provider_id | top_level 
--------------------------------------+------------------------+-------------+-----------
 ef2e5995-674b-438d-8450-f49a379ddc1e | WebAuthn Browser       | basic-flow  | t
 cde5514a-9bfd-416e-b8ea-25ca1c1c2f53 | WebAuthn Browser forms | basic-flow  | f
(2 rows)

The question is, which table holds the default authentication bindings configuration.

jakubgs commented 3 years ago

The list of tables is quite long, but I can't seem to find the one with the bindings: https://gist.github.com/jakubgs/4fc46647545888bebce6dadabb7fa0b2

jakubgs commented 3 years ago

Okay, I found it in the realm table, which is quite long:

keycloak=# SELECT id,name,otp_policy_type,browser_flow FROM realm;
   id   |  name  | otp_policy_type |             browser_flow             
--------+--------+-----------------+--------------------------------------
 master | master | totp            | ef2e5995-674b-438d-8450-f49a379ddc1e
(1 row)
jakubgs commented 3 years ago

Updating it back to browser flow and restarting Keycloak fixed it:

keycloak=# UPDATE realm SET browser_flow = '2da3ece8-e2c0-4227-ac76-11a86f2f9a86' WHERE id = 'master';
UPDATE 1

You must be careful with these changes to flows or you can lock yourself out.

jakubgs commented 3 years ago

This documentation describes flow configuration in detail: https://www.keycloak.org/docs/12.0/server_admin/#built-in-flows

jakubgs commented 3 years ago

Also, looks like flow names can't include special characters like +, >, or /:

image

jakubgs commented 3 years ago

Okay. I got a flow working that does User+Pass then optionally WebAuthn or OTP:

image

Editing these flows is a bit tricky. This UI needs serious work.

The Conditional OTP part is a custom Flow added with Add flow from the WebAuthn Browser Forms line.

jakubgs commented 3 years ago

I verified that a user created via Google OAuth can login using user/password + WebAuthn/OTP if they set those.

This means that we can re-use existing users if they were created via Google.

jakubgs commented 3 years ago

If you set up OTP or YubiKey with you account it shows up in the Credentials section of user profile:

image

So an admin can remove or reset them.

jakubgs commented 3 years ago

Interestingly, if I log in with an already existing account it prompts me what to do:

image

jakubgs commented 3 years ago

For some reason, despite WebAuthn being first in order it is not proposed as first option during login:

image

Instead OTP is proposed as first option:

image

Only after clicking the Try Another Way link to I see the WebAuthn option:

image

jakubgs commented 3 years ago

Upgraded Keycloak from 14.0.0 to 15.0.2: https://github.com/status-im/infra-office/commit/601f2abd

jakubgs commented 3 years ago

I also found this issue: https://issues.redhat.com/browse/KEYCLOAK-13199

In a Passwordless WebAuthn deployment, it's typically the preferred option, with password + OTP as a fallback in case the user's browser doesn't support it.

However in Keycloak, manual administrator intervention is required to get the WebAuthn prompt to come first. The priority ordering in the Authentication flow configuration is apparently overridden by the user's own credential ordering, and the standard way of enrolling new users (setting a temporary password) always puts Password first.

This means that the ordering of executions in the flow does not affect the order the user sees.

There is also this: https://issues.redhat.com/browse/KEYCLOAK-13320

jakubgs commented 3 years ago

This explains why as admin I see the OTP first:

image

After moving WebAuthn up in the list it appeared first during login. But this is only doable by admin.

jakubgs commented 3 years ago

Which means, in theory, if I make the WebAuthn configuration required BEFORE OTP, that should fix it:

image

jakubgs commented 3 years ago

I've made our current required actions look like this:

image

jakubgs commented 3 years ago

I tested registration flow with Serhan and ti turns out if the Update Password required action is not set to be a Default Action the user is not prompted for it, and password is not set up:

image

So I adjusted it to be default:

image

jakubgs commented 3 years ago

Now the user is first prompted for the password:

image

And then for WebAuthn and alter for OTP, which results in the correct order:

image

jakubgs commented 3 years ago

Now, according to Serhan the setup we want should not include the user password.

Assuming WebAuthn is 2 factors(hardware + pin) If we count factors the different kinds of setup look like this:

The possible advantage of the last option is that when either of the two hardware devices is lost by a user they cannot log into the account anymore and recovery would require an Administrator intervention.

jakubgs commented 3 years ago

After discussing this with Security team in a call I think we should still retain the password, but treat it as a kind of backup/recovery code rather than a primary way of authentication. Instead we would still use the passwordless flow as default, but use the password for fallbacks where user is missing one of the devices.

So the flow we'd want would look like this:

  1. Passwordless: a. Username b. WebAuthn c. OTP
  2. Fallback with Password a. Username Password b. WebAuthn or OTP

This way we can cover a case in which someone has lost one of their devices and can't replace them.

jakubgs commented 3 years ago

There was a request for recovery/backup codes in Keycloak, but it was ignored: https://issues.redhat.com/browse/KEYCLOAK-6270

There exists a backup code extension but it's not part of the Keycloak distribution: https://github.com/thomasdarimont/keycloak-extension-playground/tree/master/auth-backup-codes https://groups.google.com/g/keycloak-dev/c/SC1JvewgLwM

So treating the password as a recovery code makes sense.

jakubgs commented 3 years ago

I tried this flow, but it only results in user being prompted for WebAuthn without any alternatives in UI:

image

jakubgs commented 3 years ago

I also tried this layout, but the result is the same:

image

jakubgs commented 3 years ago

Based on this documentation I think I will need to revamp the current Keycloak setup to not use master realm: https://www.keycloak.org/docs/latest/getting_started/#realms-and-users

It will make more sense to create a status-im realm and use that to manage our applications separately. This will also allow for more strict auth flow for the master realm.

jakubgs commented 3 years ago

I was looking into using custom domain for the status-im realm, so I could keep keycloak.status.im for administration, but use auth.status.im or login.status.im for the status-im realm. Here are some links:

Although this might be tricky to achieve.

jakubgs commented 3 years ago

I've added the auth.status.im domain as alternative frontend for status-im realm: https://github.com/status-im/infra-office/commit/5f8d94f8

So it's now available at: https://auth.status.im/auth/realms/status-im/account/

jakubgs commented 3 years ago

I can already see one issue with the realm domain:

When I try to create a Google Identity provider the redirect URL uses keycloak.status.im:

image

jakubgs commented 3 years ago

I also configured SMTP via MailGun for both Keycloak Emails and it works. Will be necessary in the future for email verification.

jakubgs commented 3 years ago

I've also re-created the Camunda OAuth application, which was easy because it can be exported as a JSON:

image

Which then can be imported then creating a new one in a separate realm. But the secret gets regenerated. Unfortunately roles and groups had to be created by hand.

jakubgs commented 3 years ago

Unfortunately there is no built-in way to move users between realms, but it can be done with an SQL query: https://keycloak.discourse.group/t/moving-users-from-one-realm-to-another-realm/6257

But it doesn't look safe, and might break things.

jakubgs commented 3 years ago

I verfied that login into Camunda works with new status-im realm and my test user:

image

jakubgs commented 3 years ago

When adding a realm one can use a JSON export from another realm:

image

But the JSON has to be edited by hand to change the ID and name of realm and all UUIDs:

image

Which means it's easy possible to make a test realm to try out new flows without messing up the orignal.

jakubgs commented 3 years ago

It actually complains about things like UUIDs of roles being imported:

Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "constraint_a"
  Detail: Key (id)=(770bbf51-f40b-4f94-b332-aa2a7d448969) already exists.

Although it is possible to not export them at all:

image

jakubgs commented 3 years ago

Here's a doc about exporting/importing using the CLI tool: https://github.com/keycloak/keycloak-documentation/blob/master/server_admin/topics/export-import.adoc

jakubgs commented 3 years ago

This doc shows an example of a flow that could help use design our own:

image

https://github.com/keycloak/keycloak-community/blob/master/design/multi-factor-admin-and-step-up.md#flow-logic-examples

jakubgs commented 3 years ago

This flow ALMOST works, but it allows user to log in by using password twice...

image

Pretty dumb that it doesn't detect password has already been used.

jakubgs commented 3 years ago

I don't know if the flow we want is even possible with Keycloak without writing some custom conditional plugins.

jakubgs commented 3 years ago

I've created a thread on Keycloak Discourse asking about a "2 out of N" authentication flow:

https://keycloak.discourse.group/t/how-do-to-2-out-of-n-authentication-flow/10964

jakubgs commented 3 years ago

This video shows use of custom authenticator plugin: https://www.youtube.com/watch?v=u36QK9oyrtM

jakubgs commented 3 years ago

This is a nice intro into Keycloak that shows some event logging settings well: https://www.youtube.com/watch?v=XJYy6Aq-PJ8

jakubgs commented 3 years ago

Since configuring a password as fallback in "2 our of N" setup seems not doable in Keycloak I started looking at what happens if a user loses one of their credentials (otp or yubikey devices) and administrator has to remove them.

With this auth flow:

image

When I remove the OTP I get prompted to set it up again after logging in with just WebAuthn:

image

And if I remove the WebAuthn credential I get prompted to register a new device after logging in with OTP:

image

But the problem is, if I remove both, it prompts user to register both WITHOUT ANY IDENTITY VERIFICATION.

jakubgs commented 3 years ago

Ok, there is an alternative way to reset credentials, which is by sending an email with a reset link:

image

image

jakubgs commented 3 years ago

And it is possible to reset multiple credentials at the same time:

image

image

So in practice it should not be necessary to delete existing credentials as I did.

Though it still worries me that it allows login without any credentials if neither WebAuthn and OTP are configured.