-
-
Notifications
You must be signed in to change notification settings - Fork 945
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
sources/saml: Basic support for EncryptedAssertion element. #10099
sources/saml: Basic support for EncryptedAssertion element. #10099
Conversation
✅ Deploy Preview for authentik-storybook ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for authentik-docs canceled.
|
This PR attempts to solve: #9172 |
@BeryJu: What can we do to help this PR? Its currently a blocker in our project, not being able to communicate with The Danish Agency for Digitization's Municipal Access Management service. |
Sorry I'm currently travelling and haven't been able to go through a bunch of PRs, I'll review this later this week/early next week when I get back home. |
Thankyou for clearing this up! Safe travels! |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #10099 +/- ##
==========================================
- Coverage 92.63% 92.62% -0.01%
==========================================
Files 734 734
Lines 35980 36039 +59
==========================================
+ Hits 33329 33381 +52
- Misses 2651 2658 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
The lint check fails due to the Exception mentioned above.
Maybe I can fix this by simply adding the # noqa comment to the line? Any thoughts? |
I can't push to the branch on the forked repo, but here's the diff for adding the option to the UI: diff --git a/authentik/sources/saml/api/source.py b/authentik/sources/saml/api/source.py
index a3f0e9bd4..1af2a8795 100644
--- a/authentik/sources/saml/api/source.py
+++ b/authentik/sources/saml/api/source.py
@@ -32,6 +32,7 @@ class SAMLSourceSerializer(SourceSerializer):
"digest_algorithm",
"signature_algorithm",
"temporary_user_delete_after",
+ "request_encrypted_assertions",
]
diff --git a/authentik/sources/saml/processors/metadata.py b/authentik/sources/saml/processors/metadata.py
index c12a55556..0820a3159 100644
--- a/authentik/sources/saml/processors/metadata.py
+++ b/authentik/sources/saml/processors/metadata.py
@@ -46,7 +46,7 @@ class MetadataProcessor:
return key_descriptor
return None
- def get_encryption_key_descriptor(self) -> Optional[Element]: # noqa: UP007
+ def get_encryption_key_descriptor(self) -> Optional[Element]: # noqa: UP007
"""Get Encryption KeyDescriptor, if encrypted assertion is requested"""
if self.source.request_encrypted_assertions:
key_descriptor = Element(f"{{{NS_SAML_METADATA}}}KeyDescriptor")
diff --git a/authentik/sources/saml/processors/response.py b/authentik/sources/saml/processors/response.py
index 805f59ee4..5434a2c98 100644
--- a/authentik/sources/saml/processors/response.py
+++ b/authentik/sources/saml/processors/response.py
@@ -83,7 +83,6 @@ class ResponseProcessor:
def _decrypt_response(self):
"""Decrypt SAMLResponse EncryptedAssertion Element"""
-
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_memory(
self._source.signing_kp.key_data,
@@ -93,9 +92,7 @@ class ResponseProcessor:
manager.add_key(key)
encryption_context = xmlsec.EncryptionContext(manager)
- encrypted_assertion = self._root.find(
- ".//{urn:oasis:names:tc:SAML:2.0:assertion}EncryptedAssertion"
- )
+ encrypted_assertion = self._root.find(f".//{{{NS_SAML_ASSERTION}}}EncryptedAssertion")
encrypted_data = xmlsec.tree.find_child(
encrypted_assertion, "EncryptedData", xmlsec.constants.EncNs
)
diff --git a/blueprints/schema.json b/blueprints/schema.json
index d32ba34e5..b321222ba 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -5106,6 +5106,11 @@
"minLength": 1,
"title": "Delete temporary users after",
"description": "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
+ },
+ "request_encrypted_assertions": {
+ "type": "boolean",
+ "title": "Request encrypted assertions",
+ "description": "When enabled, the SAML IdP will encrypt the assertion element using the public key of the SP signing keypair. The SAMLResponse will contain an EncryptedAssertion element, which will be decrypted by the private key of the service provider."
}
},
"required": []
diff --git a/schema.yml b/schema.yml
index 822318ad1..05fcb7384 100644
--- a/schema.yml
+++ b/schema.yml
@@ -43429,6 +43429,12 @@ components:
description: 'Time offset when temporary users should be deleted. This only
applies if your IDP uses the NameID Format ''transient'', and the user
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
+ request_encrypted_assertions:
+ type: boolean
+ description: When enabled, the SAML IdP will encrypt the assertion element
+ using the public key of the SP signing keypair. The SAMLResponse will
+ contain an EncryptedAssertion element, which will be decrypted by the
+ private key of the service provider.
PatchedSCIMMappingRequest:
type: object
description: SCIMMapping Serializer
@@ -46114,6 +46120,12 @@ components:
description: 'Time offset when temporary users should be deleted. This only
applies if your IDP uses the NameID Format ''transient'', and the user
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
+ request_encrypted_assertions:
+ type: boolean
+ description: When enabled, the SAML IdP will encrypt the assertion element
+ using the public key of the SP signing keypair. The SAMLResponse will
+ contain an EncryptedAssertion element, which will be decrypted by the
+ private key of the service provider.
required:
- component
- icon
@@ -46217,6 +46229,12 @@ components:
description: 'Time offset when temporary users should be deleted. This only
applies if your IDP uses the NameID Format ''transient'', and the user
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
+ request_encrypted_assertions:
+ type: boolean
+ description: When enabled, the SAML IdP will encrypt the assertion element
+ using the public key of the SP signing keypair. The SAMLResponse will
+ contain an EncryptedAssertion element, which will be decrypted by the
+ private key of the service provider.
required:
- name
- pre_authentication_flow
diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts
index c969411fb..945a74bfd 100644
--- a/web/src/admin/sources/saml/SAMLSourceForm.ts
+++ b/web/src/admin/sources/saml/SAMLSourceForm.ts
@@ -449,6 +449,25 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
>
</ak-radio>
</ak-form-element-horizontal>
+ <ak-form-element-horizontal name="requestEncryptedAssertions">
+ <label class="pf-c-switch">
+ <input
+ class="pf-c-switch__input"
+ type="checkbox"
+ ?checked=${first(this.instance?.requestEncryptedAssertions, true)}
+ />
+ <span class="pf-c-switch__toggle">
+ <span class="pf-c-switch__toggle-icon">
+ <i class="fas fa-check" aria-hidden="true"></i>
+ </span>
+ </span>
+ <span class="pf-c-switch__label"
+ >${msg(
+ "Request Encrypted assertions from Identity provider.",
+ )}</span
+ >
+ </label>
+ </ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
One thing I would probably change about this is explicitly throw an error if Another thing, shouldn't the initial |
@BeryJu: You have been added as outside collaborator with write permissions to the fork... |
Thank you very much for the UI additions - that will greatly simplify the process of setting up this type of SAML source.
Definitely a good addition. I'm fine with renaming the model field to reflect the requirement if need be.
In my understanding the initial AuthnRequest to the IdP is typically not encrypted, but I'll need to check if the IdP supports decryption AuthnRequests from service providers. The encryption layer is mainly to protect the sensitive information in the Assertion element. (Please correct me if I'm wrong though!) |
One thing that I noticed in the PR is that there's no explicit error handling, I'm assuming this would throw some xmlsec Error when the encryption is missing/invalid?
That makes sense. One other thing I'd like to see for this is some added tests (we can also add those in this PR) |
Something like this? def _decrypt_response(self):
"""Decrypt SAMLResponse EncryptedAssertion Element"""
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_memory(
self._source.signing_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
)
manager.add_key(key)
encryption_context = xmlsec.EncryptionContext(manager)
try:
encrypted_assertion = self._root.find(f".//{{{NS_SAML_ASSERTION}}}EncryptedAssertion")
except:
raise MissingEncryptedAssertion()
encrypted_data = xmlsec.tree.find_child(
encrypted_assertion, "EncryptedData", xmlsec.constants.EncNs
)
try:
decrypted_assertion = encryption_context.decrypt(encrypted_data)
except:
raise DecryptionError():
index_of = self._root.index(encrypted_assertion)
self._root.remove(encrypted_assertion)
self._root.insert(
index_of,
decrypted_assertion,
)
LOGGER.debug("Successfully decrypted EncryptedAssertion element") With the following added to source/saml/exceptions.py ... class MissingEncryptedAssertion(SAMLException):
"""Exception raised when SAMLResponse does not contain EncryptedAssertion element"""
class DecryptionError(SAMLException):
"""Exception raised when decryption of SAMLResponse could not be performed""" If this is acceptable I can commit&push, but please let me know if it's too simple. |
yeah pretty much what you said, only thing is we usually try to prevent bare excepts (i've pushed some stuff) one other thing I'm noticing while testing this is that I think it might be a good option to add another certificate setting to the source to configure the signature and encryption certificate separately, as I'm testing against keycloak which by default generates two different certificates for each |
This would be the right way to do it, I only initially planned to re-use the signing keypair for simplicity, but It would be best to have the choice between:
EDIT: A simpler solution could maybe be just to replicate what is done for the signing certificate? EDITEDIT: I realize this changes the implementation we've slowly been building now, but maybe it's better this way? Kind Regards, |
Sorry didnt see your edits, yeah I think what you suggested in the first Edit makes most sense |
Roger that, will see if I can't figure out how to replicate it. |
94c86ea
to
6915421
Compare
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
…e it's not in the schema Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
7fd8c3d
to
c438557
Compare
* main: web/admin: refactor property mappings forms (#10810) web: bump API Client version (#10811) sources/saml: Basic support for EncryptedAssertion element. (#10099) web: bump API Client version (#10809) sources: add property mappings for all oauth and saml sources (#8771) web: bump API Client version (#10808) stages/authenticator: add created, last_updated and last_used metadata (#10636) providers/proxy: avoid erroring on logout with session_id is None (#9119) core: bump google-api-python-client from 2.139.0 to 2.140.0 (#10802) core: bump pyyaml from 6.0.1 to 6.0.2 (#10803) core: bump django from 5.0.7 to 5.0.8 (#10804) core: bump goauthentik.io/api/v3 from 3.2024063.1 to 3.2024063.2 (#10805) web: bump @sentry/browser from 8.23.0 to 8.24.0 in /web in the sentry group across 1 directory (#10806) web: bump the wdio group across 2 directories with 2 updates (#10807)
* main: (25 commits) website/docs: add link from Install docs to Enterprise docs (#10827) website/docs: new upgrade page (#10742) stages/authenticator: actually update last_used (#10813) sources/ldap: Add enabled filter for ldap_password_validate signal (#10823) web: bump API Client version (#10821) sources/plex: add property mappings (#10772) core: bump goauthentik.io/api/v3 from 3.2024063.2 to 3.2024063.5 (#10817) web: bump the wdio group across 2 directories with 4 updates (#10818) web: bump chromedriver from 127.0.1 to 127.0.2 in /tests/wdio (#10819) web: update to ESLint 9 (#10812) website/docs: add source property mappings, rework provider property mappings (#10652) web/admin: refactor property mappings forms (#10810) web: bump API Client version (#10811) sources/saml: Basic support for EncryptedAssertion element. (#10099) web: bump API Client version (#10809) sources: add property mappings for all oauth and saml sources (#8771) web: bump API Client version (#10808) stages/authenticator: add created, last_updated and last_used metadata (#10636) providers/proxy: avoid erroring on logout with session_id is None (#9119) core: bump google-api-python-client from 2.139.0 to 2.140.0 (#10802) ...
Details
This PR aims to be an initial step in expanding Authentik's SAML2 Federation capabilities to adhere more fully to the entire SAML2 specification. It does so by providing the option to use an existing signing key-pair for requesting the Assertion element to be encrypted by the SAML IdP. I've attempted to make it such that existing SAML federations should not be disturbed by this change. Currently, the only way to enable that the Assertion should be encrypted, is by updating the "request_encrypted_assertions" model field to be True, instead of the default "False" value. I'm not sure how to update Authentik's UI but I'm hoping this is a small fix.
closes #7999
If the field is set to "True" for a SAMLSource, the metadata will be updated with an encryption key descriptor, as well as a new attribute in the SPSSO descriptor (WantAssertionsEncrypted="True"). This will inform the IdP that assertion should be encrypted. When the SAMLResponse is recieved, the _decrypt_response() method should locate and decrypt the EncryptedData in the EncryptedAssertion element and finally replacing the EncryptedAssertion with the decrypted Assertion. In this way, once the flow proceeds the SAMLResponse will appear as if it was never encrypted to begin with.
We have tested this decryption flow against the The Danish Agency for Digitization's Municipal Access Management service, that requires the service provider to request encrypted assertions.
Checklist
ak test authentik/sources/saml
)make lint-fix
) (EXCEPTION: In the metadata processor, the signing key descriptor method does not use the Union type, even though make lint-fix wants it to. I've chosen to adopt the same return type for the encryption key descriptor.)If applicable
make website
)