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

[#171703313] SAML certs and Startup IDP metadata from ENV #595

Merged
merged 7 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ Those are all Environment variables needed by the application:
| SAML_CALLBACK_URL | The absolute URL of the assertion consumer service endpoint | string |
| SAML_ISSUER | The issuer id for this Service Provider | string |
| SAML_ATTRIBUTE_CONSUMING_SERVICE_INDEX | The index in the attribute consumer list | int |
| SAML_KEY | Private Key used by SAML protocol | string |
| SAML_CERT | Certificate used by SAML protocol | string |
| PRE_SHARED_KEY | The key shared with the API backend to authenticate the webhook notifications | string |
| ALLOW_NOTIFY_IP_SOURCE_RANGE | The range in CIDR form of allowed IPs for the webhook notifications | string |
| AZURE_NH_HUB_NAME | The hub name configured in the Azure Notification HUB | string |
Expand All @@ -153,6 +155,7 @@ Those are all Environment variables needed by the application:
| PAGOPA_API_URL_TEST | The url for the PagoPA api endpoints in test mode | string |
| PAGOPA_BASE_PATH | The root path for the PagoPA endpoints | string |
| SPID_AUTOLOGIN | The user used in the autologin feature, omit this to disable autologin | string |
| STARTUP_IDPS_METADATA | Stringified JSON containing idps metadata `Record<string, string>` | string |
| CIE_METADATA_URL | Url to download CIE metadata from | string |
| IDP_METADATA_URL | Url to download IDP metadata from | string |
| IDP_METADATA_REFRESH_INTERVAL_SECONDS | The number of seconds when the IDPs Metadata are refreshed | int |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"homepage": "https://github.com/pagopa/io-backend#readme",
"dependencies": {
"@pagopa/io-spid-commons": "^2.7.0",
"@pagopa/io-spid-commons": "^2.9.0",
"apicache": "^1.4.0",
"applicationinsights": "^1.4.2",
"azure-sb": "^0.10.6",
Expand Down
23 changes: 16 additions & 7 deletions src/__tests__/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as spid from "@pagopa/io-spid-commons";
import * as spid from "@pagopa/io-spid-commons/dist/utils/metadata";
import { Express } from "express";
import { isRight } from "fp-ts/lib/Either";
import { Task } from "fp-ts/lib/Task";
Expand Down Expand Up @@ -124,24 +124,33 @@ describe("Success app start", () => {
});

describe("Failure app start", () => {
let app: Express | undefined;
afterAll(() => {
jest.restoreAllMocks();
app?.emit("server:stop");
});

it("Close app if download IDP metadata fails on startup", async () => {
// Override return value of generateSpidStrategy with a rejected promise.
jest.spyOn(spid, "withSpid").mockImplementation(() => {
return TE.left(
new Task(async () => new Error("Error download metadata"))
);
});
const mockFetchIdpsMetadata = jest
.spyOn(spid, "fetchIdpsMetadata")
.mockImplementation(() => {
return TE.left(
new Task(async () => new Error("Error download metadata"))
);
});
const mockExit = jest
.spyOn(process, "exit")
.mockImplementation(() => true as never);
await appModule.newApp(
app = await appModule.newApp(
NodeEnvironmentEnum.PRODUCTION,
aValidCIDR,
aValidCIDR,
"",
"/api/v1",
"/pagopa/api/v1"
);
expect(mockFetchIdpsMetadata).toBeCalledTimes(3);
expect(mockExit).toBeCalledWith(1);
});
});
22 changes: 15 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ import {
} from "./utils/package";

import { withSpid } from "@pagopa/io-spid-commons";
import { toError } from "fp-ts/lib/Either";
import { tryCatch } from "fp-ts/lib/TaskEither";
import { getSpidStrategyOption } from "@pagopa/io-spid-commons/dist/utils/middleware";
import { isEmpty, StrMap } from "fp-ts/lib/StrMap";
import { Task } from "fp-ts/lib/Task";
import { VersionPerPlatform } from "../generated/public/VersionPerPlatform";
import UserDataProcessingController from "./controllers/userDataProcessingController";
import MessagesService from "./services/messagesService";
Expand Down Expand Up @@ -170,7 +171,7 @@ export function newApp(
// Setup routes
//

return tryCatch(async () => {
return new Task(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

we must ensure that this code never throws

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are all static methods inside the Task that should never throw. I think that tryCatch before and new Task now, are only for chaining purposes with withSpid.

// Ceate the Token Service
const TOKEN_SERVICE = new TokenService();

Expand Down Expand Up @@ -221,7 +222,7 @@ export function newApp(
PROFILE_SERVICE
);
return { app, acsController };
}, toError)
})
.chain(_ =>
withSpid(
appConfig,
Expand All @@ -238,9 +239,16 @@ export function newApp(
_.app.on("server:stop", () => clearInterval(idpMetadataRefreshTimer));
return _.app;
})
.getOrElseL(err => {
log.error("Fatal error during Express initialization: %s", err);
process.exit(1);
.map(_ => {
const spidStrategyOption = getSpidStrategyOption(_);
// Process ends in case no IDP is configured
if (isEmpty(new StrMap(spidStrategyOption?.idp || {}))) {
log.error(
"Fatal error during application start. Cannot get IDPs metadata."
);
process.exit(1);
}
return _;
})
.run();
}
Expand Down
38 changes: 29 additions & 9 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
* Defines services and register them to the Service Container.
*/
import * as dotenv from "dotenv";
import { isLeft } from "fp-ts/lib/Either";
import { isLeft, parseJSON, toError } from "fp-ts/lib/Either";
import { fromNullable, isSome } from "fp-ts/lib/Option";
import {
getNodeEnvironmentFromProcessEnv,
NodeEnvironmentEnum
} from "italia-ts-commons/lib/environment";
import { ReadableReporter } from "italia-ts-commons/lib/reporters";
import {
errorsToReadableMessages,
ReadableReporter
} from "italia-ts-commons/lib/reporters";
import { CIDR } from "italia-ts-commons/lib/strings";
import { UrlFromString } from "italia-ts-commons/lib/url";

Expand All @@ -28,6 +31,7 @@ import {
} from "@pagopa/io-spid-commons";

import RedisSessionStorage from "./services/redisSessionStorage";
import { STRINGS_RECORD } from "./types/commons";
import {
createClusterRedisClient,
createSimpleRedisClient
Expand Down Expand Up @@ -57,18 +61,19 @@ export const CACHE_MAX_AGE_SECONDS: number = parseInt(

// Private key used in SAML authentication to a SPID IDP.
const samlKey = () => {
return readFile(
process.env.SAML_KEY_PATH || "./certs/key.pem",
"SAML private key"
return fromNullable(process.env.SAML_KEY).getOrElse(
readFile(process.env.SAML_KEY_PATH || "./certs/key.pem", "SAML private key")
);
};
export const SAML_KEY = samlKey();

// Public certificate used in SAML authentication to a SPID IDP.
const samlCert = () => {
return readFile(
process.env.SAML_CERT_PATH || "./certs/cert.pem",
"SAML certificate"
return fromNullable(process.env.SAML_CERT).getOrElse(
readFile(
process.env.SAML_CERT_PATH || "./certs/cert.pem",
"SAML certificate"
)
);
};

Expand Down Expand Up @@ -104,6 +109,20 @@ const SPID_TESTENV_URL =
export const IDP_METADATA_URL = getRequiredENVVar("IDP_METADATA_URL");
const CIE_METADATA_URL = getRequiredENVVar("CIE_METADATA_URL");

export const STARTUP_IDPS_METADATA:
| Record<string, string>
| undefined = fromNullable(process.env.STARTUP_IDPS_METADATA)
.map(_ =>
parseJSON(_, toError)
.chain<Record<string, string> | undefined>(_1 =>
STRINGS_RECORD.decode(_1).mapLeft(
err => new Error(errorsToReadableMessages(err).join(" / "))
)
)
.getOrElse(undefined)
)
.getOrElse(undefined);

export const CLIENT_ERROR_REDIRECTION_URL =
process.env.CLIENT_ERROR_REDIRECTION_URL || "/error.html";

Expand All @@ -116,7 +135,8 @@ export const appConfig: IApplicationConfig = {
clientLoginRedirectionUrl: CLIENT_REDIRECTION_URL,
loginPath: "/login",
metadataPath: "/metadata",
sloPath: "/slo"
sloPath: "/slo",
startupIdpsMetadata: STARTUP_IDPS_METADATA
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 please document this var into th readme ?

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 53626f2

};

const maybeSpidValidatorUrlOption = fromNullable(
Expand Down
3 changes: 3 additions & 0 deletions src/types/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export const SuccessResponse = t.interface({
});

export type SuccessResponse = t.TypeOf<typeof SuccessResponse>;

export const STRINGS_RECORD = t.record(t.string, t.string);
export type STRINGS_RECORD = t.TypeOf<typeof STRINGS_RECORD>;
Loading