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

feat: add PAM authenticator #6861

Merged
merged 6 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions doc/how_to/authentication/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ In other words OAuth outsources authentication to a third party provider, e.g. G
Discover how to add basic password based authentication to your application.
:::

:::{grid-item-card} {octicon}`unlock;2.5em;sd-mr-1 sd-animate-grow50` Configuring PAM Authentication
:link: pam
:link-type: doc

Discover how to configure Panel apps to configure against your system credentials (using PAM).
:::

:::{grid-item-card} {octicon}`gear;2.5em;sd-mr-1 sd-animate-grow50` Configuring OAuth
:link: configuration
:link-type: doc
Expand Down Expand Up @@ -82,6 +89,7 @@ Note that since Panel is built on Bokeh server and Tornado it is also possible t
:maxdepth: 2

basic
pam
configuration
providers
templates
Expand Down
76 changes: 76 additions & 0 deletions doc/how_to/authentication/pam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Configuring PAM Authentication

For scenarios where you want to leverage your system's existing user credentials, configuring PAM (Pluggable Authentication Modules) authentication is an effective solution. PAM authentication allows your application to authenticate users using the operating system's user database, eliminating the need to manage separate credentials.

```{admonition} Prerequisites
PAM authentication requires the [`pamela` library](https://github.com/minrk/pamela). Install it with:

:::::{tab-set}

::::{tab-item} pip

```bash
pip install pamela
```

::::

::::{tab-item} conda

```bash
conda install -c conda-forge pamela
```

::::

:::::

```

## Setting up PAM authentication

PAM authentication can be set up by specifying the --oauth-provider pam command-line argument (despite PAM not being an OAuth provider). User credentials are inherited directly from the system, simplifying the authentication process. Here’s how to configure it:

Create a basic `app.py` file.

```python
import panel as pn

pn.extension(template="fast")

logout = pn.widgets.Button(name="Log out")
logout.js_on_click(code="""window.location.href = './logout'""")
pn.Column(f"Congrats `{pn.state.user}`. You got access!", logout).servable()
```

Now serve the application using PAM authentication:

```bash
panel serve app.py --oauth-provider pam --cookie-secret my_super_safe_cookie_secret
```

When accessing the application, users will be prompted to log in using their system credentials:

![Basic Auth Login Form](../../_static/images/basic_auth.png)

If you enter an invalid password it will look like

![Invalid Basic Auth Login Form](../../_static/images/basic_auth_invalid.png)

If you enter the valid password (i.e. `my_password`) it will look like

![Valid Basic Auth Login Form](../../_static/images/basic_auth_valid.png)

If you click the *Log out* button you will be sent back to the log in form.

## User credentials

With PAM authentication, there is no need to manage a separate file for user credentials. Instead, authentication is handled directly through the operating system, ensuring that user credentials are consistent across all system applications.

## Custom templates

```{admonition} Prerequisites
For a more in-depth understanding of custom template take a look at the [how to > Build a Custom Template](../templates/template_custom) guide.
```

If you wish to customize the authentication template, you can provide a custom template using the --basic-login-template CLI argument. The template must submit username and password to the /login endpoint of the Panel server. For reference, the form structure of the default PAM authentication template is similar to basic authentication. You can view the default template here for inspiration.
23 changes: 22 additions & 1 deletion panel/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,26 @@ async def _refresh_access_token(self, user, refresh_token, application, request)
del state._oauth_user_overrides[user]


class PAMLoginHandler(BasicLoginHandler):
"""
A LoginHandler that authenticates users via PAM.
"""

def _validate(self, username, password):
try:
import pamela
except ImportError as e:
log.error(
"PAM authentication requires the pamela package. Please install it with e.g. 'pip install pamela'"
)
raise e
try:
pamela.authenticate(username, password)
except pamela.PAMError:
return False
return True


AUTH_PROVIDERS = {
'auth0': Auth0Handler,
'azure': AzureAdLoginHandler,
Expand All @@ -1203,7 +1223,8 @@ async def _refresh_access_token(self, user, refresh_token, application, request)
'gitlab': GitLabLoginHandler,
'okta': OktaLoginHandler,
'password': PasswordLoginHandler,
'auth_code': CodeChallengeLoginHandler
'auth_code': CodeChallengeLoginHandler,
'pam': PAMLoginHandler,
}

# Populate AUTH Providers from external extensions
Expand Down
7 changes: 4 additions & 3 deletions panel/command/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ def setup_file():
if args.oauth_provider:
config.oauth_provider = args.oauth_provider
if config.oauth_provider:
is_pam = config.oauth_provider
config.oauth_refresh_tokens = args.oauth_refresh_tokens
config.oauth_expiry = args.oauth_expiry_days
if config.oauth_key and args.oauth_key:
Expand All @@ -597,7 +598,7 @@ def setup_file():
)
elif args.oauth_key:
config.oauth_key = args.oauth_key
elif not config.oauth_key:
elif not (config.oauth_key or is_pam):
raise ValueError(
"When enabling an OAuth provider you must supply "
"a valid oauth_key either using the --oauth-key "
Expand All @@ -620,7 +621,7 @@ def setup_file():
)
elif args.oauth_secret:
config.oauth_secret = args.oauth_secret
elif not config.oauth_secret:
elif not (config.oauth_secret or is_pam):
raise ValueError(
"When enabling an OAuth provider you must supply "
"a valid OAuth secret either using the --oauth-secret "
Expand Down Expand Up @@ -651,7 +652,7 @@ def setup_file():
"base64-encoded bytes."
)
config.oauth_encryption_key = encryption_key
elif not config.oauth_encryption_key:
elif not (config.oauth_encryption_key or is_pam):
print("WARNING: OAuth has not been configured with an " # noqa: T201
"encryption key and will potentially leak "
"credentials in cookies and a JWT token embedded "
Expand Down