Skip to main content

Security

Andromeda authenticates nobody. Anyone who can reach the broker's API port can call every operation, in every tenant. To restrict access, put a reverse proxy or an API gateway in front of the broker, and keep the broker itself on a network that only the proxy reaches.

  1. ClientsApplications, devices, other brokers
  2. HTTPS
  3. Reverse proxy or API gatewayChecks who is calling, applies per-caller rules and rate limits
  4. only the requests it lets through, on a private network
  5. AndromedaAnswers every request that reaches it
AndromedaA system Andromeda doesn't run

What the broker leaves to you​

  • No login, token or permission check

    Every request that reaches the broker is served. A TLS client certificate, if you require one, only decides who may connect at all.

  • No rate limiting

    Set rate limits and connection limits at the proxy.

  • Tenants on demand

    Writing to a tenant that doesn't exist creates it.

  • Requests to addresses callers choose

    Subscriptions, @context URLs and context source registrations make the broker send requests where the caller says, so limit what the broker can reach.

  • Credentials in plain sight

    Subscriptions and registrations hand the credentials stored in them back to anyone who reads them.

Putting a proxy in front​

Andromeda leaves authentication to whatever sits in front of it. That can be an API gateway, a reverse proxy that checks OAuth 2.0 or OIDC tokens, API keys or client certificates, or a dataspace connector. The layout is the same every time.

  1. Clients reach the proxy, over TLS.
  2. The proxy checks who is calling, and forwards only the requests it lets through.
  3. The broker listens on a private interface or a container network, so nothing but the proxy reaches it.

Per-caller rules belong at the proxy too, because the broker can't tell callers apart. Who may write, who may use which tenant, and who may create subscriptions and registrations are all rules on the NGSILD-Tenant header, the method and the path.

Keep http.forwarded.trust at "none" unless the proxy overwrites the forwarded headers. When it does, prefer "proxies", with the proxy's addresses in http.forwarded.proxies, to "any". With "any", a caller who reaches the broker directly can choose the scheme and host of the URLs the broker stores. Trusting forwarded fields safely explains when to change it.

The management port, 1086 by default, serves health, readiness and metrics without any checks either. Let only your health checks and monitoring reach it, as Monitoring describes.

Client certificates​

With tls.client_authentication.mode = "required", the broker accepts a connection only from a client that holds a certificate signed by tls.client_authentication.ca. That decides who can connect, not what they can do, so every client with such a certificate can call every operation. Client certificates has the settings.

Tenants​

The NGSILD-Tenant header picks the tenant, and nothing checks that the caller may use it. Any caller can read and write any tenant, and the first write to a new name creates a tenant and its database, as Tenants describes. If callers mustn't see each other's data, check the header at the proxy. If you don't use tenants at all, turn them off with tenancy.mode = "disabled".

With the tenant management extension on, any caller can also list tenants and suspend them. Leave it off, or allow /ngsi-ld/v1/tenants only to operators at the proxy.

Requests the broker sends for a caller​

Three features make the broker send requests to an address the caller names. Unless you turn on the checks below, nothing checks where that address points. A caller can then aim the broker at internal services, cloud metadata endpoints or other hosts on the broker's network.

FeatureThe broker sendsSettings under
SubscriptionsNotifications to endpoint.urinotifications.delivery
@context URLsGET for each @context a caller namesjsonld.fetch
Context source registrationsForwarded queries and writes to endpointfederation.forwarding

Each of the three has the same three settings.

  • private_addresses = "refused"

    Refuses an address that resolves into loopback, private or reserved address space.

  • allowed_addresses

    Addresses and subnets exempt from that refusal, such as ["10.2.0.0/24", "fd00:1234::/48"], so receivers inside your network stay reachable while the rest stays out of reach.

  • denied_addresses

    Addresses and subnets refused whatever private_addresses says, public ones included. A denied address wins over an allowed one.

How a refusal shows differs between the three.

