Single Sign-On with OpenBao

This section explains how to enable SSO in Parsec via OpenBao, an open-source secrets manager from the Linux Foundation’s OpenSSF community.

Overview

Parsec supports SSO via an OpenBao server connected to an OIDC identity provider. This integration serves the following purposes:

User Authentication & Device protection

Parsec stores cryptographic device keys on the user’s machine. These keys are protected by different mechanism depending on the selected authentication method.

With SSO, the keys are encrypted using the user’s SSO identity. This way, an attacker who manages to steal the key file cannot decrypt it without also controlling the user’s SSO account.

Asynchronous enrollment

When a new user wants to join a Parsec organization, they normally go through a synchronous enrollment: both the new user and the organization administrator must be online at the same time and exchange codes to validate each other identity.

With SSO, the identity is provided for both parties which means the enrollment can be done in an asynchronous way: the user sends a request to join, and the administrator approves (or reject) the join request later (no simultaneous presence is required).

Note

Only ProConnect OIDC is fully supported for the moment.

Security

The security level of storing opaque keys in OpenBao is lower than traditional approaches, such as storing the keys on the OS Keyring or deriving them from a strong password.

On the other hand, if your users are already familiar with SSO and use it for other services, they probably already have an account which will greatly simplify both Parsec login and enrolment.

This is therefore a typical trade-off between security and user-friendliness and the decision to whether use this method or not must be made by the server administrator and should be consistent with your own threat model.

Architecture

flowchart TD oip[OpenID Provider] bao[OpenBao Server] client[Parsec Client] server[Parsec Server] oip --(1) OIDC authentication (login / token) ---> client client --(2) Sign join request or (d)encrypt device keys---> bao client --(3) Submit signed join request---> server
  1. The Parsec client authenticates the user against an OpenID provider (such as a corporate or government SSO). OpenBao is configured to trust that provider, so the resulting token is usable on OpenBao.

  2. The Parsec client uses the token to call OpenBao to either:

    • Store or retrieve the encrypted device keys (“Device protection” use-case), or

    • Sign/verify an enrollment payload using the user’s per-entity transit key (“Asynchronous enrollment” use-case).

  3. For asynchronous enrollment, the signed enrollment request is then send to the Parsec Server, for later retrieval by the organization administrator (which can then verify its signature using OpenBao).

Requirements

  • A running OpenBao instance reachable by both users and the Parsec Server.

  • An OIDC-compatible identity provider

  • The bao CLI tool to configure OpenBao.

OIDC Identity provider configuration

OIDC authentication works by passing a token to the client through an HTTP redirection initiated by the identity provider.

For this, the OIDC identity provider must be configured two redirection URL to use in web and native:

  • For web, the URL is https://<your-parsec-server>/client/oidc/callback.

  • For the native client, the URL is always https://callback.parsec.cloud.invalid/oidc/callback.

Note

The native client doesn’t actually request the redirection URL (and this is not even possible given it is a .invalid TLD). Instead this special domain name is detected by the client to prevent the redirection and directly extract the authentication token from the URL.

OpenBao configuration

The following steps are required to configure OpenBao to enable SSO in Parsec.

Before starting, make sure to define the following environment variables:

  • BAO_ADDR: The URL of your OpenBao instance

  • BAO_TOKEN: The root token to authenticate to the OpenBao instance

For instance:

export BAO_ADDR=https://openbao.example.com
export BAO_TOKEN=s.raPGTZdARXdY0KvHcWSpp5wWZIHNT

Enable the required secrets engines

# Transit engine — used to sign/verify enrollment payloads
bao secrets enable -path=transit transit

# KV v2 engine — used to store encrypted device key files
bao secrets enable -path=secret kv-v2

Enable and configure the OIDC auth method

The path <parsec_oidc> is used below for the OIDC auth method. If you want you can specify a different mount path.

Use your client credentials to replace <your-client-id> and load the secret from a client-secret.txt file so it is not displayed in the command line and to prevent it to show up in the shell history.

# Enable the OIDC auth method at path "<parsec_oidc>"
bao auth enable -path=<parsec_oidc> oidc

# Point it at your identity provider and set the client credentials
bao write auth/<parsec_oidc>/config \
    oidc_discovery_url="https://<parsec_oidc>.example.com/login" \
    oidc_client_id="<your-client-id>" \
    oidc_client_secret=@client-secret.txt \
    default_role="default"

Configure the OIDC role

The role maps a field from the OIDC token to the OpenBao entity. Parsec uses the email claim to identify users, so that the enrollment workflow can verify that the email in the enrollment payload matches the authenticated user’s email.

bao write auth/<parsec_oidc>/role/default \
    user_claim="email" \
    allowed_redirect_uris="https://<your-parsec-server>/client/oidc/callback" \
    allowed_redirect_uris="https://callback.parsec.cloud.invalid/oidc/callback" \
    token_policies="parsec-default"

Note

allowed_redirect_uris must include all redirect URIs that the Parsec client will use. Consult your OpenBao deployment documentation for the exact values.

