Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# If you're not using docker
KIND_EXPERIMENTAL_PROVIDER=podman
# Path to your local Platform Mesh setup (used by hack/kubeconfig-copy.sh)
PORTAL_DIRECTORY=/path/to/your/local-setup
151 changes: 151 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
> [!WARNING]
> This repository is under development and not ready for productive use. It is in an alpha stage. APIs and concepts may change on short notice, including breaking changes.

# Platform Mesh - search-service
![Build Status](https://github.com/platform-mesh/search/actions/workflows/pipeline.yml/badge.svg)

## Description

The platform-mesh `search-service` provides a REST API to query resources indexed in OpenSearch and post-filter results through OpenFGA authorization checks.

The service is organization-aware and derives org context from the request host. It resolves the active SearchIndex in KCP (`root:orgs`) and uses `status.indexName` as source of truth for the OpenSearch index.

## Features

- REST endpoints:
- `GET /rest/v1/search`
- `GET /rest/v1/search/resources`
- `GET /rest/v1/search/filter-values`
- Free-text search in OpenSearch with stable cursor pagination (`search_after`)
- OpenFGA post-filtering (`relation=get`) with fail-closed behavior for incomplete auth context
- Org-aware context + KCP token/org access pre-check
- SearchIndex-driven resource/field metadata:
- `defaultFields` drive searchable fields
- `filterableFields` drive exact-match filters
- `semanticFields` are exposed as metadata (no semantic query mode yet)
- Health endpoints: `/healthz`, `/readyz`

## API

### Search endpoint

`GET /rest/v1/search?q=<query>&limit=<n>&cursor=<opaque>&resource=<plural>&filter.<field>=<value>`

Query params:

- `q` (required): free-text query
- `resource` (optional): plural resource name; if omitted, searches across all resources
- `filter.<field>` (optional, repeatable): exact-match filters; requires `resource`
- `limit` (optional): default `20`, max `100`
- `cursor` (optional): opaque pagination cursor

Response shape:

- `results[]` with compact fields (`id`, `score`, `kind`, `name`, `namespace`, `apiGroup`, `apiVersion`, `workspacePath`, `clusterName`, `organizationId`, `organizationName`, `accountId`, `accountName`)
- `results[].resource` indicates which resource index produced the hit
- `source` containing the raw indexed document source per hit
- `nextCursor` for pagination

### Resource metadata endpoint

`GET /rest/v1/search/resources`

Returns all searchable resources for the org with:

- `resource`
- `defaultFields`
- `filterableFields`
- `semanticFields`

### Filter values endpoint

`GET /rest/v1/search/filter-values?resource=<plural>&field=<filterable>&q=<optional>&filter.<field>=<value>`

Returns distinct authorized values for one filterable field within a single resource.

## Getting Started

### Requirements

- Go `1.25+` (see [go.mod](go.mod))
- Access to:
- KCP API (for org access check + SearchIndex resolution)
- OpenSearch
- OpenFGA gRPC endpoint

### Run locally

Example:

```bash
export OPENSEARCH_USERNAME=<username>
export OPENSEARCH_PASSWORD=<password>

go run . serve \
--kubeconfig ~/.kube/config \
--is-local=true \
--opensearch-url http://localhost:9200 \
--openfga-grpc-addr localhost:8081
```

### Local Development Mode (`--is-local=true`)

Use `--is-local=true` for local development to match the local behavior of `kubernetes-graphql-gateway`.

When enabled:

- org context is still derived from host (`localhost` is mapped to `--local-development-org`)
- JWT claims are still parsed for user/tenant context
- KCP org token validation (`ValidateTokenForOrg`) is bypassed

This is intended for local/dev usage only. Keep `--is-local=false` for production-like environments.

### Configuration flags

Main runtime flags (with defaults):

- `--port` (default: `8080`)
- `--opensearch-url` (default: `http://opensearch.platform-mesh-system.svc.cluster.local:9200`)
- `--opensearch-username` (default: value of env `OPENSEARCH_USERNAME`)
- `--opensearch-password` (default: value of env `OPENSEARCH_PASSWORD`)
- `--opensearch-insecure` (default: `false`)
- `--opensearch-timeout` (default: `10s`)
- `--openfga-grpc-addr` (default: `openfga:8081`)
- `--searchindex-workspace-path` (default: `root:orgs`)
- `--searchindex-group` (default: `core.platform-mesh.io`)
- `--searchindex-version` (default: `v1alpha1`)
- `--searchindex-resource` (default: `searchindexes`)
- `--search-default-limit` (default: `20`)
- `--search-max-limit` (default: `100`)
- `--search-fetch-batch-size` (default: `100`)
- `--search-max-scanned-hits` (default: `1000`)
- `--is-local` (default: `false`) enables local development behavior in auth middleware
- `--local-development-org` (default: env `SEARCH_LOCAL_ORG`, fallback `local`) org used when host is `localhost`

Global flags from `golang-commons` are also available (e.g. logging and kubeconfig related flags).

### Run tests

```bash
go test ./...
```

## Security Notes

- JWT signature validation is expected to happen upstream (gateway/mesh).
- The service consumes parsed claims from context (`mail`, fallback `sub`).
- Search hits missing required authorization hierarchy fields are dropped (fail-closed).

## Releasing

Releases are performed via GitHub Actions workflows.

## Contributing

Contributions are welcome via pull requests in the Platform Mesh GitHub organization.

## Code of Conduct

Please refer to our [Code of Conduct](https://github.com/platform-mesh/.github/blob/main/CODE_OF_CONDUCT.md) for expected conduct when contributing.

<p align="center"><img alt="Bundesministerium für Wirtschaft und Energie (BMWE)-EU funding logo" src="https://apeirora.eu/assets/img/BMWK-EU.png" width="400"/></p>
8 changes: 7 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ var serverCmd = &cobra.Command{
if err != nil {
log.Fatal().Err(err).Msg("failed to create OpenFGA client")
}
log.Info().
Str("openSearchURL", serviceCfg.OpenSearch.URL).
Str("openFGAGRPCAddr", serviceCfg.OpenFGA.GRPCAddr).
Str("searchIndexWorkspacePath", serviceCfg.SearchIndex.WorkspacePath).
Str("searchIndexGVR", fmt.Sprintf("%s/%s/%s", serviceCfg.SearchIndex.Group, serviceCfg.SearchIndex.Version, serviceCfg.SearchIndex.Resource)).
Msg("search service backend configuration")

metrics := observability.NewMetrics()
svc := searchservice.NewService(
Expand All @@ -82,7 +88,7 @@ var serverCmd = &cobra.Command{
)

mws := cmw.CreateMiddleware(log, true)
orgCtxMW := lmw.NewOrgContextMiddleware(orgValidator)
orgCtxMW := lmw.NewOrgContextMiddleware(orgValidator, defaultCfg.IsLocal, serviceCfg.LocalDevelopmentOrg)
mws = append(mws, orgCtxMW.SetRequestContext())

r := router.CreateRouter(svc, mws)
Expand Down
35 changes: 18 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
module github.com/platform-mesh/search

go 1.25.0
go 1.26

require (
github.com/go-chi/chi/v5 v5.2.5
github.com/openfga/api/proto v0.0.0-20260217232149-f917ddb000ce
github.com/platform-mesh/golang-commons v0.13.12
github.com/openfga/api/proto v0.0.0-20260319214821-f153694bfc20
github.com/platform-mesh/golang-commons v0.13.24-0.20260331080836-cc42019dfb2e
github.com/platform-mesh/search-operator v0.1.2-0.20260413065247-a4a8625c1a9c
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
google.golang.org/grpc v1.79.3
k8s.io/client-go v0.35.2
google.golang.org/grpc v1.80.0
k8s.io/apimachinery v0.35.3
k8s.io/client-go v0.35.3
)

require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getsentry/sentry-go v0.43.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
Expand All @@ -26,7 +28,6 @@ require (
github.com/go-logr/zerologr v1.2.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
Expand All @@ -35,7 +36,7 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/rs/zerolog v1.35.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.42.0 // indirect
Expand All @@ -47,20 +48,20 @@ require (
go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/apimachinery v0.35.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect
sigs.k8s.io/controller-runtime v0.23.3 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
Expand Down
Loading
Loading