-
Notifications
You must be signed in to change notification settings - Fork 17
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
Enhancing signature validation in SAML Response #144 #145
Enhancing signature validation in SAML Response #144 #145
Conversation
… on signature for saml response & extending tests
Thank you for your submission! We require that all contributors sign our Contributor License Agreement ("CLA") before we can accept the contribution. Read and sign the agreement Learn more about why HashiCorp requires a CLA and what the CLA includes Have you signed the CLA already but the status is still pending? Recheck it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @himran92 👋 Thx a lot for opening this PR.
A general comment:
I think gosaml2
follows the SAML spec and it is not a requirement to sign both Response and Assertions. If the Reponse is signed signature inheritance applies, and if the assertions are signed it leaves only the Response unsigned. The fields in the Response object are not really an issue if tempered. The reason is mainly because relevant fields like the InResponseTo
field is (should be) checked on the service provider. The cap/saml implementation by default requires the InResponseTo
field to be checked and explicitly prefixes the option to leave it out with Insecure
. If additional protection is required.
If we, however, still want to have the entire response to be signed, I'd suggest to make this optional. If this is forced it could lead to be incompatible with identity providers that do not provide the option to signed the full response.
I left a few comments/questions.
saml/response.go
Outdated
@@ -12,6 +12,7 @@ import ( | |||
|
|||
"github.com/jonboulle/clockwork" | |||
saml2 "github.com/russellhaering/gosaml2" | |||
"github.com/russellhaering/gosaml2/types" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the cap/saml
types instead? https://github.com/hashicorp/cap/blob/main/saml/models/core/response.go#L10-L14
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
saml/response.go
Outdated
|
||
func validateSignature(response *types.Response, op string) error { | ||
// validate child object assertions | ||
for _, assert := range response.Assertions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gosaml2 requires either the response or the assertions to be signed. It doesn't require both.
My qustion would be if we need to sign both (response and assertions)? Because signature inheritance applies (if the parent object is signed, the child object is included).
Having both objects signed makes sense in some instances where the service provider could retrieve the assertion outside of a SAML Response, but that's not the case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some further digging on this. There were many threads on the internet were customers required to have both, response
and assertions
to be signed. So there is a customer need for that.
Most SAML IDPs (e.g. Entra ID and Okta) do provide the option to sign both. However, it looks like there are indeed some IDPs that do not provide the option, such as Auth0
. This is from their application settings:
signResponse (bool): Whether or not the SAML Response should be signed. By default the SAML Assertion will be signed, but not the SAML Response. If true, SAML Response will be signed instead of SAML Assertion.
On your edit above in the description:
After few major version, the vault variable will be removed and user will always have to sign both.
Making both a requirement might lead to incompatibilities with some IDPs (like Auth0 for example). So I wonder if supporting all 3 options would be the ideal case:
- Either
Response
orAssertion
has to be signed Response
has to be signed- Both have to be signed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @hcjulz for researching this point.
I do agree that with this point it does not make sense to add just a strict validation option today and make the setting permanent in future version , rather what makes sense is to give user the control to opt for right validation.
I still would need to check with team on thoughts here as it was called out as a gap from trail of bits
. Will update shortly if team aligns with these thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes done to add separate options for all signature validation. Really appreciate for you finding the info on Auth0 as IDP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also btw the options i kept are:
- assertion is at least signed
- response is at least signed
- both are signed
Did not include the either
option as i thought user should makes a known decision relative to their IDP. Also either
is the default case today when you do not opt for any option.
Let me know if you think otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @himran92 sorry for the late response and thx a lot for addressing my comments 🙇
Sounds great. The options makes totally sense.
The default would still be either
, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am catching up after vacations :).
yes correct.
The default where neither of these options is provided is still either
.
This is given by these unit tests.
saml/response.go
Outdated
@@ -151,6 +152,14 @@ func (sp *ServiceProvider) ParseResponse( | |||
} | |||
} | |||
|
|||
if !opts.skipSignatureValidation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this fully optional? Something like opts.ResponseSigned
or opts.ResponseAndAssertionsSigned
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes agree that we need to make this optional.
I had a discussion internally and moved the PR to draft again to bring the optional field change + test the e2e flow with vault.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made all settings optional.
saml/response.go
Outdated
skipAssertionConditionValidation bool | ||
skipSignatureValidation bool | ||
assertionConsumerServiceURL string | ||
requireSignatureForResponseAndAssertion bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For requireSignatureForResponseAndAssertion
:
On the vault usage side i opted for a name EnableStrictResponseSignatureValidation
as that sounded more user friendly for a user exposed setting. The description will share the impact of the setting. Whereas kept it requireSignatureForResponseAndAssertion
on cap side thinking of it as more of an internal lib so can have any name.
Any preference of using EnableStrictResponseSignatureValidation
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong preference. Based on my comment about having all 3 options signature validation, it would just be important to think about the general naming pattern:
- Validate Response And Assertion Signatures =
EnableStrictSignatureValidation
- Validate Response Signature =
EnableResponseSignatureValidation
?
Alternatively, we could use a more explicit naming pattern:
ValidateResponseSignature
ValidateResponseAndAssertionSignatures
I'm ok either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the name of the setting.
saml/response.go
Outdated
switch { | ||
case sp == nil: | ||
return nil, fmt.Errorf("%s: missing service provider %w", op, ErrInternal) | ||
case samlResp == "": | ||
return nil, fmt.Errorf("%s: missing saml response: %w", op, ErrInvalidParameter) | ||
case requestID == "": | ||
return nil, fmt.Errorf("%s: missing request ID: %w", op, ErrInvalidParameter) | ||
case opts.skipSignatureValidation && callValidateSignature: | ||
return nil, fmt.Errorf("%s: option `skip signature validation` cannot be true with any validate signature option : %w", op, ErrInvalidParameter) | ||
case multipleSignatureOptionEnabled(opts.validateResponseAndAssertionSignatures, opts.validateResponseSignature, opts.validateAssertionSignature): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I see discussion on these options already in the discussion, so I'll defer to cap
people on the ergonomics/patterns, but if we need to check if the options are sensible, would it be better to reshape the options so that any pattern is valid? (for example, can we remove the "both" boolean and let having both validateResponse
and validateAssertion
be true imply it instead?
If there is a need/desire to have an explicit "both" option mutually exclusive with the other two, would it be worth turning it into a single field set to const
values, e.g.
const (
NoSignatureValidation = iota
ValidateAssertionSignature
ValidateResponseSignature
ValidateResponseAndAssertionSignature
)
I understand we wouldn't want to remove the InsecureSkipValidation
functions, for compatibility reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hcjulz Do you have any recommendation for above w.r.t cap?
I like Kay point to remove the both
option and ask user to set individual option to achieve signatures-validation-on-both for cap level and then similarly at Vault Plugin level too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mgaffney just randomly tagging you as you are mentioned as the repo owner.
Would you be able to assist in the review as Julian is out till end of the year, thanks
Let me know if you prefer/recommend someone else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mike was not familiar with this piece of code..
tagging @jimlambrt for review for this Pr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, removing ValidateResponseAndAssertionSignatures
makes for a better
dev experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR is updated now with only keeping two options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty for the tests!
saml/response.go
Outdated
switch { | ||
case sp == nil: | ||
return nil, fmt.Errorf("%s: missing service provider %w", op, ErrInternal) | ||
case samlResp == "": | ||
return nil, fmt.Errorf("%s: missing saml response: %w", op, ErrInvalidParameter) | ||
case requestID == "": | ||
return nil, fmt.Errorf("%s: missing request ID: %w", op, ErrInvalidParameter) | ||
case opts.skipSignatureValidation && callValidateSignature: | ||
return nil, fmt.Errorf("%s: option `skip signature validation` cannot be true with any validate signature option : %w", op, ErrInvalidParameter) | ||
case multipleSignatureOptionEnabled(opts.validateResponseAndAssertionSignatures, opts.validateResponseSignature, opts.validateAssertionSignature): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, removing ValidateResponseAndAssertionSignatures
makes for a better
dev experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty!
The SAML protocol allows signing of a Response, its Assertion(s), neither, or both. Since Assertion(s) are sub-elements of a Response, they are signed if the Response is signed. Today we are depending on the gosaml2 for signature validation. It only checks only one or the other be signed.
For enhancing security, we would like to give user the options to sign just assign response or assertion or both.
Changes in PR include:
Points before/after merging:
EDIT: there is another PR merged in cap related to
Adds an option to enable sAMAccountname logins when upndomain is set