Skip to content

Security Guide: Configuration

This page is the procedural reference for hardening a Logster deployment. It covers authentication, CORS, TLS, reverse-proxy setup, Elasticsearch security, and Kafka transport security, with step-by-step instructions for each.

Read Overview and Security Validations first if you haven't — this page assumes you already understand what needs to be protected and why.


Authentication

The problem

The default Logster build has no authentication on the dashboard, REST API, Kibana, Prometheus, or Tempo. Only Grafana has a default password (admin / logster), and anonymous read is enabled.

The solution: reverse proxy

The recommended pattern is a single auth-enforcing reverse proxy in front of every user-facing port. This centralizes authentication and lets Logster stay simple internally.

Architecture

┌─────────────────────────────────────────────┐
│            Public / corporate network       │
└────────────────────┬────────────────────────┘
                     ▼ TLS + auth
┌─────────────────────────────────────────────┐
│  Reverse proxy (nginx / Traefik / Caddy)    │
│  - TLS termination                          │
│  - OIDC / SAML / Basic auth                 │
│  - /dashboard  → :5001                      │
│  - /api        → :8080                      │
│  - /grafana    → :3000                      │
│  - /kibana     → :5601                      │
└────────────────────┬────────────────────────┘
                     │ plaintext, localhost-only
┌─────────────────────────────────────────────┐
│              Logster stack                   │
└─────────────────────────────────────────────┘

Step 1 — Bind Logster to localhost

Edit deploy/docker-compose.yml so every published port binds only to 127.0.0.1:

api:
    ports:
        - "127.0.0.1:8080:8080"
dashboard:
    ports:
        - "127.0.0.1:5001:5000"
grafana:
    ports:
        - "127.0.0.1:3000:3000"
kibana:
    ports:
        - "127.0.0.1:5601:5601"
prometheus:
    ports:
        - "127.0.0.1:9090:9090"
elasticsearch:
    ports:
        - "127.0.0.1:9200:9200"

After restarting the stack, nmap from another host should show every one of these ports as closed.

Step 2 — Deploy a reverse proxy

Use nginx, Traefik, Caddy, or any other auth-enforcing reverse proxy. The proxy must:

  • Terminate TLS (TLS 1.2 or newer).
  • Enforce authentication on every upstream path.
  • Forward to the localhost-bound Logster ports from Step 1.

TBD — reference reverse-proxy configuration. Replace this placeholder with a vetted configuration snippet for your chosen proxy (nginx / Traefik / Caddy). Do not copy an example from generic documentation — test it against a real Logster deployment and verify every published route responds correctly.

Step 3 — Verify

# From outside the host, unauthenticated
curl -i https://<your-logster-fqdn>/api/health
# Expect: 401 Unauthorized

# With valid credentials
curl -u <user>:<password> https://<your-logster-fqdn>/api/health
# Expect: {"status":"ok",...}

TBD — SaaS auth topology. Logster Support's SaaS deployment model for Logster uses its own reverse proxy and identity provider stack. Confirm the exact topology with Logster Support Customer Services before documenting it here.


CORS

The problem

The REST API defaults to api.cors_origins: ["*"] in deploy/service-config.yaml. This means any web page on the internet can make authenticated requests to your API from a user's browser (if the user is behind the reverse proxy).

The fix

Tighten api.cors_origins to only the origins you actually serve the dashboard from:

api:
  host: "0.0.0.0"
  port: 8080
  cors_origins:
    - "https://logster.example.com"

Restart the API:

docker compose --profile services restart api

Verify via browser devtools — a cross-origin request from any other origin should now fail with a CORS error.


TLS

The problem

Every Logster service in the default Compose stack speaks plain HTTP or plain TCP. There is no TLS anywhere in the default build.

The fix

Terminate TLS at the reverse proxy. Do not try to configure TLS inside the individual Logster containers — the architecture assumes plain transport on the internal network.

  • Certificates. Use Let's Encrypt (Caddy handles this automatically; nginx/Traefik have plugins), or your corporate CA.
  • Minimum protocol version. TLS 1.2 or higher. Disable all older versions at the proxy.
  • HSTS. Enable HTTP Strict Transport Security headers so browsers refuse to fall back to plain HTTP.

