Skip to content
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

[#174710289] Better SAML Response validation #49

Merged
merged 6 commits into from
Sep 30, 2020

Conversation

BurnedMarshal
Copy link
Contributor

Improve the SAML validation inside getPreValidateResponse:

  1. SAML Response can have only one Response element
  2. SAML Response can have only one Assertion element
  3. Any validation Error on a SAML Response must be sent to appInsights
  4. Send an event if some IDP issuer sends an unsigned Response

Some unit tests were added for the previous scenarios.

@pagopa-github-bot
Copy link
Contributor

pagopa-github-bot commented Sep 25, 2020

Warnings
⚠️ This PR changes a total of 430 LOCs, that is more than a reasonable size of 250. Consider splitting the pull request into smaller ones.

Affected stories

  • ⚙️ #174710289: Ottimizzazioni sicurezza login SPID

Generated by 🚫 dangerJS against 7e7ff8e

@codecov-commenter
Copy link

codecov-commenter commented Sep 25, 2020

Codecov Report

Merging #49 into master will increase coverage by 0.75%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #49      +/-   ##
==========================================
+ Coverage   65.05%   65.81%   +0.75%     
==========================================
  Files          11       11              
  Lines         724      740      +16     
  Branches      120      122       +2     
==========================================
+ Hits          471      487      +16     
  Misses        252      252              
  Partials        1        1              
Impacted Files Coverage Δ
src/index.ts 59.42% <ø> (ø)
src/strategy/spid.ts 36.95% <100.00%> (ø)
src/utils/saml.ts 57.75% <100.00%> (+1.76%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5da249b...7e7ff8e. Read the comment docs.

@@ -60,7 +60,7 @@ export class SpidStrategy extends SamlStrategy {
super(options, verify);
if (!options.requestIdExpirationPeriodMs) {
// 1 hour
options.requestIdExpirationPeriodMs = 3600 * 1000;
options.requestIdExpirationPeriodMs = 3600 * 1000; // TODO: Change the default value or set it into io-backend?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be better, probably keeping a sane default here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3198ea4

Comment on lines +1083 to +1089
fromOption(new Error("Assertion element must be present"))(
fromNullable(
_1.Response.getElementsByTagNameNS(
SAML_NAMESPACE.ASSERTION,
"Assertion"
).item(0)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this implied by the previous check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is only for type check. We need a cast like .item(0) as Element to avoid the fromNullable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add this as a comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the predicate to discriminate 2 different errors:

  • multiple Responses or Assertions
  • missing Response or Assertion

Fixed in a729df3

Comment on lines 1323 to 1325
const signatureOfResponseCount = (xpath.select(
"count(//*[local-name(.)='Response']/*[local-name(.)='Signature'])",
doc
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use DOM parser and remove xpath dependency instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the code will be a little more complex

const signatureOfResponseCount =
  _.Response.getElementsByTagNameNS(SAML_NAMESPACE.XMLDSIG, "Signature")
    .length -
  _.Assertion.getElementsByTagNameNS(
    SAML_NAMESPACE.XMLDSIG,
    "Signature"
  ).length;

Copy link
Contributor

@gunzip gunzip Sep 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that's right, we must chose one between the two approaches and try to not mix them.
(if I'd had to start again I'd rather have used https://www.npmjs.com/package/xml2js instead of xpath / dom :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a729df3

error => {
if (telemetryClient) {
telemetryClient.trackEvent({
name: "backend.login.prevalidate",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: "backend.login.prevalidate",
name: "spid.error.generic",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a729df3

) as unknown) as number;
if (telemetryClient && signatureOfResponseCount === 0) {
telemetryClient.trackEvent({
name: "backend.login.prevalidate",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: "backend.login.prevalidate",
name: "spid.error.signature",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a729df3

src/index.ts Outdated
getPreValidateResponse(serviceProviderConfig.strictResponseValidation),
getPreValidateResponse(
serviceProviderConfig.strictResponseValidation,
appConfig.applicationInsights
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we take the AI client from the backend? (so we will share the configuration)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the appConfig comes from the backend (provided inside the withSpid method)

Copy link
Contributor

@gunzip gunzip Sep 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about this a little bit. Since this may be a general purpose library used by other projects as well (there're no parts tied to the IO platform), it's better to do not introduce any deps to AI (which is Azure specific). That would be better to pass some track({eventName, type: "error"|"event", data: any }) method from the backend that uses its own instance of AI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a729df3

@BurnedMarshal BurnedMarshal marked this pull request as ready for review September 28, 2020 10:32
type: "INFO"
});
}
return callback(null, true, _.InResponseTo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signatureOfResponseCount === 0 is considered a successulf case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the Signature of the Response element is optional (link), we just log that we have received a SAML response without a signature.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we check response#signature - assertion#signature ? can you add some comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with a comment in 7e7ff8e

@@ -59,8 +59,8 @@ export class SpidStrategy extends SamlStrategy {
) {
super(options, verify);
if (!options.requestIdExpirationPeriodMs) {
// 1 hour
options.requestIdExpirationPeriodMs = 3600 * 1000;
// 5 minutes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after a quick chat with @pp-ps we agreed to put this to 15 minutes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e7ff8e

type: "INFO"
});
}
return callback(null, true, _.InResponseTo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we check response#signature - assertion#signature ? can you add some comments?

@gunzip gunzip merged commit 17c84ff into master Sep 30, 2020
@gunzip gunzip deleted the 174710289-better-assertion-validation branch September 30, 2020 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants