SAML Authentication Plugin for Caddy v2.
The plugin supports the following identity providers:
This plugin is an application in itself. It has a simple UI and a routine that checks the validity of the SAML assertions provided by an Identity Provider (IdP).
Importantly, SAML assertion validation checks timestamps. It is critical that the application validating the assertions maintains accurate clock. The out of sync time WILL result in failed authentications.
Each instance of the plugin requires an endpoint. Let's examine
the endpoint /saml
provided in a sample configuration file:
cat assets/conf/Caddyfile.json | jq '.apps.http.servers.srv0.routes'
The output is:
{
"handle": [
{
"handler": "authentication",
"providers": {
"saml": {
"auth_url_path": "/saml",
"jwt": {
"token_name": "JWT_TOKEN",
"token_secret": "383aca9a-1c39-4d7a-b4d8-67ba4718dd3f",
"token_issuer": "7a50e023-2c6e-4a5e-913e-23ecd0e2b940"
},
"azure": {
"idp_metadata_location": "/etc/caddy/auth/saml/idp/azure_ad_app_metadata.xml",
"idp_sign_cert_location": "/etc/caddy/auth/saml/idp/azure_ad_app_signing_cert.pem",
"tenant_id": "1b9e886b-8ff2-4378-b6c8-6771259a5f51",
"application_id": "623cae7c-e6b2-43c5-853c-2059c9b2cb58",
"application_name": "My Gatekeeper",
"entity_id": "urn:caddy:mygatekeeper",
"acs_urls": [
"https://mygatekeeper/saml",
"https://mygatekeeper.local/saml",
"https://192.168.10.10:3443/saml",
"https://localhost:3443/saml"
]
},
"ui": {
"template_location": "assets/ui/ui.template",
"allow_role_selection": false
}
}
}
}
],
"match": [
{
"path": [
"/saml*"
]
}
],
"terminal": true
}
The SAML endpoint /saml
serves a UI. This is defined by the following
snippet of the above configuration. The /saml*
ensures that anything
matching /saml
would end at the above handler.
"match": [
{
"path": [
"/saml*"
]
}
],
The UI template is Golang template. The template in
assets/ui/ui.template
is the default UI served by the plugin.
template_location
: The location of a custom UI templateallow_role_selection
: Enables or disables the ability to select a role after successful validation of a SAML assertion.
"ui": {
"template_location": "assets/ui/ui.template",
"allow_role_selection": false
}
After a successful validation of a SAML assertion, the plugin issues a JWT token.
token_name
: The name of the issues token (default: 'jwt_token`)token_secret
: The token signing secret (symmetric, i.e. HMAC algo)token_key
: (TODO: not supported) The token signing public/private key pair (asymmetric, i.e. RSA or ECDSA algo).token_issuer
: The value ofiss
field inserted by the plugin.
"jwt": {
"token_name": "JWT_TOKEN",
"token_secret": "383aca9a-1c39-4d7a-b4d8-67ba4718dd3f",
"token_issuer": "7a50e023-2c6e-4a5e-913e-23ecd0e2b940"
},
The issued token will be passed to a requester via:
- The cookie specified in
token_name
key - The
Authorization
header viaBearer
directive
First, fetch the Azure IdP plugin configuration:
cat assets/conf/Caddyfile.json | jq '.apps.http.servers.srv0.routes[0].handle[0].providers.saml.azure'
The Azure configuration:
{
"idp_metadata_location": "assets/idp/azure_ad_app_metadata.xml",
"idp_sign_cert_location": "assets/idp/azure_ad_app_signing_cert.pem",
"tenant_id": "1b9e886b-8ff2-4378-b6c8-6771259a5f51",
"application_id": "623cae7c-e6b2-43c5-853c-2059c9b2cb58",
"application_name": "My Gatekeeper",
"entity_id": "urn:caddy:mygatekeeper",
"acs_urls": [
"https://mygatekeeper/saml",
"https://mygatekeeper.local/saml",
"https://192.168.10.10:3443/saml",
"https://localhost:3443/saml"
]
}
The plugin supports the following parameters for Azure Active Directory (Office 365) applications:
Parameter Name | Description |
---|---|
idp_metadata_location |
The url or path to Azure IdP Metadata |
idp_sign_cert_location |
The path to Azure IdP Signing Certificate |
tenant_id |
Azure Tenant ID |
application_id |
Azure Application ID |
application_name |
Azure Application Name |
entity_id |
Azure Application Identifier (Entity ID) |
acs_urls |
One of more Assertion Consumer Service URLs |
The acs_urls
must list all URLs the users of the application
can reach it at.
In Azure AD, you will have an application, e.g. "My Gatekeeper".
The application is a Caddy web server running on port 3443 on
localhost
. This example meant to emphasize that the authorization
is asynchronious. That is when a user clicks on "My Gatekeeper" icon
in Office 365, the browser takes the user to a sign in page
at URL https://localhost:3443/saml
.
The Application Identifiers are as follows:
- Application (client) ID:
623cae7c-e6b2-43c5-853c-2059c9b2cb58
- Directory (tenant) ID:
1b9e886b-8ff2-4378-b6c8-6771259a5f51
- Object ID:
515d2e8b-7548-413f-abee-a23ece1ea576
The "Branding" page configures "Home Page URL".
For demostration purposes, we will create the following "Roles" in the application:
Azure Role Name | Role Name in SAML Assertion |
---|---|
Viewer | AzureAD_Viewer |
Editor | AzureAD_Editor |
Administrator | AzureAD_Administrator |
Use "Manifest" tab to add roles in the manifest via appRoles
key:
{
"allowedMemberTypes": [
"User"
],
"description": "Administrator",
"displayName": "Administrator",
"id": "91287df2-7028-4d5f-b5ae-5d489ba217dd",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "AzureAD_Administrator"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Editor",
"displayName": "Editor",
"id": "d482d827-1757-4f60-9bea-021c10037674",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "AzureAD_Editor"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Viewer",
"displayName": "Viewer",
"id": "c69f7abd-0a88-401e-b515-92d74b6fff2f",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "AzureAD_Viewer"
}
After, we added the roles, we could assign any of the roles to a user:
The app is now available to the provisioned users in Office 365:
Go to "Enterprise Application" and browse to "My Gatekeeper" application.
There, click "Single Sign-On" and select "SAML" as the authentication method.
Next, in the "Set up Single Sign-On with SAML", provide the following "Basic SAML Configuration":
- Identifier (Entity ID):
urn:caddy:mygatekeeper
- Reply URL (Assertion Consumer Service URL):
https://localhost:3443/saml
Under "User Attributes & Claims", add the following claims to the list of default claims:
Namespace | Claim name | Value |
---|---|---|
http://claims.contoso.com/SAML/Attributes |
RoleSessionName |
user.userprincipalname |
http://claims.contoso.com/SAML/Attributes |
Role |
user.assignedroles |
http://claims.contoso.com/SAML/Attributes |
MaxSessionDuration |
3600 |
Next, record the following:
- App Federation Metadata Url
- Login URL
Further, download:
- Federation Metadata XML
- Certificate (Base64 and Raw)
The following command downloads IdP metadata file for Azure AD Tenant with
ID 1b9e886b-8ff2-4378-b6c8-6771259a5f51
. Please note the xmllint
utility
is a part of libxml2
library.
curl -s -L -o /tmp/federationmetadata.xml https://login.microsoftonline.com/1b9e886b-8ff2-4378-b6c8-6771259a5f51/federationmetadata/2007-06/federationmetadata.xml
sudo mkdir -p /etc/caddy/auth/saml/idp/
cat /tmp/federationmetadata.xml | xmllint --format - | sudo tee /etc/caddy/auth/saml/idp/azure_ad_app_metadata.xml
The /etc/caddy/auth/saml/idp/azure_ad_app_metadata.xml
contains IdP metadata.
This file contains the data necessary to verify the SAML claims received by this
service and signed by Azure AD. The idp_metadata
argument is being used to
pass the location of IdP metadata.
Next, download the "Certificate (Base64)" and store it in
/etc/caddy/auth/saml/idp/azure_ad_app_signing_cert.pem
.
First option is a login button on the login server web page. Once Azure AD has
been enabled, the /saml
page will have "Sign in with Office 365" button
Second option is Office 365 applications. When a user click on the application's icon in Office 365, the user gets redirected to the web server by Office 365.
The URL is https://localhost:3443/saml
.
The below are the headers of the redirected POST
request that the user's
browser makes upon clicking "My Gatekeeper" application:
Method: POST
URL: /saml
Protocol: HTTP/2.0
Host: localhost:3443
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru;q=0.8
Cache-Control: max-age=0
Content-Length: 7561
Content-Type: application/x-www-form-urlencoded
Origin: https://login.microsoftonline.com
Referer: https://login.microsoftonline.com/
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Upgrade-Insecure-Requests: 1
The above redirect contains login.microsoftonline.com
in the request's
Referer
header. It is the trigger to perform SAML-based authorization.
TODO.