Optionally, also terminate TLS on internal paths (Kafka broker listeners, Elasticsearch). See the next sections.


Elasticsearch security

The problem

The default Compose stack runs Elasticsearch with xpack.security.enabled: "false". Anyone who can reach port 9200 has full read/write access.

The fix

Enable ES security in deploy/docker-compose.yml:

elasticsearch:
    environment:
        discovery.type: single-node
        xpack.security.enabled: "true"
        ELASTIC_PASSWORD: "<strong-password>"

Then update every service that talks to ES:

  1. Logstash — add credentials to the pipeline conf files under deploy/logstash/pipeline/.
  2. Dashboard — set the dashboard's Elasticsearch client to use basic auth. Update ES_HOST to http://elastic:<password>@elasticsearch:9200.
  3. Kibana — add ELASTICSEARCH_USERNAME and ELASTICSEARCH_PASSWORD environment variables.

Restart the full stack and verify:

curl http://localhost:9200/_cluster/health
# Expect: 401 without credentials

Kafka authentication

The problem

The default Kafka broker uses PLAINTEXT listeners with no authentication. Anyone who can reach the broker can publish to any topic — which means injecting fake events into sysmon-logs, deleting messages from logster-alerts, or draining normalized-endpoint-events.

The fix

Use SASL/SSL in production. The high-level steps are:

  1. Provision SASL credentials for each Kafka client (a principal per service and per log shipper).
  2. Configure Kafka ACLs so each principal has only the read / write permissions it needs. The mapping of service to topic access is derivable from the architecture — the normalizer reads the raw topics and writes the normalized topic, and so on. See Enterprise Architecture.
  3. Reconfigure the Compose file to use SASL_SSL listeners, mount the truststore/keystore, and enable broker-side ACL enforcement.
  4. Update kafka.brokers in deploy/service-config.yaml to the new listener address, and pass SASL credentials via environment variables.

TBD — concrete SASL configuration. Document your production-validated Kafka SASL/SSL configuration here, including the exact principal-to-topic ACL mapping, once it has been tested against a real multi-broker cluster. The dev single-broker KRaft stack does not exercise ACL enforcement.

This is the largest single hardening step in this guide and is usually best handled by your platform team as part of their existing Kafka operations practice.


Grafana

The problem

The default Grafana admin password is logster and anonymous read is enabled.

The fix

In deploy/docker-compose.yml:

grafana:
    environment:
        GF_SECURITY_ADMIN_PASSWORD: "<strong-password>"
        GF_AUTH_ANONYMOUS_ENABLED: "false"

Restart Grafana:

docker compose restart grafana

Now only authenticated users can view or modify dashboards. Consider also enabling Grafana's OAuth/OIDC integration to avoid a second set of credentials for analysts.


Multi-tenancy

Logster's data model includes a tenant_id on every event, every inference, and every alert. The default build does not enforce isolation between tenants at the API layer — any caller can pass any tenant ID to GET /alerts?tenant_id=....

Two patterns

Pattern A — one instance per tenant. Run a separate Logster stack per tenant, with tenant data never commingled on the same Kafka topics or ES indices. Simple, strong, and the recommended approach unless you have a good reason otherwise.

Pattern B — multi-tenant in one instance, enforced at the proxy. Run one stack and use a reverse proxy to inject a hard-coded tenant_id query parameter on every request, based on the authenticated user's tenant. The proxy must strip any tenant_id the client tries to submit.

[!WARNING] Pattern B is fragile. Any bug or misconfiguration that lets a tenant ID through from the client leaks data across tenants. Pattern A is strongly preferred.


Production hardening checklist

  • [ ] Reverse proxy in front of every user-facing port
  • [ ] TLS terminated at the proxy, TLS 1.2+
  • [ ] Ports bound to 127.0.0.1 in the Compose file
  • [ ] api.cors_origins restricted to dashboard origin(s)
  • [ ] Grafana password rotated, anonymous access disabled
  • [ ] Elasticsearch security enabled, credentials provisioned
  • [ ] Kafka on SASL/SSL with ACLs
  • [ ] Alert store swapped to Postgres (see Admin Guide: Important Considerations)
  • [ ] Model file checksums verified on every deploy
  • [ ] Audit logging captured at the proxy layer

For the full hardening list, see Admin Guide: Important Considerations.


Where to go next