[API Request] Declarative Server-to-Server (S2S) Relay Policy and Loop Prevention #254
Replies: 2 comments
|
Edgesonic (wuyilingwei/edgesonic) is an open‑source music server implementation built on Cloudflare Workers, designed specifically to lower the barrier to entry for self‑hosting enthusiasts. Unlike traditional server software that requires dedicated infrastructure and complex network configuration, Edgesonic leverages edge computing to provide a stable, globally distributed deployment with minimal operational overhead. By running at the edge, it inherently mitigates many common security threats – such as released CVE, DDoS attacks and exposure of home IP addresses – that have become increasingly problematic for novice administrators, thereby reducing the learning curve significantly. The project emphasises broad compatibility with existing storage ecosystems. It natively supports multiple data source backends, including Amazon S3‑compatible object storage (and R2), WebDAV shares, generic WebURL endpoints and other subsonic server, and is engineered to integrate seamlessly with NAS devices commonly used in home environments. This flexibility allows users to aggregate music from diverse locations without migrating their existing storage layouts. Although Edgesonic remains in its early stages and still contains known bugs, the first public release was published today and has reached a baseline level of usability. It serves both as a practical tool for hobbyists and as a validation platform for advanced server‑to‑server aggregation patterns, while its core design philosophy remains focused on simplicity, security, and interoperability. |
|
Actually, I haven't fully figured out how to avoid the server loop yet, but it's not an urgent issue. The current solution is not perfect, and we still have time to fully discuss the protocol implementation. The UUID should be optional, which will affect the final implementation. Some people may not want to display the UUID, as this identifies the server. |
Uh oh!
There was an error while loading. Please reload this page.
Type of change
API extension
Proposal description
The current OpenSubsonic protocol specification defines interactions solely between a client and a server. It does not provide any metadata, headers, or semantics for server-to-server (S2S) communication, such as proxying, aggregation, or federation. This gap has led to real-world deployments using OpenSubsonic endpoints in S2S topologies (e.g., caching layers, aggregators like Edgesonic), which have uncovered critical operational issues.
This proposal introduces two optional fields inside the block of the getOpenSubsonicExtensions response to address these issues:
server_relay_policy (Declarative Relay and Cache Policy): This allows a server owner to explicitly signal to downstream S2S nodes how their content may be handled. Valid values are allow, deny, and no-cache. This provides fine-grained control over bandwidth usage, data persistence, and content redistribution, aligning with data sovereignty and operational cost constraints.
server_uuid (Loop-Prevention Identifier): This is a unique, persistent UUID v4 identifier for each server. S2S aggregators will use this in a new X-OpenSubsonic-Path HTTP header to track the chain of servers a request has traversed. If a server detects its own UUID in the header, it will abort the request with a 403 Forbidden error, effectively preventing infinite routing loops in a mesh of mutually proxying servers.
These extensions are declarative, optional, and fully backward compatible. They provide explicit control for server owners and a deterministic loop-breaking mechanism for aggregator operators, making the S2S ecosystem safer and more predictable without a central registry or breaking changes.
Backward compatibility impact
This proposal is designed to have no backward compatibility impact.
For legacy clients: They will not request or parse the getOpenSubsonicExtensions response for these fields, and they will not send the X-OpenSubsonic-Path header. Their behavior remains completely unchanged.
For legacy servers: They will not advertise the fields. Aggregators implementing this proposal will treat them as having the default allow policy and will not perform loop detection for that branch (since no server_uuid is available). This ensures older servers remain functional in S2S contexts.
For aggregators: The proposal introduces a MUST requirement to fetch the extension response before binding, but this is an internal operational change that does not alter the core API semantics or break existing integrations. The new HTTP header is optional and can be ignored by non-compliant servers.
Backward compatibility
API details
The proposal adds two new optional fields to the response of the existing getOpenSubsonicExtensions endpoint. The new HTTP header is used for loop prevention.
The response currently returns a list of objects within an container. The two new fields are added to the root of the response object for a given server.
Field: server_relay_policy
Type: string
Cardinality: 0..1 (optional)
Description: Declares the server owner's policy for downstream S2S nodes.
Valid Values:
"allow": Downstream S2S nodes are permitted to relay, aggregate, and persistently cache content.
"deny": The server forbids any S2S proxying or binding. Compliant aggregators must not forward requests from this origin.
"no-cache": Relaying is allowed, but media files must not be persisted to disk. Transient in-memory caching is acceptable.
Default: If the field is absent, the default is allow.
Field: server_uuid
Type: string (format: UUID v4 as per RFC 4122)
Cardinality: 0..1 (optional)
Description: A unique identifier generated once upon server installation and persisted across restarts.
Default: If the field is absent, the server does not support loop prevention.
Header Name: X-OpenSubsonic-Path
Format: A comma-separated list of UUIDs in chronological order (e.g., uuid_of_origin_server,uuid_of_first_proxy,uuid_of_second_proxy).
Usage:
The initial S2S node (e.g., an aggregator receiving a client request) MUST create this header and include its own server_uuid and the server_uuid of the first upstream server it is contacting.
Each subsequent S2S node that forwards the request MUST append its own server_uuid to the header before forwarding.
Upon receiving a request, a server MUST inspect this header. If it finds its own UUID in the list, it MUST immediately abort the request and return a 403 Forbidden HTTP response with an explanatory body (e.g., "Loop detected") and not process the request further.
Note: This header is omitted for direct client requests.
Security impacts
Policy Enforcement: The server_relay_policy is a declarative statement of intent. Its enforcement relies on the goodwill and compliance of downstream aggregators. A malicious aggregator could ignore the policy, but this is a trust issue inherent in S2S communication rather than a protocol vulnerability.
UUID Spoofing: A malicious actor could spoof the X-OpenSubsonic-Path header to cause a server to incorrectly detect a loop and reject a legitimate request. However, this would require knowing the target server's UUID and would only cause a denial of service for that specific request chain. The stateless nature of the check makes it difficult to cause widespread disruption.
Header Integrity: The header is not signed or encrypted. This is not considered a high-security risk as the primary goal is loop prevention, not authentication. Tampering would only result in a false positive loop detection, not a security breach.
Information Disclosure: The server_uuid is not a secret and is already exposed via the getOpenSubsonicExtensions endpoint. Its inclusion in the header does not disclose sensitive information.
Potential issues
Soft Enforcement: The server_relay_policy is a "MUST" for compliant aggregators. Its effectiveness is dependent on operator compliance, which is outside the protocol's control.
Operator Awareness: The default for absent policy is allow. This is chosen for maximum interoperability but requires aggregator operators to be aware of the logs and their upstreams' configurations. An operator might not notice that an upstream has not set a policy.
Configuration Mismatch: A server owner might set no-cache, but an aggregator might misunderstand "transient in-memory caching" and persist data for an extended period. Clearer definition of "transient" might be needed in the future.
Legacy Interoperability in Loops: A loop involving a legacy server that does not send or check the header will not be broken. However, such loops are less likely in a pure mesh of modern servers. The risk is mitigated by the fact that once a loop hits a compliant server, it will be broken.
Alternative solutions
Stateful Loop Tracking: Servers could maintain a stateful session or request ID to track traversed chains. This is more complex and introduces state management overhead and scalability issues, whereas the UUID-based header is stateless and efficient.
Separate Endpoint for Policy: A new endpoint (e.g., /getServerPolicy) could be created to fetch the relay policy. This would be more work for aggregators, requiring an extra call. Integrating it into an existing extension endpoint is more efficient.
Standard on the URL or Path: The relay policy could be conveyed via a new URL parameter or a special path segment. This is less clean as it mixes data with the resource location and is more intrusive.
DNS-based Policies: Operators could use DNS TXT records to advertise policies. This decouples policy from the API but is less discoverable and harder to validate, as the aggregator would need to perform a separate DNS lookup per upstream. The current proposal keeps the policy self-contained within the server's API response.
All reactions