Browser clients
In front of the API, Andromeda runs a few web-facing layers. The defaults suit machine clients, which is how most context brokers are used. Change them when a browser application talks to the broker, or when a proxy in front already does the same job. None of these layers run on the separate management port.
| Layer | Default | Switched with |
|---|---|---|
| CORS | Any origin, without credentials | http.cors.mode |
| CSRF protection | Off | http.csrf.mode |
| Security headers | On | http.security_headers.mode |
| Response compression | gzip, Brotli, deflate and zstd | http.compression.mode |
| Request decompression | The same four | http.decompression.mode |
Server-Timing | The total time | http.server_timing.mode |
Browser applications: CORS
By default, http.cors.mode is "permissive". Any origin, method and header is allowed, without credentials, so a browser application on any origin can read the API. That is handy in development and too open for most production setups. "disabled" sends no CORS headers at all, so browsers on other origins can't read responses.
For production, name the origins you serve with http.cors.mode = "configured".
[http.cors]
mode = "configured"
allow_origins = ["https://dashboard.example.org"]
allow_methods = ["GET", "POST", "PATCH", "PUT", "DELETE"]
allow_headers = "mirror"
expose_headers = ["Link", "Location", "NGSILD-Results-Count"]
preflight_lifetime = "1h"
expose_headers lists the response headers a browser script may read. An NGSI-LD client usually needs Link for pagination and the @context, Location after a create, and NGSILD-Results-Count when it asks for a count. "mirror" allows whatever headers the browser asks for in its preflight.
If the application sends cookies or HTTP authentication, add credentials = "allowed". Browsers don't allow credentials together with a wildcard, so the broker refuses to start when credentials is "allowed" and any of allow_origins, allow_methods, allow_headers or expose_headers is "any". Use lists, or "mirror" for methods and headers.
"very-permissive" is for development onlyIt mirrors the request's origin, method and headers and allows credentials, which lets any site make credentialed requests to the broker.
Browser applications: CSRF
CSRF protection is off by default. Turning it on doesn't affect machine clients, because they send neither an Origin nor a Sec-Fetch-Site header, and such requests always go through. Turn it on when a browser application with credentials talks to the broker.
[http.csrf]
mode = "enabled"
trusted_origins = ["https://dashboard.example.org"]
A request then goes through when any of these holds.
- Its method is
GET,HEADorOPTIONS. - Its
Originis intrusted_origins. - Its
Sec-Fetch-Siteissame-originornone. - It has neither
OriginnorSec-Fetch-Site. - Its
Originnames the same host and port as the request.
Anything else gets 403. The check relies on Origin and Host arriving as the browser sent them, so a proxy in front must pass both on unchanged.
Security headers
With http.security_headers.mode = "enabled", the default, every API response carries these headers.
Content-Security-Policy: default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: sameorigin
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
Each header has a key of its own in [http.security_headers]. Change one, or set it to "disabled" to drop it, and the others stay as they are. Content-Security-Policy is a table of its own, because its value is free text.
[http.security_headers]
x_frame_options = "deny"
referrer_policy = "strict-origin-when-cross-origin"
x_xss_protection = "disabled"
[http.security_headers.content_security_policy]
policy = "default-src 'none'; frame-ancestors 'none'"
A policy that can't be sent as a header value, such as one holding a line break, stops the broker at startup. Strict-Transport-Security is off by default, and TLS covers it.
If your proxy sets security headers of its own, turn off the broker's copy so that a response doesn't carry both. Use the header's own key for one header, or http.security_headers.mode = "disabled" for all of them.
Compression
Responses are compressed with whichever of gzip, Brotli, deflate and zstd the client accepts, and responses of 32 bytes or less go out as they are. Request bodies may arrive in any of the four codings. To change the set, list the codings you want. Each table has a mode that turns it off, and while it is on, its list must name at least one coding.
[http.compression]
algorithms = ["gzip", "zstd"]
quality = "fastest"
[http.decompression]
mode = "disabled"
quality trades CPU for size, with "fastest", "best", or "default" for each coding's usual balance. If your proxy already compresses responses, set http.compression.mode = "disabled" and save the CPU.
Two rules apply to a compressed request body.
- It needs a
Content-Lengththat gives its size as sent, compressed. request_limits.max_bodycounts its size after decompression, so a small body that expands past the limit still gets413.
A body in a coding that isn't in http.decompression.algorithms is refused, unless http.decompression.unaccepted_encoding = "allowed" hands it to the API still encoded.
Server-Timing
Every response carries a Server-Timing header with the time the request took, named total. Add "dispatch" to http.server_timing.phases to also get the time until the request reached its handler. Browser developer tools show both next to each request. Set http.server_timing.mode = "disabled" if you'd rather not publish timings.
How Link values are laid out
A response can carry several Link values, such as the @context link and the pagination links. http.link_field decides how they are spread over header lines.
| Value | Layout |
|---|---|
"combined", the default | All on one line, separated by , |
"grouped" | One line for the @context link, one for the pagination links, one for any others |
"separate" | Every value on a line of its own |
"combined" suits clients that read only one Link line, such as Go's Header.Get, and "separate" suits clients that don't split a line at commas. Every layout keeps the same order, the @context link first, then first, prev, next and last, then anything else. HTTP notifications follow the same setting. An MQTT notification always carries its Link as one metadata member.
Checking it works
Ask for a response the way a browser on your application's origin would, and look at the headers.
curl -si -H 'Origin: https://dashboard.example.org' \
-H 'Accept-Encoding: gzip' \
-o /dev/null -D - http://localhost:1056/ngsi-ld/v1/types
With the CORS example above, the answer carries access-control-allow-origin: https://dashboard.example.org, and carries no such header for an origin you didn't list. The security headers appear unless you turned them off, and server-timing carries total. A response over 32 bytes also carries content-encoding: gzip.