OpenConnect wrapper with Azure AD (SAML) SSO support for Cisco SSL-VPNs
Project description
openconnect-saml
OpenConnect wrapper supporting Azure AD / SAML authentication for Cisco AnyConnect SSL-VPNs.
Modernized fork based on vlaci/openconnect-sso with improvements from kowyo/openconnect-lite.
Features
- SAML / Azure AD authentication via embedded Qt WebEngine browser or headless CLI mode
- Headless mode: No display/GUI required — works on servers, containers, and SSH sessions
- Automatic form-filling for username, password, and TOTP
- Password stored in system keyring (with in-memory fallback)
- TOTP secret configurable directly in config file
- Profile auto-detection from AnyConnect XML profiles
- Proxy support (SOCKS/HTTP)
- Client certificate handling (auto-fallback on cert-request)
--no-sudomode for use with--script-tun--csd-wrapperpassthrough for CSD/hostscan support--reset-credentialsto clear saved keyring entries- Microsoft Authenticator number matching support
- Office365 "Stay signed in?" auto-dismiss
- Robust XML parsing (recovers from malformed responses)
Requirements
- Python ≥ 3.10
- OpenConnect installed and in PATH
- Qt6 WebEngine (provided by PyQt6) — only for GUI mode
Installation
# Headless mode (no GUI dependencies):
pip install openconnect-saml
# With GUI browser support:
pip install openconnect-saml[gui]
# Or via uv:
uv tool install openconnect-saml # headless
uv tool install openconnect-saml[gui] # with browser
Usage
GUI Mode (default)
# Connect to a VPN server
openconnect-saml --server vpn.example.com
# With a specific user group
openconnect-saml --server vpn.example.com/usergroup
# Use AnyConnect profile
openconnect-saml --profile /opt/cisco/anyconnect/profile
Headless Mode (no display required)
Perfect for servers, containers, CI/CD, and SSH sessions:
# Automatic authentication (username/password/TOTP from keyring)
openconnect-saml --server vpn.example.com --headless --user user@example.com
# Authentication only — output cookie for scripting
openconnect-saml --server vpn.example.com --headless --user user@example.com --authenticate json
How headless mode works:
- Automatic: Uses HTTP requests + form parsing to submit credentials (username, password, TOTP) without a browser. Works with standard Azure AD / Microsoft Online flows.
- Callback fallback: If automatic auth fails (e.g., CAPTCHA, unsupported MFA), starts a local HTTP server and prints a URL. Open the URL in any browser (even on another machine), authenticate, and the callback captures the token.
Docker Example
FROM python:3.12-slim
RUN pip install openconnect-saml
RUN apt-get update && apt-get install -y openconnect && rm -rf /var/lib/apt/lists/*
# Run headless — no GUI needed
ENTRYPOINT ["openconnect-saml", "--headless"]
docker run -it --cap-add=NET_ADMIN --device=/dev/net/tun \
vpn-client --server vpn.example.com --user user@example.com
Server Deployment
On a headless server (no X11/Wayland):
# Install without GUI deps
pip install openconnect-saml
# First run — will prompt for password & TOTP secret, saves to keyring
openconnect-saml --server vpn.example.com --headless --user user@example.com
# Subsequent runs use saved credentials
openconnect-saml --server vpn.example.com --headless --user user@example.com
More Options
# Without sudo (for --script-tun)
openconnect-saml --server vpn.example.com --no-sudo -- --script-tun
# With CSD hostscan wrapper
openconnect-saml --server vpn.example.com --csd-wrapper /path/to/csd-wrapper.sh
# Reset saved credentials
openconnect-saml --user user@example.com --reset-credentials
# SSL legacy mode (for older VPN appliances)
openconnect-saml --server vpn.example.com --ssl-legacy
# Custom timeout
openconnect-saml --server vpn.example.com --timeout 60
Configuration
Config file: $HOME/.config/openconnect-saml/config.toml
[default_profile]
address = "vpn.example.com"
user_group = ""
name = "My VPN"
[credentials]
username = "user@example.com"
# Optional: run a command on disconnect
on_disconnect = ""
Auto-fill rules
Custom auto-fill rules can be defined per URL pattern:
[auto_fill_rules]
"https://*" = [
{ selector = "input[type=email]", fill = "username" },
{ selector = "input[name=passwd]", fill = "password" },
{ selector = "input[id=idTxtBx_SAOTCC_OTC]", fill = "totp" },
]
Office365 "Stay signed in?" page
The default rules now auto-dismiss the "Stay signed in?" prompt. If you use custom auto_fill_rules, add these entries:
[[auto_fill_rules."https://*"]]
selector = "input[id=KmsiCheckboxField]"
action = "click"
[[auto_fill_rules."https://*"]]
selector = "input[id=idSIButton9]"
action = "click"
TOTP / Password
Credentials are stored in the system keyring. On first use, you'll be prompted for your password and optional TOTP secret.
If keyring is unavailable (e.g., headless server), passwords are kept in memory for the session.
To clear stored credentials:
openconnect-saml --user user@example.com --reset-credentials
Credits
Based on vlaci/openconnect-sso by László Vaskó, with improvements from kowyo/openconnect-lite.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openconnect_saml-0.2.0.tar.gz.
File metadata
- Download URL: openconnect_saml-0.2.0.tar.gz
- Upload date:
- Size: 49.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
977aef8ef3f00bad1f18c8ff60ed23d641112f824ea1226e1783ae3e1e57c932
|
|
| MD5 |
5b5e7f9ef01e9e2b5b6465f3b96b05e7
|
|
| BLAKE2b-256 |
7b6fcb6dca3ee5cd5d8e659370eaecd31c8311478ab8f98cbaa04bb17dfa4595
|
Provenance
The following attestation bundles were made for openconnect_saml-0.2.0.tar.gz:
Publisher:
publish.yml on mschabhuettl/openconnect-saml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openconnect_saml-0.2.0.tar.gz -
Subject digest:
977aef8ef3f00bad1f18c8ff60ed23d641112f824ea1226e1783ae3e1e57c932 - Sigstore transparency entry: 1202157860
- Sigstore integration time:
-
Permalink:
mschabhuettl/openconnect-saml@7adaa666bdbb050a1c0abfe9a859b5bfc66a756c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mschabhuettl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7adaa666bdbb050a1c0abfe9a859b5bfc66a756c -
Trigger Event:
push
-
Statement type:
File details
Details for the file openconnect_saml-0.2.0-py3-none-any.whl.
File metadata
- Download URL: openconnect_saml-0.2.0-py3-none-any.whl
- Upload date:
- Size: 40.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
818b5e99283ea58e092facf29cb10aaa963b11a63530ed5eadd5ec67d3407425
|
|
| MD5 |
3a94c9e8f413c0aafca6db832eae3c46
|
|
| BLAKE2b-256 |
e94af037343aee0801c85f608dc348aa88b2bae7eb208d4d3a283dd3c7917e7d
|
Provenance
The following attestation bundles were made for openconnect_saml-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on mschabhuettl/openconnect-saml
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openconnect_saml-0.2.0-py3-none-any.whl -
Subject digest:
818b5e99283ea58e092facf29cb10aaa963b11a63530ed5eadd5ec67d3407425 - Sigstore transparency entry: 1202157863
- Sigstore integration time:
-
Permalink:
mschabhuettl/openconnect-saml@7adaa666bdbb050a1c0abfe9a859b5bfc66a756c -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mschabhuettl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7adaa666bdbb050a1c0abfe9a859b5bfc66a756c -
Trigger Event:
push
-
Statement type: