Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keycloak SSO #5711

Open
wants to merge 5 commits into
base: refactor/argilla-server/better-oauth2-integration
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions argilla-frontend/components/features/login/components/KeycloakLogo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<template>
<!--https://github.com/keycloak/keycloak-misc/blob/main/logo/icon.svg-->
<svg
width="256"
height="256"
viewBox="0 0 44.216 39.861"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m88.61 138.456 5.716-9.865 23.018-.004 5.686 9.965.007 19.932-5.691 9.957-23.012.008-5.782-9.965z"
style="
display: inline;
fill: #4d4d4d;
fill-opacity: 1;
stroke-width: 0.264583;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M88.552 158.481h10.375l-5.699-10.041 4.634-9.982-9.252-.002-5.795 10.065"
style="
fill: #ededed;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M102.073 158.481h7.582l6.706-9.773-6.589-10.156h-8.921l-5.373 9.814z"
style="
fill: #e0e0e0;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m82.815 148.52 5.738 9.964h10.374l-5.636-9.93z"
style="
fill: #acacac;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m95.589 148.522 6.484 9.963h7.582l6.601-9.959z"
style="
fill: #9e9e9e;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m98.157 148.529-1.958.569-1.877-.572 7.667-13.288 1.918 3.316"
style="
fill: #00b8e3;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m103.9 158.482-1.909 3.332-5.093-5.487-2.58-7.797v-.004h3.838"
style="
fill: #33c6e9;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M94.322 148.526h-.003v.003l-1.918 3.322-1.925-3.307 1.952-3.386 5.728-9.92h3.834"
style="
fill: #008aaa;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M115.42 158.481h11.611l-.007-19.93h-11.605z"
style="
fill: #d4d4d4;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M115.42 148.554v9.93h11.59v-9.93z"
style="
fill: #919191;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M101.992 161.817h-3.836l-5.755-9.966 1.918-3.321z"
style="
fill: #00b8e3;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m117.333 148.526-7.669 13.289c-.705-1.036-1.913-3.331-1.913-3.331l5.753-9.959z"
style="
fill: #008aaa;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="m113.495 161.815-3.831-.001 7.67-13.288 1.917-3.317 1.921 3.34m-3.839-.023h-3.828l-5.755-9.973 1.905-3.314 4.658 5.922z"
style="
fill: #00b8e3;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
<path
d="M119.25 145.205v.003l-1.917 3.318-7.677-13.286 3.841.002z"
style="
fill: #33c6e9;
fill-opacity: 1;
fill-rule: nonzero;
stroke: none;
stroke-width: 0.330729;
"
transform="translate(-82.815 -128.588)"
/>
</svg>
</template>
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
<template>
<BaseButton class="sign-in-button" @click="$emit('click')">
<KeycloakLogo v-if="provider === 'keycloak'" />
{{ signinText }}
</BaseButton>
</template>

<script>
import KeycloakLogo from "./KeycloakLogo.vue";

