Docker-Compose prerequisites

All of the deployments use Kubernetes as primary deployment environment. If for some reason you do not want to use Kubernetes in your environment, Docker-compose can also be used to deploy the TSG. This documentation page will highlight differences between the Docker-compose deployments and the Kubernetes deployments.

General

Docker-compose can be used as an alternative for Kubernetes, but not all functionalities are available. For security reasons, the Workflow Orchestration module should be disabled in Docker-compose deployments.

Next to this, Linux Control Groups should be used to limit access to the containers memory. Since docker containers have volatile memory by default, all restarts or shutdowns of the containers will result in clearing the volatile memory. In case of a user logout, the JWT is invalidated which means no one can access the session data in memory. With the next restart or shutdown, the information will be gone.

As mentioned, the containers are restarted to a secure state. The restart policy should be set to either always, on-failure, or unless-stopped. In most situations unless-stopped is the most suitable setting.

Furthermore, segmentation of networks can be achieved by leveraging Docker networks, which only allow communication with a single network. Containers that require to cover two (or more) networks can be attached to multiple networks.

Log rotation

For Docker-compose deployments, audit records are stored via the Docker deamon logging driver. To increase security, the Docker daemon can be configured using the journald logging driver.

Alternatively, gelf or fluentd can be used as a more centralized logging solution.

Cluster management

For cluster management, in Kubernetes it is required to have access outside of Kubernetes ingresses. If an adminstrator has access to a cluster, port-forwarding can be used to access containers in case essential functions should be reached. In Docker-compose, this can be done by using a seperate socat container. Such a container can be used to temporarily expose a port of a service. As explained in the Configuration part of the documentation, we assume anyone who has access to the running components to have administrator rights in the connector. Therefore, be sure to deploy the Docker-compose environment on a machine that only administrators have access to.

Backups

The TSG does not offer its own backup solution, so hosting parties should setup their own way of backing up regularly. In Docker-compose, backups can be enabled by regularly backing up the tsg-data volume. The easiest method is to use the Volumes Backup & Share extension for backup and restore of the volumes. Simply install the extension, open it, press the action to export the volume and follow the steps.

NGINX reverse proxy

In the Kubernetes deployments, connections from outside are handled by an NGINX Ingress Controller. For Docker-Compose deployments, a NGINX reverse proxy can be used to secure deployments from outside threats.

An example configuration of this proxy is available below:

Click to expand example
client_max_body_size 256M;
ssl_trusted_certificate   /secrets/idsidentity/ca.crt;
ssl_certificate           /secrets/idsidentity/ids.crt;
ssl_certificate_key       /secrets/idsidentity/ids.key;
ssl_protocols             TLSv1.2 TLSv1.3;
ssl_ciphers               ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_stapling              on;
ssl_stapling_verify       on;

add_header X-Frame-Options            "SAMEORIGIN";
add_header Content-Security-Policy    "default-src 'self'; font-src *;img-src * data:; script-src *; style-src *";
add_header X-XSS-Protection           "1; mode=block";
add_header X-Content-Type-Options     "nosniff";
add_header Referrer-Policy            "strict-origin";
add_header Permissions-Policy         "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";
add_header Strict-Transport-Security  "max-age=31536000; includeSubdomains; preload";
proxy_intercept_errors                 on;
limit_req_zone $binary_remote_addr zone=limitreqsbyaddr:20m rate=10r/s;
limit_req zone=limitreqsbyaddr burst=20 nodelay;

map $status $status_text {
  400 'Bad Request';
  401 'Unauthorized';
  402 'Payment Required';
  403 'Forbidden';
  404 'Not Found';
  405 'Method Not Allowed';
  406 'Not Acceptable';
  407 'Proxy Authentication Required';
  408 'Request Timeout';
  409 'Conflict';
  410 'Gone';
  411 'Length Required';
  412 'Precondition Failed';
  413 'Payload Too Large';
  414 'URI Too Long';
  415 'Unsupported Media Type';
  416 'Range Not Satisfiable';
  417 'Expectation Failed';
  418 'I\'m a teapot';
  421 'Misdirected Request';
  422 'Unprocessable Entity';
  423 'Locked';
  424 'Failed Dependency';
  425 'Too Early';
  426 'Upgrade Required';
  428 'Precondition Required';
  429 'Too Many Requests';
  431 'Request Header Fields Too Large';
  451 'Unavailable For Legal Reasons';
  500 'Internal Server Error';
  501 'Not Implemented';
  502 'Bad Gateway';
  503 'Service Unavailable';
  504 'Gateway Timeout';
  505 'HTTP Version Not Supported';
  506 'Variant Also Negotiates';
  507 'Insufficient Storage';
  508 'Loop Detected';
  510 'Not Extended';
  511 'Network Authentication Required';
  default 'Something is wrong';
}