If callers aren't fully trusted, go further.

  • Limit outgoing traffic with a firewall, so the broker reaches only the database, the notification endpoints and the context sources you expect.
  • Allow POST and PATCH to /ngsi-ld/v1/subscriptions and /ngsi-ld/v1/csourceRegistrations only to trusted callers at the proxy.
  • Set jsonld.fetch.mode = "disabled" and list the @context documents your clients use as [[jsonld.preloaded_contexts]] tables, so nothing is fetched at all.

Credentials stored in subscriptions and registrations​

A subscription's receiverInfo, its endpoint URI with an MQTT user name and password in it, and a registration's contextSourceInfo often hold credentials. Andromeda stores them as written and returns them as written, to anyone who reads the subscription or registration. Any caller can read them, so treat them as visible to everyone who can reach the API, and restrict reads at the proxy if that matters.

A registration can collect your callers' credentials

A registration can ask for headers of the request that triggered a forwarded request. A contextSourceInfo pair whose value is urn:ngsi-ld:request sends the triggering request's header of the same name to the source. So a registration with {"key": "Authorization", "value": "urn:ngsi-ld:request"} receives the Authorization header of every caller whose query it matches. Have the proxy remove the credentials it checked before it forwards a request, or let only trusted callers register sources.

The broker keeps credentials out of its own output. Request traces don't show the Authorization, Cookie and Proxy-Authorization headers or a response's Set-Cookie, and andromeda configuration show prints the database connections redacted.

Limits on a request​

These bound what one request can cost. They protect the broker from mistakes and oversized requests, not from a determined caller, so set rate limits and connection limits at the proxy as well.

SettingDefaultBounds
request_limits.timeout30 secondsOne request, its body included
request_limits.max_body64 MiBThe body, in bytes
request_limits.max_json_values4,000,000The body, in JSON values
request_limits.max_header_section32 KiBThe header section
federation.query.max_entities10,000Distinct entities a forwarded query reads back
federation.query.max_source_rows30,000Entities fetched from sources, duplicates included
federation.query.max_instances100,000Attribute instances fetched from sources

A client that sends its body too slowly gets 408 when request_limits.timeout runs out, so a slow sender holds a connection for no longer than that. HTTP, Server and Federation in the configuration reference have every limit.

Browser applications​

The default CORS policy, http.cors.mode = "permissive", lets any web page call the broker from a browser, without cookies or HTTP authentication, and CSRF protection is off. Both suit machine clients behind a proxy. For a browser application, restrict CORS to its origins and turn on CSRF protection, as Browser clients describes.

Checking it works​

From a machine outside the private network, the broker's ports shouldn't answer. Both of these should time out or be refused.

curl --max-time 5 http://broker.internal:1056/ngsi-ld/v1/version
curl --max-time 5 http://broker.internal:1086/readyz

Through the proxy, a request without credentials should get the proxy's 401, not an answer from the broker.

curl -i 'https://broker.example.org/ngsi-ld/v1/entities?type=Building'

From inside the broker's container, an address you don't want it to reach should be blocked.

curl --max-time 5 http://169.254.169.254/

If it goes wrong​

  • A caller sees or changes another tenant's data

    The proxy doesn't check NGSILD-Tenant. Add the rule there, since the broker has no way to.

  • Notifications to an internal host fail after you add an egress policy

    The policy blocks an endpoint you meant to allow. Add the endpoint's address to it.

  • Clients get 504 LdContextNotAvailable after you set jsonld.fetch.mode = "disabled"

    They name a @context the broker hasn't preloaded or stored. Add a [[jsonld.preloaded_contexts]] table for it.

Where Andromeda interprets the specification​

The specification defines no authentication or authorisation, and leaves both to the deployment. Values taken from the triggering request (urn:ngsi-ld:request), the receiverInfo of a subscription and the contextSourceInfo of a registration are all defined by it, and Andromeda stores and returns them as written.