diff --git a/.vscode/launch.json b/.vscode/launch.json index 122e22b80..989ebc7f3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,7 @@ { "type": "node", "request": "launch", - "name": "App", + "name": "Server", "program": "${workspaceFolder}\\dist\\index.js", "autoAttachChildProcesses": true, "console": "internalConsole", @@ -63,12 +63,12 @@ ], "compounds": [ { - "name": "App&Docs", - "configurations": ["App", "Docs", "Launch Docs"] + "name": "Server&Docs", + "configurations": ["Server", "Docs", "Launch Docs"] }, { - "name": "App&Client", - "configurations": ["App", "Client", "Launch Client"] + "name": "Server&Client", + "configurations": ["Server", "Client", "Launch Client"] } ] } diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index fdad7e566..dfaed18b6 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -93,6 +93,7 @@ module.exports = { 'config/workerProcessCount', 'config/middleware', 'config/oidc', + 'config/certificates', ], }, { diff --git a/docs/docs/api/administrator.md b/docs/docs/api/administrator.md index f98ff2e9f..c589892de 100644 --- a/docs/docs/api/administrator.md +++ b/docs/docs/api/administrator.md @@ -4,10 +4,12 @@ permalink: /docs/api-administrator/ # Administrator -The administrator API provides knowledge factor authentication to identify admin request by access token (aka API token in other literature), as opposed to possession factor admin ip list. Because knowledge factor authentication is vulnerable to brute-force attack, administrator API is less favorable than admin ip list. Administrator API is advised to be used only when obtaining client's ip or ip range is infeasible. +The administrator API provides knowledge factor authentication to identify admin request by access token (aka API token in other literatures) associated with a registered administrator maintained in _NotifyBC_ database. Because knowledge factor authentication is vulnerable to brute-force attack, administrator API based access token authentication is less favorable than admin ip list, client certificate, or OIDC authentication. -::: tip Example Use Case +::: warning Avoid Administrator API Administrator API was created to circumvent an OpenShift limitation - the source ip of a request initiated from an OpenShift pod cannot be exclusively allocated to the pod's project, rather it has to be shared by all OpenShift projects. Therefore it's difficult to impose granular access control based on source ip. + +With the introduction client certificate in v2.4.0, most use cases, if not all, that need Administrator API including the OpenShift use case mentioned above can be addressed by client certificate. Therefore only use Administrator API sparingly as last resort. ::: To enable access token authentication, diff --git a/docs/docs/config/certificates.md b/docs/docs/config/certificates.md new file mode 100644 index 000000000..d47b3d7f5 --- /dev/null +++ b/docs/docs/config/certificates.md @@ -0,0 +1,99 @@ +--- +permalink: /docs/config-certificates/ +--- + +# TLS Certificates + +_NotifyBC_ supports HTTPS TLS to achieve end-to-end encryption. In addition, both server and client can be authenticated using certificates. + +To enable HTTPS for server authentication only, you need to create two files + +- _server/certs/key.pem_ - a PEM encoded private key +- _server/certs/cert.pem_ - a PEM encoded X.509 certificate chain + +::: tip Use ConfigMaps on OpenShift +Create _key.pem_ and _cert.pem_ as items in ConfigMap _notify-bc_, then mount the items under _/opt/app-root/src/server/certs_ similar to how _config.local.js_ and _middleware.local.js_ are implemented. +::: + +For self-signed certificate, run + +```sh +openssl req -x509 -newkey rsa:4096 -keyout server/certs/key.pem -out server/certs/cert.pem -nodes -days 365 -subj "/CN=NotifyBC" +``` + +to generate both files in one shot. + +To create a CSR from the private key generated above, run + +```sh +openssl req -new -key server/certs/key.pem -out server/certs/csr.pem +``` + +Then bring your CSR to your CA to sign. Replace _server/certs/cert.pem_ with the cert signed by CA. If your CA also supplied intermediate certificate in PEM encoded format, say in a file called _intermediate.pem_, append all of the content of _intermediate.pem_ to file _server/certs/cert.pem_. + +::: warning Make a copy of self-signed server/certs/cert.pem +If you want to enable [client certificate authentication](#client-certificate-authentication) documented below, make sure to copy self-signed _server/certs/cert.pem_ to _server/certs/ca.pem_ before replacing the file with the cert signed by CA. +You need the self-signed _server/certs/cert.pem_ to sign client CSR. +::: + +In case you created _server/certs/key.pem_ and _server/certs/cert.pem_ but don't want to enable HTTPS, create following config in _src/config.local.js_ + +```js +module.exports = { + tls: { + enabled: false, + }, +}; +``` + +::: warning Update URL configs after enabling HTTPS +Make sure to update the protocol of following URL configs after enabling HTTPS + +- [httpHost](../config/httpHost.md) +- [internalHttpHost](../config/internalHttpHost.md) + +When _NotifyBC_ is hosted on OpenShift, also update on all deployed environments + +- notify-bc-app [livenessProbe httpGet scheme](https://github.com/bcgov/NotifyBC/blob/d389d260ce29beb9631dd73867870fa842fb6181/.openshift-templates/notify-bc.yml#L81) +- notify-bc-app [readinessProbe httpGet scheme](https://github.com/bcgov/NotifyBC/blob/d389d260ce29beb9631dd73867870fa842fb6181/.openshift-templates/notify-bc.yml#L96) +- notify-bc-cron [livenessProbe httpGet scheme](https://github.com/bcgov/NotifyBC/blob/d389d260ce29beb9631dd73867870fa842fb6181/.openshift-templates/notify-bc.yml#L169) +- notify-bc-cron [readinessProbe httpGet scheme](https://github.com/bcgov/NotifyBC/blob/d389d260ce29beb9631dd73867870fa842fb6181/.openshift-templates/notify-bc.yml#L182) + ::: + +## Client certificate authentication + +After enabling HTTPS, you can further configure such that a client request can be authenticated using client certificate. To do so, copy self-signed _server/certs/cert.pem_ to _server/certs/ca.pem_. You will use your server key to sign client certificate CSR, and advertise _server/certs/ca.pem_ as acceptable CAs during TLS handshake. + +Assuming a client's CSR file is named _myClientApp_csr.pem_, to sign the CSR + +```sh +openssl x509 -req -in myClientApp_csr.pem -CA server/certs/ca.pem -CAkey server/certs/key.pem -out myClientApp_cert.pem -set_serial 01 -days 365 +``` + +Then give _myClientApp_cert.pem_ to the client. How a client app supplies the client certificate when making a request to _NotifyBC_ varies by client type. Usually the client first needs to bundle the signed client cert and client key into PKCS#12 format + +```sh +openssl pkcs12 -export -clcerts -in myClientApp_cert.pem -inkey myClientApp_key.pem -out myClientApp.p12 +``` + +To use _myClientApp.p12_, for cURL, + +```sh +curl --insecure --cert myClientApp.p12 --cert-type p12 https://localhost:3000/api/administrators/whoami +``` + +For browsers, check browser's instruction how to import _myClientApp.p12_. When browser accessing _NotifyBC_ API endpoints such as _https://localhost:3000/api/administrators/whoami_, the browser will prompt to choose from a list certificates that are signed by the server certificate. + +In case you created _server/certs/ca.pem_ but don't want to enable client certificate authentication, create following config in _src/config.local.js_ + +```js +module.exports = { + tls: { + clientCertificateEnabled: false, + }, +}; +``` + +::: warning TLS termination has to be passthrough +For client certification authentication to work, TLS termination of all reverse proxies has to be set to passthrough rather than offload and reload. This means, for example, when _NotifyBC_ is hosted on OpenShift, router [tls termination](https://github.com/bcgov/NotifyBC/blob/d389d260ce29beb9631dd73867870fa842fb6181/.openshift-templates/notify-bc.yml#L319) has to be changed from _edge_ to _passthrough_. +::: diff --git a/docs/docs/config/internalHttpHost.md b/docs/docs/config/internalHttpHost.md index a3a1c80b7..b9763e22b 100644 --- a/docs/docs/config/internalHttpHost.md +++ b/docs/docs/config/internalHttpHost.md @@ -17,5 +17,5 @@ then the HTTP request will be sent to the configured host. An internal request c All internal requests are supposed to be admin requests. The purpose of _internalHttpHost_ is to facilitate identifying the internal server ip as admin ip. ::: tip OpenShift Use Case -The OpenShift deployment script has set internalHttpHost to service url http://notify-bc:3000 in file config.production.json so you shouldn't re-define it in /src/config.local.js. The source ip in such case would be in a private OpenShift ip range. You should add this private ip range to admin ip list. The private ip range varies from OpenShift installation. In BCGov's cluster, it starts with octet 172. +The OpenShift deployment script sets internalHttpHost to service url http://notify-bc:3000 in config map. The source ip in such case would be in a private OpenShift ip range. You should add this private ip range to admin ip list. The private ip range varies from OpenShift installation. In BCGov's OCP4 cluster, it starts with octet 10. ::: diff --git a/docs/docs/getting-started/overview.md b/docs/docs/getting-started/overview.md index a96c4ce12..d38d112d5 100644 --- a/docs/docs/getting-started/overview.md +++ b/docs/docs/getting-started/overview.md @@ -75,12 +75,19 @@ _NotifyBC_, designed to be a microservice, doesn't use full-blown ACL to secure Each type has two subtypes based on following criteria -- super-admin, if the source ip of the request is in the admin ip list and the request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers +- super-admin, if the request meets both of the following two requirements - - sm_universalid - - sm_user - - smgov_userdisplayname - - is_anonymous + 1. The request carries one of the following two attributes + + - the source ip is in the admin ip list + - has a client certificate that is signed using _NotifyBC_ server certificate. See [Client certificate authentication](../config/certificates.md#client-certificate-authentication) on how to sign. + + 2. The request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers + + - sm_universalid + - sm_user + - smgov_userdisplayname + - is_anonymous - admin, if the request is not super-admin and meets one of the following criteria @@ -120,9 +127,10 @@ The way _NotifyBC_ interacts with other components is diagrammed below. API requests to _NotifyBC_ can be either anonymous or authenticated. As alluded in [Request Types](#request-types) above, _NotifyBC_ supports following authentication strategies 1. ip whitelisting -2. Access token associated with an builtin admin user -3. OpenID Connect (OIDC) -4. CA SiteMinder +2. client certificate +3. access token associated with an builtin admin user +4. OpenID Connect (OIDC) +5. CA SiteMinder Authentication is performed in above order. Once a request passed an authentication strategy, the rest strategies are skipped. A request that failed all authentication strategies is anonymous. @@ -153,36 +161,69 @@ The mapping between authentication strategy and request type is