export default {
name: "OAuthLoginButton",
components: {
KeycloakLogo,
},
props: {
provider: {
type: String,
Expand Down
2 changes: 1 addition & 1 deletion argilla-frontend/translation/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default {
button: {
ignore_and_continue: "Ignorieren und fortfahren",
login: "Anmelden",
signin_with_provider: "Anmeldung bei {provider} starten",
signin_with_provider: "Mit {provider} anmelden",
"hf-login": "Mit Hugging Face anmelden",
sign_in_with_username: "Mit Benutzername anmelden",
cancel: "Abbrechen",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

import os
from typing import Type, Dict, Any
from typing import Type, Dict, Any, Optional

from social_core.backends.oauth import BaseOAuth2
from social_core.backends.open_id_connect import OpenIdConnectAuth
Expand Down Expand Up @@ -48,6 +48,26 @@ class HuggingfaceOpenId(OpenIdConnectAuth):
DEFAULT_SCOPE = ["openid", "profile"]


class KeycloakOpenId(OpenIdConnectAuth):
"""Huggingface OpenID Connect authentication backend."""

name = "keycloak"

def oidc_endpoint(self) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only need this one. The rest of the required values (auth URL or access token URL) should be returned by the oicd_config endpoint based on the oidc endpoint

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this works, I updated the docs accordingly. Now configuring keycloak would work by just setting the SOCIAL_AUTH_OIDC_ENDPOINT environment variable in addition to the oauth yaml.

value = super().oidc_endpoint()

if value is None:
from social_core.utils import setting_name

name = setting_name("OIDC_ENDPOINT")
raise ValueError(
"oidc_endpoint needs to be set in the Keycloak configuration. "
f"Please set the {name} environment variable."
)

return value


_SUPPORTED_BACKENDS = {}


Expand All @@ -56,6 +76,7 @@ def load_supported_backends(extra_backends: list = None) -> Dict[str, Type[BaseO

backends = [
"argilla_server.security.authentication.oauth2._backends.HuggingfaceOpenId",
"argilla_server.security.authentication.oauth2._backends.KeycloakOpenId",
"social_core.backends.github.GithubOAuth2",
"social_core.backends.google.GoogleOAuth2",
]
Expand Down
152 changes: 152 additions & 0 deletions argilla/docs/reference/argilla-server/sso_keycloak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# SSO Integration Keycloak

## Set-up Keycloak

To test this run a test version of Keycloak in Docker:

```bash
docker run -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.0.5 start-dev
```

General steps:
1. create a new realm and a new client to use with Argilla.
2. The client should expose the client audience via userinfo.
3. After that add the users you want to have access to argilla.

The script below should do all of that for you to test. It needs one python dependency you can install with `pip install python-keycloak`.


```python
from keycloak import KeycloakAdmin
from keycloak import KeycloakOpenIDConnection
from keycloak import KeycloakOpenID

keycloak_connection = KeycloakOpenIDConnection(
server_url="http://localhost:8080/",
username="admin",
password="admin",
realm_name="master",
client_id="admin-cli",
)

keycloak_admin = KeycloakAdmin(connection=keycloak_connection)

keycloak_admin.create_realm(
{
"realm": "argilla",
"enabled": True,
"displayName": "Argilla",
"userManagedAccessAllowed": True,
}
)
keycloak_connection = KeycloakOpenIDConnection(
server_url="http://localhost:8080/",
username="admin",
password="admin",
user_realm_name="master",
realm_name="argilla",
)

keycloak_admin = KeycloakAdmin(connection=keycloak_connection)

client = keycloak_admin.create_client(
{
"clientId": "example-client", # The client ID (you can choose a name)
"enabled": True,
"protocol": "openid-connect", # Protocol (you can use other protocols like 'saml' if needed)
"publicClient": False, # Set to False if the client will use client secrets
"directAccessGrantsEnabled": True,
"standardFlowEnabled": True,
"frontchannelLogout": True,
"secret": "client-secret", # Set a secret if it's not a public client
"redirectUris": [
"http://localhost:3000/*",
"http://localhost:6900/*",
], # Redirect URIs after authentication
}
)

keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/",
client_id="example-client",
realm_name="argilla")

public_key = keycloak_openid.public_key()

client_scope = keycloak_admin.create_client_scope({
"name": "example-client-scope_3",
"protocol": "openid-connect"
})

# Create Audience Mapper
mapper = keycloak_admin.add_mapper_to_client_scope(
client_scope_id=client_scope,
payload={
"name": "Client Audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": False,
"config": {
"included.client.audience": "example-client",
"id.token.claim": "false",
"access.token.claim": "true"
}
})

keycloak_admin.add_default_default_client_scope(client_scope)

new_user = keycloak_admin.create_user(
{
"email": "[email protected]",
"username": "example",
"enabled": True,
"firstName": "Example",
"lastName": "User",
"credentials": [
{
"value": "secret",
"type": "password",
}
],
}
)
```

## Set-up Argilla Server

After that you need to configure you endpoints in the `.oauth.yaml` same as this is done for the HuggingFace Oauth:

```yaml
# Change to `false` to disable HF oauth integration
#enabled: false

allow_http_redirect: true

providers:
- name: keycloak
client_id: <name of your client e.g. example-client>
client_secret: <value of your specified secret e.g. client-secret>
redirect_uri: http://localhost:3000/oauth/keycloak/callback # if you test locally
- name: huggingface
client_id: <create a new https://huggingface.co/settings/connected-applications>
client_secret: <create a new https://huggingface.co/settings/connected-applications>
redirect_uri: http://localhost:3000/oauth/huggingface/callback # if you test locally

# Allowed workspaces must exists
allowed_workspaces:
- name: default
```

Then you need to set the two environment variables:

```bash
export SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL="http://localhost:8080/realms/argilla/protocol/openid-connect/auth"
export SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL="http://localhost:8080/realms/argilla/protocol/openid-connect/token"
export SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY=MIIBIj...
export SOCIAL_AUTH_KEYCLOAK_KEY=argilla
```

- `http://localhost:8080` is your keycloak endpoint in this case the local docker
- `argilla` is the name of the realm configured above
- `MIIBIj...` is the public key from the script above `public_key = keycloak_openid.public_key()`