[Feature] Add PPL rest command#5599
Conversation
Add a leading `rest <endpoint>` command that exposes a curated, read-only, fixed-schema set of in-cluster management endpoints as a PPL table, modeled as a system row source bridged through visitRelation (the same seam as describe and the system-index family), so it runs on the Calcite engine without the unsupported table-function path. - Grammar/AST: REST/TIMEOUT tokens, restCommand rule, RestRelation, AstBuilder visit, query anonymizer. - Execution: RestSourceTable -> CalciteLogicalRestScan / CalciteEnumerableRestScan; RestEndpointRegistry (read-only allow-list + fixed schema + accepted args); RestEnumerator/RestRequest dispatch via OpenSearchClient (NodeClient in-cluster, RestClient standalone). - 9 endpoints: cluster health/state/settings, cat indices/nodes/cluster_manager/ plugins/shards, resolve/index. - Output shaping: numeric type normalization, id-to-name resolution, role-name expansion, structural flattening, graceful null degrade. - Args: count caps emitted rows; timeout reserved but rejected with 400; get-args applied server-side with per-arg value validation (local on health, health on cat/indices, expand_wildcards on resolve/index). Undeclared arg or out-of-domain value is rejected with a 400. level and include_defaults are deferred to a later release; flat_settings is dropped as redundant. - Error handling: blank endpoint, negative count, disallowed arg, and uncoercible value all surface clean 400s rather than 500s. Tests: CalcitePPLRestIT 25/25, RestEndpointRegistryTest 16, RestSourceTableTest 10. Signed-off-by: Louis Chu <clingzhi@amazon.com>
PR Code Analyzer ❗AI-powered 'Code-Diff-Analyzer' found issues on commit aef4663.
The table above displays the top 10 most important findings. Pull Requests Author(s): Please update your Pull Request according to the report above. Repository Maintainer(s): You can Thanks. |
PR Reviewer Guide 🔍(Review updated until commit aef4663)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to aef4663 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 7d9df84
Suggestions up to commit d43f28c
Suggestions up to commit 06ff820
Suggestions up to commit 06ff820
Suggestions up to commit 0819158
|
…xes; comment cleanup - /_cluster/settings: run the persistent and transient tiers through the node SettingsFilter (published via RestSettingsFilterHolder from SQLPlugin#getRestHandlers) so Property.Filtered and plugin-registered pattern settings are redacted exactly as the native GET /_cluster/settings endpoint. Remove the dead secretFields column-filter, which was the wrong shape for the (setting, value, tier) rows. - Parser: add TIMEOUT to searchableKeyWord so a bare 'timeout' term still matches searchLiteral. - coerce(): narrow the catch to IllegalArgumentException | ClassCastException; add empty-string guards in toNumber/toBoolean. - spotlessApply formatting; drop outdated and redundant comments. Tests: RestEndpointRegistryTest, RestSourceTableTest, OpenSearchNodeClientClusterSettingsFilterTest green. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit a7a8a20 |
- clusterSettings: fail closed (throw IllegalStateException) when the node SettingsFilter is unavailable, instead of returning unredacted settings - collectSettings: handle list-type settings via getAsList fallback - decodeRestSpec: reject a blank/missing endpoint with a clear error - docs: correct rest.md allow-list table (9 endpoints + accepted args), quote endpoint literals, fix timeout/get-arg descriptions, add security note - register docs/user/ppl/cmd/rest.md in docs/category.json (deterministic single-node examples: number_of_nodes=1, cluster_manager count=1) Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit 8b0a147 |
|
Persistent review updated to latest commit e4c63e0 |
…fetch - CalciteExplainIT.explainRestCommand: single-quote the endpoint literal; the explain harness inlines the query into a JSON body without escaping, so a double-quoted literal produced an invalid payload and a 400 (integration CI failure). - OpenSearchNodeClient.clusterSettings: resolve the SettingsFilter and fail closed BEFORE fetching cluster state, so settings are never read into memory when the redaction filter is unavailable. - OpenSearchRestClient.clusterState: narrow filter_path to nodes.*.name so node IPs/attributes are not over-fetched; manager-name resolution is preserved. Signed-off-by: Louis Chu <clingzhi@amazon.com>
e4c63e0 to
343e93a
Compare
|
Persistent review updated to latest commit 343e93a |
… decode - AnalyticsEngineCompatIT: assert | rest '/_cluster/health' behaves identically with the analytics engine enabled (rest is never routed to DataFusion). - SystemIndexUtils.fromHex: reject an odd-length hex body so a crafted source name that passes the isRestSource suffix check fails clearly rather than silently dropping the trailing half-byte. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit 42f77a5 |
|
Did verification on a live composite cluster with analytics path, it seems like Repro: composite cluster (cluster.pluggable.dataformat=composite) + analytics stack + this PR, run
|
On a cluster started with cluster.pluggable.dataformat=composite, RestUnifiedQueryAction.isAnalyticsIndex() routed every non-system-catalog PPL query to the analytics engine. The rest command's reserved in-cluster source (REST...__REST_SOURCE) has no backing index and only resolves on the Calcite path, so it was routed to DataFusion and failed with "Table 'REST...__REST_SOURCE' not found". Fix: exclude isRestSource(name) alongside isSystemCatalog(name) so a rest source falls back to the default (Calcite) pipeline and is never routed to the analytics engine. - RestUnifiedQueryActionTest: unit repro under cluster-composite. - integ-test analyticsEngineCompat testcluster set composite-default so the existing rest coexistence IT exercises this routing exclusion. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit 2db942f |
|
|
||
| private static final Map<String, Endpoint> REGISTRY = buildRegistry(); | ||
|
|
||
| private static Map<String, Endpoint> buildRegistry() { |
There was a problem hiding this comment.
Could you confirm if all exposed API here are allowed in managed service / serverless. Just concerned if any security issue later. Ref: https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-operations.html
There was a problem hiding this comment.
+1, Is there anyway we could verify it by adding some security ITs? Not quite sure whether it's feasible in current plugin.
There was a problem hiding this comment.
@dai-chen @songkant-aws introduced both allow-listing and column redaction 95f0618
There was a problem hiding this comment.
I think it's still risky. Managed service customer can toggle the PPL_REST_REDACTION_ENABLED setting via our SQL setting endpoint. Please double check.
There was a problem hiding this comment.
I think it's still risky. Managed service customer can toggle the PPL_REST_REDACTION_ENABLED setting via our SQL setting endpoint. Please double check.
Good catch. Both settings were registered Dynamic, so a managed-service customer could flip PPL_REST_REDACTION_ENABLED off at runtime via _plugins/_query/settings or _cluster/settings. aef4663 updated it by dropping Setting.Property.Dynamic so both are now NodeScope-only (set in opensearch.yml). OpenSearch core rejects runtime updates on both paths.
| * on the Calcite path. | ||
| */ | ||
| @Getter | ||
| public class RestSourceTable extends AbstractOpenSearchTable { |
There was a problem hiding this comment.
Design question: Just wonder is it possible to follow our V2 engine approach to simplify and make this generic, like enable system table in Calcite instead of such specific table type. I recall in V2 we convert SHOW/DESC statement into regular SELECT on system table which is backed by cat index API.
There was a problem hiding this comment.
Design question: Just wonder is it possible to follow our V2 engine approach to simplify and make this generic, like enable system table in Calcite instead of such specific table type. I recall in V2 we convert SHOW/DESC statement into regular SELECT on system table which is backed by cat index API.
Yes, we can introduce an "system table" abstraction to cover both. However, the actual logic are quite diverged so the benefit is minimal. @dai-chen could you please confirm if 0819158 is what you meant or there's a better approach for refactor?
Good catch, revised on 2db942f |
Collapse the two near-duplicate Calcite scan hierarchies -- SHOW/DESCRIBE system tables and the rest command -- into one generic OpenSearchCatalogTable whose per-endpoint behavior is supplied by a pluggable CatalogSource. - OpenSearchSystemIndex + RestSourceTable -> one OpenSearchCatalogTable backed by SystemIndexCatalogSource / RestCatalogSource. - Two Abstract/Logical/Enumerable scans + two enumerators + two converter rules -> AbstractCalciteCatalogScan / CalciteLogicalCatalogScan / CalciteEnumerableCatalogScan (+ rest-only CalciteScannableCatalogScan) / OpenSearchCatalogEnumerator / EnumerableCatalogScanRule. - Concerns stay per-source: system tables keep the real V2 implement() path; rest is Calcite-only (implement() throws) and opts into Scannable for the collect short-circuit. No behavior change: dispatch, schemas, V2 support, and the Scannable marker are preserved; this is pure de-duplication of the Calcite scan plumbing. Verified: opensearch compileJava/compileTestJava green; ppl and integ-test test-compile green; affected unit tests pass. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit 0819158 |
Two dynamic cluster settings gate the rest command per deployment: - plugins.ppl.rest.redaction.enabled (default false): mask network identifiers in _cat/* cells and availability-zone names in _cluster/settings values. - plugins.ppl.rest.allowed_endpoints (default all): restrict which endpoints are served; an empty list disables the rest command. Both default to open-source parity: all endpoints served, no masking. Signed-off-by: Louis Chu <clingzhi@amazon.com>
The shared language-grammar carried REST and TIMEOUT lexer tokens and the restCommand/restArgument parser rules with no consumer: async-query-core has no rest visitor, and the rest command grammar lives in the ppl module. Remove the dead rules. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit 95f0618 |
|
Persistent review updated to latest commit 06ff820 |
|
Persistent review updated to latest commit d43f28c |
d43f28c to
a3b1a9e
Compare
Verify the rest command is subject to the security plugin fine grained access control: a caller without cluster:monitor privilege is denied the cat and cluster endpoints, a caller holding the privilege can run them, and the resolve index endpoint is filtered to the caller authorized indices. Test indices are created idempotently, and denials are asserted by the security denial reason in the response body because a denied action on the Calcite only rest path surfaces as a wrapped error carrying that reason. Calcite fallback is disabled so the denial reason is not replaced by an unsupported command error. Signed-off-by: Louis Chu <clingzhi@amazon.com>
a3b1a9e to
7d9df84
Compare
|
Persistent review updated to latest commit 7d9df84 |
plugins.ppl.rest.redaction.enabled and plugins.ppl.rest.allowed_endpoints were dynamic cluster settings, so they could be changed at runtime through _cluster/settings or the _plugins/_query/settings endpoint. On a managed deployment that let a caller disable redaction or widen the allow-list an operator had configured. Drop Setting.Property.Dynamic so both are node-level settings set in the node config; the engine rejects runtime updates on both paths. Register them without an update consumer and read the node-configured value. Signed-off-by: Louis Chu <clingzhi@amazon.com>
|
Persistent review updated to latest commit aef4663 |
Description
Add a leading
rest <endpoint>command that exposes a curated, read-only, fixed-schema set of in-cluster management endpoints as a PPL table, modeled as a system row source bridged through visitRelation (the same seam as describe and the system-index family), so it runs on the Calcite engine without the unsupported table-function path.Response redaction, endpoint allow-list, and access control
Two node-level settings (
NodeScope, set inopensearch.yml) let an operator harden the command; both default to native behavior, so existing clusters are unchanged. They are intentionally not dynamically updatable, so the hardening cannot be turned off at runtime via_cluster/settingsor_plugins/_query/settingsonce an operator has set it.plugins.ppl.rest.redaction.enabled(Boolean, defaultfalse): when enabled, masks network identifiers in/_cat/*output — IPv4, IPv6,inet[/...]addresses, EC2-style host names (ip-a-b-c-d), and availability-zone names — and availability-zone names in/_cluster/settingsvalues. Defaultfalsepreserves the native_catoutput (real values).plugins.ppl.rest.allowed_endpoints(List, default["*"]): restricts which registered endpoints the command serves.["*"]allows all (unchanged); an explicit subset allows only those (others return a clear "not enabled on this cluster" 400);[]disables the command entirely. Enforced at a single choke point inOpenSearchStorageEngine#getTable.Access control. The command is already subject to the security plugin's fine-grained access control with no special integration. Each endpoint dispatches a standard transport action (for example
cluster:monitor/nodes/stats,cluster:monitor/state,indices:admin/resolve/index) through the node client under the caller's identity, so the securityActionFilterauthorizes every call by action name. A caller lacking the privilege is rejected, exactly as on the native endpoint, and/_resolve/indexrequires theindices:admin/resolve/indexprivilege because the command resolves all indices. Document- and field-level security do not apply because the command issues no document searches, which matches the native_catand_clusterAPIs. Consequently the default settings reproduce native behavior 1:1 — real IPs and all endpoints are visible only to callers who already hold the correspondingcluster:monitor/*privilege — and redaction and the allow-list are additional opt-in hardening beyond native for managed or restricted deployments.Related Issues
Resolves #5597
Check List
--signoffor-s.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.