diff --git a/README.md b/README.md index 0db5454c..95a7f7d2 100644 --- a/README.md +++ b/README.md @@ -87,21 +87,61 @@ If you want different passwords, database names or ports you can change them in that file. Please note that the following setup is for development purposes only and should not be used in production. +```mermaid + C4Component + title Component diagram for CSAF CMS Backend + + Person(user,"User") + Container(reverseproxy, "Reverse-Proxy", "nginx") + + Container_Boundary(c4, "Internal") { + Container(secvisogram, "Secvisogram", "nginx + javascript", "Provides secvisogramm via their web browser.") + + Container_Boundary(c2, "Keycloak") { + Container(keycloak, "Keycloak", "keycloak") + ContainerDb(keycloak-db, "PostGreSQL", "Keycloak-Database") + } + + Container_Boundary(c3, "Oauth") { + Container(oauth, "OAuth2-Proxy", "Authentication for REST-API") + Container(validator, "CSAF validator service", "node") + + Container_Boundary(c1, "Backend") { + Container(backend, "CSAF-CMS-Backend", "Spring Boot") + ContainerDb(backend-db, "CouchDB", "CMS-Backend-Database") + } + } + } + + Rel(user, reverseproxy,"","HTTPS") + Rel(reverseproxy, secvisogram,"/") + Rel(reverseproxy, oauth,"/api/*") + Rel(reverseproxy, keycloak,"/realm/csaf/") + Rel(oauth, validator, "/api/v1/test") + Rel(oauth, validator, "/api/v1/validate") + Rel(oauth, backend, "/api/v1/advisories/*") + Rel(backend, backend-db,"") + Rel(backend, keycloak,"") + Rel(keycloak, keycloak-db,"") + + +``` + - run `docker compose up` - After Keycloak is up, open a second terminal window and run `docker compose up csaf-keycloak-cli` to import a realm with all the users and roles already set up. - To set up our CouchDB server open `http://127.0.0.1:5984/_utils/#/setup` - and run the [Single Node Setup](https://docs.couchdb.org/en/stable/setup/single-node.html). This creates databases like **_users** and - stops CouchDB from spamming our logs + and run the [Single Node Setup](https://docs.couchdb.org/en/stable/setup/single-node.html). This creates databases like **_users** and stops CouchDB from spamming our logs (Admin credentials from .env) +- Create a database in CouchDB with the name specified in `CSAF_COUCHDB_DBNAME` - Open `http://localhost:9000/` and log in with the admin user. - The port is defined in .env - CSAF_KEYCLOAK_PORT, default 9000 + - Select `CSAF`-Realm - On the left side, navigate to "Clients" and select the Secvisogram client. - Select the **Credentials** tab and copy the Secret. This is our `CSAF_CLIENT_SECRET` environment variable. - [Generate a cookie secret](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#generating-a-cookie-secret) and paste it in `CSAF_COOKIE_SECRET`. -- Create a database in CouchDB with the name specified in `CSAF_COUCHDB_DBNAME` - restart compose - (required for exports) install [pandoc (tested with version 2.18)](https://pandoc.org/installing.html) as well as [weasyprint (tested with version 56.0)](https://weasyprint.org/) and make sure both are in @@ -109,9 +149,32 @@ only and should not be used in production. - (optional for exports) define the path to a company logo that should be used in the exports through the environment variable `CSAF_COMPANY_LOGO_PATH`. The path can either be relative to the project root or absolute. See .env.example file for an example. You should now be able to start the spring boot application, navigate to -`localhost:4180/api/v1/about`, log in with one of the users and get a +`http://localhost/api/v1/about`, log in with one of the users and get a response from the server. -The port is defined in .env - CSAF_APP_EXTERNAL_PORT, default 4180 + +You should now be able to access Secvisogram, navigate to `http://localhost/`. +There are the following default users: +|User |Password |Roles | +|----- |-------- |----- | +|registered |registered |**registered** | +|author |author |registered, editor, **author** | +|editor |editor |registered, **editor** | +|publisher |publisher |registered, editor, **publisher** | +|reviewer |reviewer |registered, **reviewer** | +|auditor |auditor |**auditor** | +|all |all |**auditor, reviewer, publisher, editor, author, registred** | +|none |none | | + +### Login & Logout in combination with Secvisogram + +Some explantion on the logoutUrl configured in `.well-known/appspecific/de.bsi.secvisogram.json` for Secvisogram + +``` +"logoutUrl": "/oauth2/sign_out?rd=http://localhost/realms/csaf/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost&client_id=secvisogram", +``` + +`/oauth2/sign_out` is the logout URI from the OAUTH-Proxy. This will invalidate the session on the proxy. Then, a redirect to Keycloak (`http://localhost/realms/csaf/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost&client_id=secvisogram`) is necessary to log out from the session on Keyloak. Subsequently, there is a redirect back to Secvisogram (`localhost`). +When hostnames are changed, this has to adapted. ### build and execute tests diff --git a/compose.yaml b/compose.yaml index 1fb7af8f..c6291914 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,9 +3,10 @@ ############################################################################### services: - csaf-couchdb: + cms-couchdb: image: couchdb:3.3 - container_name: csaf-couchdb + hostname: couchdb.csaf.internal + #container_name: cms-couchdb restart: on-failure env_file: .env environment: @@ -15,10 +16,15 @@ services: - csaf-couchdb-data:/opt/couchdb/data ports: - "${CSAF_COUCHDB_PORT}:5984" + networks: + default: + aliases: + - "couchdb.csaf.internal" - csaf-keycloak-db: + keycloak-db: image: postgres:14 - container_name: csaf-keycloak-db + hostname: keycloak-db.csaf.internal + #container_name: keycloak-db volumes: - csaf-keycloak-db-data:/var/lib/postgresql/data env_file: .env @@ -29,17 +35,22 @@ services: restart: on-failure ports: - "${CSAF_KEYCLOAK_DATABASE_PORT}:5432" + networks: + default: + aliases: + - "keycloak-db.csaf.internal" - csaf-keycloak: + keycloak: image: quay.io/keycloak/keycloak:20.0 - container_name: csaf-keycloak + hostname: keycloak.csaf.internal + #container_name: keycloak env_file: .env environment: # https://www.keycloak.org/server/all-config KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" KC_DB: postgres - KC_DB_URL_HOST: csaf-keycloak-db + KC_DB_URL_HOST: keycloak-db.csaf.internal KC_DB_URL_PORT: 5432 KC_DB_URL_DATABASE: ${CSAF_KEYCLOAK_DATABASE_NAME} KC_DB_USERNAME: ${CSAF_KEYCLOAK_DATABASE_USER} @@ -48,36 +59,42 @@ services: KEYCLOAK_ADMIN: ${CSAF_KEYCLOAK_ADMIN_USER} KEYCLOAK_ADMIN_PASSWORD: ${CSAF_KEYCLOAK_ADMIN_PASSWORD} depends_on: - - csaf-keycloak-db + - keycloak-db restart: on-failure ports: - "${CSAF_KEYCLOAK_PORT}:8080" command: ["start-dev"] # https://www.keycloak.org/server/configuration#_starting_keycloak_in_production_mode - + networks: + default: + aliases: + - "keycloak.csaf.internal" + # Run this manually to import the default keycloak config since 'depends_on' is currently broken. - csaf-keycloak-cli: + keycloak-cli: image: adorsys/keycloak-config-cli:latest-20.0.1 - container_name: csaf-keycloak-cli + #container_name: keycloak-cli profiles: [ "run_manually" ] env_file: .env environment: - KEYCLOAK_URL: "http://csaf-keycloak:8080/" + KEYCLOAK_URL: "http://keycloak.csaf.internal:8080/" KEYCLOAK_USER: ${CSAF_KEYCLOAK_ADMIN_USER} KEYCLOAK_PASSWORD: ${CSAF_KEYCLOAK_ADMIN_PASSWORD} IMPORT_FILES_LOCATIONS: "/config/csaf-realm.json" volumes: - ./keycloak:/config:z - restart: on-failure + depends_on: + - keycloak - csaf-oauth2-proxy: + oauth2-proxy: image: bitnami/oauth2-proxy:7.4.0 - container_name: csaf-oauth2-proxy + hostname: oauth2.csaf.internal + #container_name: oauth2-proxy command: [""] env_file: .env environment: # listening address and proxy target OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:4180" - OAUTH2_PROXY_UPSTREAMS: "http://host.docker.internal:${CSAF_VALIDATOR_PORT}/api/v1/validate,http://host.docker.internal:${CSAF_VALIDATOR_PORT}/api/v1/tests,http://host.docker.internal:${CSAF_CMS_BACKEND_PORT}/api/v1/" + OAUTH2_PROXY_UPSTREAMS: "http://host.docker.internal:${CSAF_CMS_BACKEND_PORT}/api/v1/" # Security related config OAUTH2_PROXY_COOKIE_SECURE: "false" @@ -91,7 +108,7 @@ services: OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: "CSAF OIDC Provider" # You need to set your keycloak "Frontend URL", in our case "http://localhost:9000/auth/" # If you don't want to use autodiscovery, you have to set all urls by hand (login-url, oidc-jwks-url, redeem-url, ...) - OAUTH2_PROXY_OIDC_ISSUER_URL: "http://csaf-keycloak:8080/realms/${CSAF_REALM}" + OAUTH2_PROXY_OIDC_ISSUER_URL: "http://keycloak.csaf.internal:8080/realms/${CSAF_REALM}" OAUTH2_PROXY_INSECURE_OIDC_SKIP_ISSUER_VERIFICATION: "true" OAUTH2_PROXY_WHITELIST_DOMAINS: "localhost:4180,localhost:8080" @@ -113,16 +130,53 @@ services: extra_hosts: - "host.docker.internal:host-gateway" restart: on-failure - - csaf-validation-server: + depends_on: + - keycloak + networks: + default: + aliases: + - "oauth2.csaf.internal" + + validator: build: context: https://github.com/secvisogram/csaf-validator-service.git#main - container_name: csaf-validation-server + #container_name: validator + hostname: validator.csaf.internal env_file: .env ports: - "$CSAF_VALIDATOR_PORT:8082" + networks: + default: + aliases: + - "validator.csaf.internal" + secvisogram: + build: + context: ./docker/secvisogram + dockerfile: Dockerfile + hostname: secvisogram.csaf.internal + volumes: + - "./docker/secvisogram/appspecific:/usr/share/nginx/html/.well-known/appspecific" + networks: + default: + aliases: + - "secvisogram.csaf.internal" + + reverse-proxy: + image: nginx:1.23-alpine + hostname: "reverseproxy.csaf.internal" + restart: on-failure + ports: + - "80:80" + volumes: + - "./docker/reverseproxy/nginx.conf:/etc/nginx/nginx.conf" + depends_on: + - secvisogram + - keycloak + - oauth2-proxy + - validator + volumes: csaf-couchdb-data: driver: local diff --git a/docker/reverseproxy/nginx.conf b/docker/reverseproxy/nginx.conf new file mode 100644 index 00000000..745f022d --- /dev/null +++ b/docker/reverseproxy/nginx.conf @@ -0,0 +1,62 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + sendfile on; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + + + #https://www.getpagespeed.com/server-setup/nginx/tuning-proxy_buffer_size-in-nginx + proxy_buffer_size 16k; # should be enough for most PHP websites, or adjust as above + proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k + proxy_buffers 64 4k; # should be enough for most PHP websites, adjust as above to get an accurate value + + server { + listen 80; + + location /realms { + proxy_pass http://keycloak.csaf.internal:8080/realms; + proxy_redirect off; + } + + location /resources{ + proxy_pass http://keycloak.csaf.internal:8080/resources; + proxy_redirect off; + } + + location /validate/api/v1/tests { + proxy_pass http://validator.csaf.internal:8082/api/v1/tests; + proxy_redirect off; + } + + location /validate/api/v1/validate { + proxy_pass http://validator.csaf.internal:8082/api/v1/validate; + proxy_redirect off; + } + + location /api/ { + proxy_pass http://oauth2.csaf.internal:4180; + proxy_redirect off; + } + + location /oauth2 { + proxy_pass http://oauth2.csaf.internal:4180/oauth2; + proxy_redirect off; + } + + location /.well-known/appspecific/de.bsi.secvisogram.json { + proxy_pass http://secvisogram.csaf.internal/.well-known/appspecific/de.bsi.secvisogram.json; + proxy_redirect off; + } + + location / { + proxy_pass http://secvisogram.csaf.internal/; + proxy_redirect off; + } + } +} \ No newline at end of file diff --git a/docker/secvisogram/Dockerfile b/docker/secvisogram/Dockerfile new file mode 100644 index 00000000..5ec08dc4 --- /dev/null +++ b/docker/secvisogram/Dockerfile @@ -0,0 +1,18 @@ +# Build Stage 1 +# This build created a staging docker image +# +FROM node:20-alpine AS build +WORKDIR /usr/src +RUN apk add git; \ + git clone https://github.com/secvisogram/secvisogram.git; \ + cd secvisogram; \ + npm ci; \ + npm run build + +# Build Stage 2 +# This build takes the production build from staging build +# + +FROM nginx:1.23-alpine +COPY --from=build /usr/src/secvisogram/app/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/docker/secvisogram/appspecific/de.bsi.secvisogram.json b/docker/secvisogram/appspecific/de.bsi.secvisogram.json new file mode 100644 index 00000000..27fc7cec --- /dev/null +++ b/docker/secvisogram/appspecific/de.bsi.secvisogram.json @@ -0,0 +1,7 @@ +{ + "loginAvailable": true, + "loginUrl": "/oauth2/sign_in?rd=http%3A%2F%2Flocalhost", + "logoutUrl": "/oauth2/sign_out?rd=http://localhost/realms/csaf/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost&client_id=secvisogram", + "userInfoUrl": "/oauth2/userinfo", + "validatorUrl": "/validate" +} \ No newline at end of file diff --git a/keycloak/csaf-realm.json b/keycloak/csaf-realm.json index bc6a106a..cc3161ac 100644 --- a/keycloak/csaf-realm.json +++ b/keycloak/csaf-realm.json @@ -42,7 +42,7 @@ "registrationAllowed": false, "verifyEmail": false, "attributes" : { - "frontendUrl": "http://localhost:9000/" + "frontendUrl": "http://localhost/" }, "roles": { "client": { diff --git a/src/main/java/de/bsi/secvisogram/csaf_cms_backend/rest/MainController.java b/src/main/java/de/bsi/secvisogram/csaf_cms_backend/rest/MainController.java index 9f6e098d..2dc71997 100644 --- a/src/main/java/de/bsi/secvisogram/csaf_cms_backend/rest/MainController.java +++ b/src/main/java/de/bsi/secvisogram/csaf_cms_backend/rest/MainController.java @@ -53,7 +53,7 @@ public class MainController { ) public String about() { LOG.info("about"); - return "{version:\"" + buildProperties.getVersion() + "\"}"; + return "{\"version\":\"" + buildProperties.getVersion() + "\"}"; } } diff --git a/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/DocumentEntity.mjs b/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/DocumentEntity.mjs index a1978066..8e72297b 100644 --- a/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/DocumentEntity.mjs +++ b/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/DocumentEntity.mjs @@ -262,6 +262,36 @@ export default class DocumentEntity { } } + templateDoc.secureHref = () => { + return function ( + /** @type {string} */ text, + /** @type {function} */ render + ) { + const href = render(text) + let isValid = false + + const validStarts = ['#', 'mailto', 'tel', 'http', 'ftp'] + const validMimeTypes = [ + 'image/png;base64,', + 'image/jpeg;base64,', + 'image/gif;base64,', + ].map((x) => x.replaceAll('/', '/')) + validStarts.forEach((x) => (isValid = isValid || href.startsWith(x))) + const isBase64 = (/** @type {string} */ value) => + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/.test( + value + ) + validMimeTypes.forEach((mimeType) => { + const isValidDataHref = + href.startsWith(`data:${mimeType}`) && + isBase64(href.split(',')[1]?.replaceAll('=', '=')) + isValid = isValid || isValidDataHref + }) + + return isValid ? `href="${href}"` : '' + } + } + return { document: templateDoc } } } diff --git a/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/Script.mjs b/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/Script.mjs index 7bbb753e..52dcb464 100644 --- a/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/Script.mjs +++ b/src/main/resources/de/bsi/secvisogram/csaf_cms_backend/mustache/Script.mjs @@ -18,7 +18,7 @@ const PRODUCT_STATUS_ROW = ` const REMEDIATION = `
{{details}}
+{{{details}}}
{{#product_ids.length}}{{#url}}{{> url }}{{/url}}
{{#entitlements}} -{{.}}
+{{{.}}}
{{/entitlements}} {{#restart_required}} Restart required: {{category}} -{{details}}
+{{{details}}}
{{/restart_required}}` const THREAT = `{{details}}
+{{{details}}}
{{#product_ids.length}}{{text}}
{{/text}}` +{{#text}}{{{text}}}
{{/text}}` const DOCUMENT_NOTE = ` {{#title}}{{text}}
{{/text}}` +{{#text}}{{{text}}}
{{/text}}` const ACKNOWLEDGEMENT = ` {{#.}} -Publisher: exccellent | -Document category: generic_csaf | -
Initial release date: 2022-01-12T11:00:00.000Z | -Engine: Secvisogram 1.10.0 | -
Current release date: 2022-01-11T11:00:00.000Z | -Build Date: 2022-01-11T04:07:27.246Z | -
Current version: 0.0.1 | -Status: draft | -
CVSSv3.1 Base Score: 0 | -Severity: - - - | -
Original language: | -Language: | -
Also referred to: | -
Publisher: exccellent | +Document category: generic_csaf | +
Initial release date: 2022-01-12T11:00:00.000Z | +Engine: Secvisogram 1.10.0 | +
Current release date: 2022-01-11T11:00:00.000Z | +Build Date: 2022-01-11T04:07:27.246Z | +
Current version: 0.0.1 | +Status: draft | +
CVSSv3.1 Base Score: 0 | +Severity: + + + | +
Original language: | +Language: | +
Also referred to: | +
Namespace: https://exccellent.de
- - +Namespace: https://exccellent.de
+ + -Version | -Date of the revision | -Summary of the revision | -
---|---|---|
0.0.1 | -2022-01-12T11:00:00.000Z | -Test rsvSummary | -
Version | +Date of the revision | +Summary of the revision | +
---|---|---|
0.0.1 | +2022-01-12T11:00:00.000Z | +Test rsvSummary | +