ACL policy

To define the ACL policy, create a file parsec-default.hcl with the following content:

parsec-default.hcl
 1# -------------------------------------------------------------------------
 2# Token self-management
 3# -------------------------------------------------------------------------
 4
 5# Allow tokens to look up their own properties
 6path "auth/token/lookup-self" {
 7    capabilities = ["read"]
 8}
 9
10# Allow tokens to renew themselves
11path "auth/token/renew-self" {
12    capabilities = ["update"]
13}
14
15# Allow tokens to revoke themselves
16path "auth/token/revoke-self" {
17    capabilities = ["update"]
18}
19
20# Allow a token to look up its own entity by id or name
21path "identity/entity/id/{{identity.entity.id}}" {
22    capabilities = ["read"]
23}
24path "identity/entity/name/{{identity.entity.name}}" {
25    capabilities = ["read"]
26}
27
28# Allow a token to look up its resultant ACL from all policies
29path "sys/internal/ui/resultant-acl" {
30    capabilities = ["read"]
31}
32
33# Allow a token to make requests to the Authorization Endpoint for OIDC providers
34path "identity/oidc/provider/+/authorize" {
35    capabilities = ["read", "update"]
36}
37
38# -------------------------------------------------------------------------
39# Parsec per-entity device key store
40# (used to protect device files stored on the user's machine)
41# -------------------------------------------------------------------------
42
43# Allow an entity to store its own device keys
44path "secret/data/{{identity.entity.id}}/*" {
45    capabilities = ["create", "update", "patch", "read", "delete"]
46}
47path "secret/metadata/{{identity.entity.id}}/*" {
48    capabilities = ["read", "list", "delete"]
49}
50
51# -------------------------------------------------------------------------
52# Parsec entity-to-entity authenticated message passing
53# (used to sign and verify asynchronous enrollment payloads)
54# -------------------------------------------------------------------------
55
56# User creates its own signing key in the transit engine
57path "transit/keys/entity-{{identity.entity.id}}" {
58    capabilities = ["update"]
59}
60
61# User signs a message with its own key
62path "transit/sign/entity-{{identity.entity.id}}" {
63    capabilities = ["update"]
64}
65
66# Any user can read another entity's metadata (to retrieve its email)
67path "identity/entity/id/*" {
68    capabilities = ["read"]
69}
70
71# Any user can verify a signature made by another entity
72path "transit/verify/entity-*" {
73    capabilities = ["update"]
74}

Apply the policy:

bao policy write parsec-default parsec-default.hcl

Verify the setup

# Log in as a test user
bao login -method=oidc -path=<parsec_oidc>

# Confirm the token has the expected capabilities:
bao token capabilities \
transit/sign/entity-$(bao token lookup -format=json | jq -r '.data.entity_id')

Configure CORS

bao write sys/config/cors allowed_origins='https://<your-parsec-server>' allowed_origins='parsec-desktop://-'

Note

The parsec-desktop://- domain is used by the native Parsec client.

You can check that CORS is correctly configured for your domain with the following command:

curl -v -X OPTIONS https://<your-openbao-server>/v1/auth/<parsec_oidc>/oidc/auth_url \
      -H "Origin: https://<your-parsec-server>" \
      -H "Access-Control-Request-Method: GET"

Parsec Server configuration

Once OpenBao server is set up, you will need to configure your Parsec Server.

Following the same approach used during deployment, create a file to store the required environment variables (enter the OPENBAO_SERVER_URL from before):

parsec-openbao.env
# URL of the OpenBao server to allow SSO (OIDC) authentication in Parsec.
#
# Parsec will store opaque keys that are used to encrypt local device keys.
PARSEC_OPENBAO_SERVER_URL=<YOUR_OPENBAO_SERVER_URL>

# Mount path of the OpenBao KV2 secret module used for storage
PARSEC_OPENBAO_SECRET_MOUNT_PATH=secret

# Mount path of the OpenBao transit module
PARSEC_OPENBAO_TRANSIT_MOUNT_PATH=transit

# Mount path of the OpenBao auth module for end-user authentication using
# OIDC with ProConnect
PARSEC_OPENBAO_AUTH_PRO_CONNECT_MOUNT_PATH=parsec_oidc

Parsec Server with Docker

If you deployed Parsec Server with Docker, edit the Docker Compose file to add parsec-openbao.env:

parsec-server.docker.yaml
    env_file:
      - parsec.env
      - parsec-s3.env
      - parsec-db.env
      - parsec-smtp.env
      - parsec-admin-token.env
      - parsec-openbao.env

And then restart the server.

Parsec Server on Linux

If you deployed Parsec Server directly on Linux, edit your run script to add parsec-openbao.env:

run-parsec-server
# Load the virtual env
source venv/bin/activate

# Load the env files into the environment table
set -a
source parsec-openbao.env
source parsec-admin-token.env
source parsec-db.env
source parsec-smtp.env
source parsec-s3.env
source parsec.env
set +a

# Start Parsec Server
python -m parsec run

And then restart the server.