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
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.
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).
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 instanceBAO_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.