server {
  listen                  8080 ssl;
  server_name             tsg-reverse-proxy;
  server_tokens           off;
  proxy_intercept_errors  on;
   # If they come here using HTTP, bounce them to the correct scheme
  error_page 497 https://$host:$server_port$request_uri;

  error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;

  location = /error.html {
    ssi on;
    internal;
    auth_basic off;
    root /usr/share/nginx/html;
  }

  location / {
    proxy_pass            https://tsg-core-container-server:8080;

    modsecurity_rules '
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLog /tmp/modsec_audit.log
      SecRule REMOTE_ADDR "@contains 127.0.0.1" "id:1,phase:1,nolog,allow,ctl:ruleEngine=Off"
      SecRuleUpdateActionById 920350 "deny,status:403"
      SecRuleRemoveById 932115 933210 921110 922120
    ';
  }
}
server {
  listen                  8082 ssl;
  server_name             tsg-reverse-proxy;
  server_tokens           off;
  ssl_stapling            on;
  ssl_stapling_verify     on;
  ssl_trusted_certificate /secrets/idsidentity/ca.crt; 
  proxy_intercept_errors  on;

  # If they come here using HTTP, bounce them to the correct scheme
  error_page 497 https://$host:$server_port$request_uri;

  location / {
    proxy_pass            https://tsg-core-container-server:8082;

    modsecurity_rules '
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLog /tmp/modsec_audit.log
      SecRule REMOTE_ADDR "@contains 127.0.0.1" "id:1,phase:1,nolog,allow,ctl:ruleEngine=Off"
      SecRuleUpdateActionById 920350 "deny,status:403"
      SecRuleRemoveById 932115 933210 921110 922120 911100
    ';
  }
}
server {
  listen                  8088 ssl;
  server_name             tsg-reverse-proxy;
  server_tokens           off;
  ssl_stapling            on;
  ssl_stapling_verify     on;
  ssl_trusted_certificate /secrets/idsidentity/ca.crt;
  proxy_intercept_errors  on;
   # If they come here using HTTP, bounce them to the correct scheme
  error_page 497 https://$host:$server_port$request_uri;

  error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;

  location = /error.html {
    ssi on;
    internal;
    auth_basic off;
    root /usr/share/nginx/html;
  }

  location / {
    proxy_pass            http://tsg-core-container-ui:80;

    modsecurity_rules '
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLog /tmp/modsec_audit.log
      SecRule REMOTE_ADDR "@contains 127.0.0.1" "id:1,phase:1,nolog,allow,ctl:ruleEngine=Off"
      SecRuleUpdateActionById 920350 "deny,status:403"
      SecRuleRemoveById 932115 933210 921110 922120 911100
    ';
  }
}

This configuration enables ModSecurity, to increase Zone Boundary protection, monitor access from untrusted networks and possibly block incoming requests via the Firewall. This configuration also enables encryption in transit with only secure TLS ciphers (see line 5 for the complete list). Rate limiting is handled by adding the limit_req_zone and limit_req directives. Only the ports that are needed are exposed internally and the ports to the outside are specified in the reverse proxy.

The example configuration uses a custom error.html to not display server information in case of errors. An example for this html can be found here: https://tno-tsg.gitlab.io/assets/html/error.html

Example deployment

An example deployment is found in the docker-compose.yaml file below. This requires you to have a folder with the following file structure:

root
|   docker-compose.yaml
└───tsg-config
|   |   error.html
|   |   proxy.conf
|   |   chain.crt
|   |   tsg-connector.crt
|   |   tsg-connector.key

Make sure the proxy.conf displayed above is included in the deployment folder as well. At the moment of releasing version 1.2.3 of the core container, we recommend to use the following version for the ui: docker.nexus.dataspac.es/ui/core-container-ui:1.6.8@sha256:ce5685aec0db9ccc063bf7267e442a83e4993dc7bf1b77bbafff3be94ea27b19 and the following version for the NGINX reverse proxy: owasp/modsecurity-crs:3.3.5-nginx-alpine-202310170110@sha256:a9fbf4283c5edcc8c2fb867cd38f948f0269726160dde170a793a30d176e4669.

Click to expand example
  tsg-core-container-server:
    cap_add:
      - SYS_TIME
    image: docker.nexus.dataspac.es/core-container:1.2.3@sha256:55a1f5a4a711a9359fb1ced5d2be7fd49ae839cd150978566dddb93555dcab36
    pull_policy: always
    restart: unless-stopped
    volumes:
      - ./tsg-config/application.yaml:/config/application.yaml
      - ./tsg-config/tsg-connector.crt:/secrets/idsidentity/ids.crt
      - ./tsg-config/tsg-connector.key:/secrets/idsidentity/ids.key
      - ./tsg-config/chain.crt:/secrets/idsidentity/ca.crt
      - tsg-data:/resources
    networks:
      - local

  tsg-core-container-ui:
    image: docker.nexus.dataspac.es/ui/core-container-ui:1.6.8@sha256:ce5685aec0db9ccc063bf7267e442a83e4993dc7bf1b77bbafff3be94ea27b19
    pull_policy: always
    restart: unless-stopped
    environment:
      - API_BACKEND=tsg-core-container-server:8082
      - PASSWORD_LENGTH=12
      - APIKEY_LENGTH=20
    networks:
      - local

  tsg-reverse-proxy:
    image: owasp/modsecurity-crs:3.3.5-nginx-alpine-202310170110@sha256:a9fbf4283c5edcc8c2fb867cd38f948f0269726160dde170a793a30d176e4669
    restart: unless-stopped
    ports:
      - 8082:8082 # API port
      - 8083:8080 # IDS Multipart port
      - 8088:8088 # UI port
    environment:
      - PROXY=1
    volumes:
      - ./tsg-config/error.html:/usr/share/nginx/html/error.html
      - ./tsg-config/proxy.conf:/etc/nginx/conf.d/proxy.conf
      - ./tsg-config/tsg-connector.crt:/secrets/idsidentity/ids.crt
      - ./tsg-config/tsg-connector.key:/secrets/idsidentity/ids.key
      - ./tsg-config/chain.crt:/secrets/idsidentity/ca.crt
    networks:
      - local

Updating the core container can be done via replacing the image in the tsg-core-container-server block above. See the Releases page for the latest version.

Didn't find what you were looking for?