From 3dfae3b9841ab05afa7ac62eddc4e584f86df0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Wed, 30 Sep 2026 09:59:43 +0200 Subject: [PATCH 01/10] fix(search): honor the gRPC client TLS mode in the index command The index command read OC_GRPC_CLIENT_TLS_MODE with a meaning of its own: "insecure" dialed without TLS, and every other value, including the default and "off", dialed with verified TLS and ignored OC_GRPC_CLIENT_TLS_CACERT. Against the default setup, where the gRPC services run without TLS, it failed unless --insecure was passed, and against services with TLS enabled it only connected when their certificate was trusted by the system roots, never with the generated certificate or a private CA. Map the mode with pool.StringToTLSMode and dial with pool.NewConn, the mapping and dialer the other services' reva clients use: "off" dials without TLS, "insecure" uses TLS without verifying the certificate and "on" verifies it against the configured CA. --insecure still forces a connection without TLS, and then the mode is not read at all. --- services/search/pkg/command/index.go | 24 ++--- services/search/pkg/command/index_test.go | 124 ++++++++++++++++++++++ 2 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 services/search/pkg/command/index_test.go diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index c9b27814c1..dfc7eb6cfb 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -2,7 +2,6 @@ package command import ( "context" - "crypto/tls" "errors" "fmt" "io" @@ -14,11 +13,9 @@ import ( searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" "github.com/opencloud-eu/opencloud/services/search/pkg/config" "github.com/opencloud-eu/opencloud/services/search/pkg/config/parser" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" ) // Index is the entrypoint for the server command. @@ -45,16 +42,19 @@ func Index(cfg *config.Config) *cobra.Command { return fmt.Errorf("concurrency %d exceeds max allowed %d", concurrencyFlag, cfg.ReindexMaxConcurrency) } - var dialOpts []grpc.DialOption - if cfg.GRPCClientTLS.Mode == "insecure" || insecureFlag { - dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } else { - dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ - MinVersion: tls.VersionTLS12, - }))) + tlsMode := pool.TLSOff + if !insecureFlag { + mode, err := pool.StringToTLSMode(cfg.GRPCClientTLS.Mode) + if err != nil { + return err + } + tlsMode = mode } - conn, err := grpc.NewClient(endpointFlag, dialOpts...) + conn, err := pool.NewConn(endpointFlag, + pool.WithTLSMode(tlsMode), + pool.WithTLSCACert(cfg.GRPCClientTLS.CACert), + ) if err != nil { return fmt.Errorf("failed to dial %s: %w", endpointFlag, err) } diff --git a/services/search/pkg/command/index_test.go b/services/search/pkg/command/index_test.go new file mode 100644 index 0000000000..978ebb37b0 --- /dev/null +++ b/services/search/pkg/command/index_test.go @@ -0,0 +1,124 @@ +package command + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "path/filepath" + "testing" + "time" + + "github.com/opencloud-eu/opencloud/pkg/shared" + searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" + "github.com/opencloud-eu/opencloud/services/search/pkg/config" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +type fakeSearchProvider struct { + searchsvc.UnimplementedSearchProviderServer +} + +func (fakeSearchProvider) IndexSpace(_ *searchsvc.IndexSpaceRequest, stream grpc.ServerStreamingServer[searchsvc.IndexSpaceResponse]) error { + return stream.Send(&searchsvc.IndexSpaceResponse{SpaceId: "space-1", IndexedSpaces: 1, TotalSpaces: 1}) +} + +// startSearchServer serves a fake search service on a random local port and +// returns its address. +func startSearchServer(t *testing.T, opts ...grpc.ServerOption) string { + t.Helper() + lis, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + srv := grpc.NewServer(opts...) + searchsvc.RegisterSearchProviderServer(srv, fakeSearchProvider{}) + go func() { _ = srv.Serve(lis) }() + t.Cleanup(srv.Stop) + return lis.Addr().String() +} + +// selfSignedCert returns a certificate for 127.0.0.1 and the path of a PEM +// file holding it, to be used as the CA certificate by the client. +func selfSignedCert(t *testing.T) (tls.Certificate, string) { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "127.0.0.1"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IsCA: true, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + require.NoError(t, err) + keyDER, err := x509.MarshalECPrivateKey(key) + require.NoError(t, err) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) + cert, err := tls.X509KeyPair(certPEM, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})) + require.NoError(t, err) + caFile := filepath.Join(t.TempDir(), "ca.pem") + require.NoError(t, os.WriteFile(caFile, certPEM, 0o600)) + return cert, caFile +} + +func TestIndexHonorsGRPCClientTLSMode(t *testing.T) { + plain := startSearchServer(t) + cert, caFile := selfSignedCert(t) + withTLS := startSearchServer(t, grpc.Creds(credentials.NewServerTLSFromCert(&cert))) + + tests := []struct { + name string + endpoint string + mode string + caCert string + insecure bool + wantErr bool + errContains string + }{ + {name: "no TLS, mode unset", endpoint: plain}, + {name: "no TLS, mode off", endpoint: plain, mode: "off"}, + {name: "no TLS, --insecure", endpoint: plain, insecure: true}, + {name: "TLS, mode insecure", endpoint: withTLS, mode: "insecure"}, + {name: "TLS, mode on with CA certificate", endpoint: withTLS, mode: "on", caCert: caFile}, + {name: "no TLS, mode insecure", endpoint: plain, mode: "insecure", wantErr: true}, + {name: "TLS, mode off", endpoint: withTLS, mode: "off", wantErr: true}, + {name: "unknown mode", endpoint: plain, mode: "bogus", wantErr: true, errContains: "unknown TLS mode"}, + {name: "unknown mode, --insecure", endpoint: plain, mode: "bogus", insecure: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &config.Config{ + GRPCClientTLS: &shared.GRPCClientTLS{Mode: tt.mode, CACert: tt.caCert}, + ReindexMaxConcurrency: 3, + } + cmd := Index(cfg) + require.NoError(t, cmd.Flags().Set("all-spaces", "true")) + require.NoError(t, cmd.Flags().Set("endpoint", tt.endpoint)) + if tt.insecure { + require.NoError(t, cmd.Flags().Set("insecure", "true")) + } + + err := cmd.RunE(cmd, nil) + switch { + case tt.errContains != "": + require.ErrorContains(t, err, tt.errContains) + case tt.wantErr: + require.Error(t, err) + default: + require.NoError(t, err) + } + }) + } +} From e44bd467f3dc86cfed9353ac97c7eabe39f88d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 09:44:27 +0200 Subject: [PATCH 02/10] fix(search): default the index endpoint to the configured gRPC address --endpoint defaulted to a hardcoded 127.0.0.1:9220, so a search service moved with SEARCH_GRPC_ADDR could only be reached by passing the flag. Use the configured address when the flag is not set. --- services/search/pkg/command/index.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index dfc7eb6cfb..73f7703f53 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -42,6 +42,10 @@ func Index(cfg *config.Config) *cobra.Command { return fmt.Errorf("concurrency %d exceeds max allowed %d", concurrencyFlag, cfg.ReindexMaxConcurrency) } + if !cmd.Flags().Changed("endpoint") { + endpointFlag = cfg.GRPC.Addr + } + tlsMode := pool.TLSOff if !insecureFlag { mode, err := pool.StringToTLSMode(cfg.GRPCClientTLS.Mode) @@ -122,8 +126,8 @@ func Index(cfg *config.Config) *cobra.Command { ) indexCmd.Flags().String( "endpoint", - "127.0.0.1:9220", - "the address of the search service gRPC endpoint.", + "", + "the address of the search service gRPC endpoint. Defaults to the service's configured gRPC address (SEARCH_GRPC_ADDR).", ) indexCmd.Flags().Bool( "insecure", From 3e5e5439a340acb9bd2cfd91fa47046c73abe656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 09:44:27 +0200 Subject: [PATCH 03/10] fix(search): stop recommending --insecure for re-indexing With the client TLS mode honored, the default setup no longer needs --insecure, and with TLS enabled for gRPC the flag makes the command fail, since it turns TLS off. Drop it from the README examples and from the two re-index hints the service logs. MIGRATION.md keeps it: its section covers upgrading to 8.0.x, which still needs the flag. --- services/search/README.md | 4 ++-- services/search/pkg/mapping/reconcile.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/search/README.md b/services/search/README.md index d0f650e528..96bdc00c21 100644 --- a/services/search/README.md +++ b/services/search/README.md @@ -124,14 +124,14 @@ opencloud search index --space $SPACE_ID It can also be used to re-index all spaces: ```shell -opencloud search index --all-spaces --insecure +opencloud search index --all-spaces ``` Please note that a reindex only picks up new or changed files. Files that have already been indexed are not scanned again, even if the configuration or the whole extractor has been changed. To force a full rescan (re-running the extractor on every file) you need to use the `force-rescan` flag: ```shell -opencloud search index --all-spaces --force-rescan --insecure +opencloud search index --all-spaces --force-rescan ``` ## Metrics diff --git a/services/search/pkg/mapping/reconcile.go b/services/search/pkg/mapping/reconcile.go index 7490998a67..82cc46feb6 100644 --- a/services/search/pkg/mapping/reconcile.go +++ b/services/search/pkg/mapping/reconcile.go @@ -27,7 +27,7 @@ func Reconcile(index string, r SchemaReconciler, logger log.Logger) (Classificat case VerdictAdditive: persisted, err := r.ApplyAdditive() if persisted { - logger.Warn().Strs("fields", classification.NewFields).Str("index", index).Msg("extended the search index mapping with new fields; documents indexed before the upgrade do not contain them and queries on these fields will miss those documents until they are re-indexed; to re-index everything run: opencloud search index --all-spaces --force-rescan --insecure") + logger.Warn().Strs("fields", classification.NewFields).Str("index", index).Msg("extended the search index mapping with new fields; documents indexed before the upgrade do not contain them and queries on these fields will miss those documents until they are re-indexed; to re-index everything run: opencloud search index --all-spaces --force-rescan") } if err != nil { return classification, err @@ -40,5 +40,5 @@ func Reconcile(index string, r SchemaReconciler, logger log.Logger) (Classificat // LogNewIndexCreated logs that a fresh, empty index was created and how to // backfill it. The create path does not run through Reconcile. func LogNewIndexCreated(logger log.Logger, index string) { - logger.Info().Str("index", index).Msg("created a new empty search index; if this OpenCloud instance already held files, they are not in it yet, index them by running: opencloud search index --all-spaces --force-rescan --insecure") + logger.Info().Str("index", index).Msg("created a new empty search index; if this OpenCloud instance already held files, they are not in it yet, index them by running: opencloud search index --all-spaces --force-rescan") } From 828f7fe8f1a22a668ddcb2a69187af01ac476558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 09:44:27 +0200 Subject: [PATCH 04/10] test(search): rewrite the index command test with Ginkgo Run the command against a search service without TLS only: what the command decides is whether to turn TLS off, how the mode maps and which endpoint it dials. What pool.NewConn does with each mode belongs to reva. Also cover the endpoint default. --- .../search/pkg/command/command_suite_test.go | 13 ++ services/search/pkg/command/index_test.go | 153 ++++++------------ 2 files changed, 64 insertions(+), 102 deletions(-) create mode 100644 services/search/pkg/command/command_suite_test.go diff --git a/services/search/pkg/command/command_suite_test.go b/services/search/pkg/command/command_suite_test.go new file mode 100644 index 0000000000..c76f359e41 --- /dev/null +++ b/services/search/pkg/command/command_suite_test.go @@ -0,0 +1,13 @@ +package command_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCommand(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Command Suite") +} diff --git a/services/search/pkg/command/index_test.go b/services/search/pkg/command/index_test.go index 978ebb37b0..4d8fa51fe7 100644 --- a/services/search/pkg/command/index_test.go +++ b/services/search/pkg/command/index_test.go @@ -1,26 +1,16 @@ -package command +package command_test import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" "net" - "os" - "path/filepath" - "testing" - "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "google.golang.org/grpc" "github.com/opencloud-eu/opencloud/pkg/shared" searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" + "github.com/opencloud-eu/opencloud/services/search/pkg/command" "github.com/opencloud-eu/opencloud/services/search/pkg/config" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" ) type fakeSearchProvider struct { @@ -31,94 +21,53 @@ func (fakeSearchProvider) IndexSpace(_ *searchsvc.IndexSpaceRequest, stream grpc return stream.Send(&searchsvc.IndexSpaceResponse{SpaceId: "space-1", IndexedSpaces: 1, TotalSpaces: 1}) } -// startSearchServer serves a fake search service on a random local port and -// returns its address. -func startSearchServer(t *testing.T, opts ...grpc.ServerOption) string { - t.Helper() - lis, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - srv := grpc.NewServer(opts...) - searchsvc.RegisterSearchProviderServer(srv, fakeSearchProvider{}) - go func() { _ = srv.Serve(lis) }() - t.Cleanup(srv.Stop) - return lis.Addr().String() -} +var _ = Describe("Index", func() { + var addr string -// selfSignedCert returns a certificate for 127.0.0.1 and the path of a PEM -// file holding it, to be used as the CA certificate by the client. -func selfSignedCert(t *testing.T) (tls.Certificate, string) { - t.Helper() - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - require.NoError(t, err) - tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: "127.0.0.1"}, - IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - IsCA: true, - BasicConstraintsValid: true, + // runIndex runs the index command against a search service without TLS + // that listens on the configured gRPC address. + runIndex := func(mode string, args ...string) error { + cfg := &config.Config{ + GRPC: config.GRPCConfig{Addr: addr}, + GRPCClientTLS: &shared.GRPCClientTLS{Mode: mode}, + ReindexMaxConcurrency: 3, + } + cmd := command.Index(cfg) + Expect(cmd.ParseFlags(append([]string{"--all-spaces"}, args...))).To(Succeed()) + return cmd.RunE(cmd, nil) } - der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) - require.NoError(t, err) - keyDER, err := x509.MarshalECPrivateKey(key) - require.NoError(t, err) - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) - cert, err := tls.X509KeyPair(certPEM, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})) - require.NoError(t, err) - caFile := filepath.Join(t.TempDir(), "ca.pem") - require.NoError(t, os.WriteFile(caFile, certPEM, 0o600)) - return cert, caFile -} -func TestIndexHonorsGRPCClientTLSMode(t *testing.T) { - plain := startSearchServer(t) - cert, caFile := selfSignedCert(t) - withTLS := startSearchServer(t, grpc.Creds(credentials.NewServerTLSFromCert(&cert))) + BeforeEach(func() { + lis, err := net.Listen("tcp", "127.0.0.1:0") + Expect(err).ToNot(HaveOccurred()) + srv := grpc.NewServer() + searchsvc.RegisterSearchProviderServer(srv, fakeSearchProvider{}) + go func() { _ = srv.Serve(lis) }() + DeferCleanup(srv.Stop) + addr = lis.Addr().String() + }) - tests := []struct { - name string - endpoint string - mode string - caCert string - insecure bool - wantErr bool - errContains string - }{ - {name: "no TLS, mode unset", endpoint: plain}, - {name: "no TLS, mode off", endpoint: plain, mode: "off"}, - {name: "no TLS, --insecure", endpoint: plain, insecure: true}, - {name: "TLS, mode insecure", endpoint: withTLS, mode: "insecure"}, - {name: "TLS, mode on with CA certificate", endpoint: withTLS, mode: "on", caCert: caFile}, - {name: "no TLS, mode insecure", endpoint: plain, mode: "insecure", wantErr: true}, - {name: "TLS, mode off", endpoint: withTLS, mode: "off", wantErr: true}, - {name: "unknown mode", endpoint: plain, mode: "bogus", wantErr: true, errContains: "unknown TLS mode"}, - {name: "unknown mode, --insecure", endpoint: plain, mode: "bogus", insecure: true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg := &config.Config{ - GRPCClientTLS: &shared.GRPCClientTLS{Mode: tt.mode, CACert: tt.caCert}, - ReindexMaxConcurrency: 3, - } - cmd := Index(cfg) - require.NoError(t, cmd.Flags().Set("all-spaces", "true")) - require.NoError(t, cmd.Flags().Set("endpoint", tt.endpoint)) - if tt.insecure { - require.NoError(t, cmd.Flags().Set("insecure", "true")) - } + DescribeTable("connects without TLS", + func(mode string, args ...string) { + Expect(runIndex(mode, args...)).To(Succeed()) + }, + Entry("when the mode is unset", ""), + Entry("when the mode is off", "off"), + Entry("when --insecure is passed", "", "--insecure"), + Entry("when --insecure is passed with an unknown mode", "bogus", "--insecure"), + ) - err := cmd.RunE(cmd, nil) - switch { - case tt.errContains != "": - require.ErrorContains(t, err, tt.errContains) - case tt.wantErr: - require.Error(t, err) - default: - require.NoError(t, err) - } - }) - } -} + It("uses TLS when the mode is insecure", func() { + Expect(runIndex("insecure")).To(HaveOccurred()) + }) + + It("rejects an unknown mode", func() { + Expect(runIndex("bogus")).To(MatchError(ContainSubstring("unknown TLS mode"))) + }) + + It("prefers --endpoint over the configured gRPC address", func() { + endpoint := addr + addr = "127.0.0.1:1" + Expect(runIndex("", "--endpoint", endpoint)).To(Succeed()) + }) +}) From f6bd4364287f0f74bb74e16e555857fa5ea195b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 12:37:26 +0200 Subject: [PATCH 05/10] docs(search): cover every 8.x release in the v7 migration [docs-only] The section was titled v7.x.x to v8.0.0 and told everyone to pass --insecure, which only 8.0.0 and 8.0.1 need. Title it v7.x.x to v8.x.x, drop the flag from the commands and say in an IMPORTANT block which releases still need it. --- services/search/MIGRATION.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/search/MIGRATION.md b/services/search/MIGRATION.md index 3a026b18c6..12ad80a673 100644 --- a/services/search/MIGRATION.md +++ b/services/search/MIGRATION.md @@ -5,7 +5,12 @@ leaves the old one untouched. The service starts normally, but the new index is empty: search finds nothing until it is filled. The old index stays around until you remove it. -## v7.x.x to v8.0.0 +## v7.x.x to v8.x.x + +> [!IMPORTANT] +> On 8.0.0 and 8.0.1, `opencloud search index` needs `--insecure` in the +> default setup, where the search service runs gRPC without TLS: add it to the +> commands below. Later releases don't need it. ### OpenSearch @@ -13,7 +18,7 @@ Fill the new index by indexing all spaces again: ```shell # the service keeps running while it happens -opencloud search index --all-spaces --insecure +opencloud search index --all-spaces ``` Once the new index is filled, every index but the one with the highest @@ -31,7 +36,7 @@ The new index is a directory next to the old `bleve` one, both in bleve index cannot be copied, index all spaces again: ```shell -opencloud search index --all-spaces --insecure +opencloud search index --all-spaces ``` Once the new index is filled, every directory but the one with the highest From d071dc78ed5655631bfadbf58c01ed1d13689d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 15:58:10 +0200 Subject: [PATCH 06/10] test(search): pin the TLS failure and declare the plaintext server The insecure-mode spec only asserted an error, so against main it passed on a refused connection to the hardcoded endpoint. Match the TLS handshake error instead. Pass insecure credentials to the test server explicitly, which is what it already did, so the static analysis no longer reports a server without credentials. --- services/search/pkg/command/index_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/search/pkg/command/index_test.go b/services/search/pkg/command/index_test.go index 4d8fa51fe7..f34d3558ba 100644 --- a/services/search/pkg/command/index_test.go +++ b/services/search/pkg/command/index_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "github.com/opencloud-eu/opencloud/pkg/shared" searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" @@ -40,7 +41,7 @@ var _ = Describe("Index", func() { BeforeEach(func() { lis, err := net.Listen("tcp", "127.0.0.1:0") Expect(err).ToNot(HaveOccurred()) - srv := grpc.NewServer() + srv := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) searchsvc.RegisterSearchProviderServer(srv, fakeSearchProvider{}) go func() { _ = srv.Serve(lis) }() DeferCleanup(srv.Stop) @@ -58,7 +59,7 @@ var _ = Describe("Index", func() { ) It("uses TLS when the mode is insecure", func() { - Expect(runIndex("insecure")).To(HaveOccurred()) + Expect(runIndex("insecure")).To(MatchError(ContainSubstring("first record does not look like a TLS handshake"))) }) It("rejects an unknown mode", func() { From 6211e31b5daa2e74b8b6cef46ca700dec772a041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 15:58:20 +0200 Subject: [PATCH 07/10] refactor(search): rename the dialed endpoint and group the reva import After the fallback the variable holds the configured address rather than the flag, and the reva import belongs with the third-party ones, as in server.go. The flag help also says that with mode on the server certificate must be valid for that address. --- services/search/pkg/command/index.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index 73f7703f53..0da05850ba 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -13,8 +13,8 @@ import ( searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" "github.com/opencloud-eu/opencloud/services/search/pkg/config" "github.com/opencloud-eu/opencloud/services/search/pkg/config/parser" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/spf13/cobra" ) @@ -31,7 +31,7 @@ func Index(cfg *config.Config) *cobra.Command { allSpacesFlag, _ := cmd.Flags().GetBool("all-spaces") spaceFlag, _ := cmd.Flags().GetString("space") forceRescanFlag, _ := cmd.Flags().GetBool("force-rescan") - endpointFlag, _ := cmd.Flags().GetString("endpoint") + endpoint, _ := cmd.Flags().GetString("endpoint") insecureFlag, _ := cmd.Flags().GetBool("insecure") concurrencyFlag, _ := cmd.Flags().GetInt("concurrency") @@ -43,7 +43,7 @@ func Index(cfg *config.Config) *cobra.Command { } if !cmd.Flags().Changed("endpoint") { - endpointFlag = cfg.GRPC.Addr + endpoint = cfg.GRPC.Addr } tlsMode := pool.TLSOff @@ -55,12 +55,12 @@ func Index(cfg *config.Config) *cobra.Command { tlsMode = mode } - conn, err := pool.NewConn(endpointFlag, + conn, err := pool.NewConn(endpoint, pool.WithTLSMode(tlsMode), pool.WithTLSCACert(cfg.GRPCClientTLS.CACert), ) if err != nil { - return fmt.Errorf("failed to dial %s: %w", endpointFlag, err) + return fmt.Errorf("failed to dial %s: %w", endpoint, err) } defer conn.Close() @@ -127,7 +127,7 @@ func Index(cfg *config.Config) *cobra.Command { indexCmd.Flags().String( "endpoint", "", - "the address of the search service gRPC endpoint. Defaults to the service's configured gRPC address (SEARCH_GRPC_ADDR).", + "the address of the search service gRPC endpoint. Defaults to the service's configured gRPC address (SEARCH_GRPC_ADDR). With OC_GRPC_CLIENT_TLS_MODE=on the server certificate must be valid for this address.", ) indexCmd.Flags().Bool( "insecure", From 6a83068be231ed9fdbb822478df08483ca96741a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 15:58:20 +0200 Subject: [PATCH 08/10] docs(search): explain which address mode on verifies [docs-only] With OC_GRPC_CLIENT_TLS_MODE=on the certificate is checked against the dialed address, which is now SEARCH_GRPC_ADDR by default. A bind address such as 0.0.0.0:9220 then fails verification unless --endpoint names a host the certificate is valid for. --- services/search/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/search/README.md b/services/search/README.md index 96bdc00c21..9f7adb0bd5 100644 --- a/services/search/README.md +++ b/services/search/README.md @@ -134,6 +134,8 @@ Please note that a reindex only picks up new or changed files. Files that have a opencloud search index --all-spaces --force-rescan ``` +The command connects to the service's gRPC address (`SEARCH_GRPC_ADDR`) unless `--endpoint` is given, with the TLS mode set in `OC_GRPC_CLIENT_TLS_MODE`. With `on`, the server certificate is verified against that address, so if `SEARCH_GRPC_ADDR` is a bind address such as `0.0.0.0:9220`, pass `--endpoint` with a host name the certificate is valid for. + ## Metrics The search service exposes the following prometheus metrics at `/metrics` (as configured using the `SEARCH_DEBUG_ADDR` env var): From 5c39a40f6b7ede692655fe5cd111c36ee1c6ca86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 18:41:53 +0200 Subject: [PATCH 09/10] refactor(search): restore the endpointFlag name The Flag suffix is the convention for the command's flag variables, not only for booleans. --- services/search/pkg/command/index.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index 0da05850ba..2363d2a73c 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -31,7 +31,7 @@ func Index(cfg *config.Config) *cobra.Command { allSpacesFlag, _ := cmd.Flags().GetBool("all-spaces") spaceFlag, _ := cmd.Flags().GetString("space") forceRescanFlag, _ := cmd.Flags().GetBool("force-rescan") - endpoint, _ := cmd.Flags().GetString("endpoint") + endpointFlag, _ := cmd.Flags().GetString("endpoint") insecureFlag, _ := cmd.Flags().GetBool("insecure") concurrencyFlag, _ := cmd.Flags().GetInt("concurrency") @@ -43,7 +43,7 @@ func Index(cfg *config.Config) *cobra.Command { } if !cmd.Flags().Changed("endpoint") { - endpoint = cfg.GRPC.Addr + endpointFlag = cfg.GRPC.Addr } tlsMode := pool.TLSOff @@ -55,12 +55,12 @@ func Index(cfg *config.Config) *cobra.Command { tlsMode = mode } - conn, err := pool.NewConn(endpoint, + conn, err := pool.NewConn(endpointFlag, pool.WithTLSMode(tlsMode), pool.WithTLSCACert(cfg.GRPCClientTLS.CACert), ) if err != nil { - return fmt.Errorf("failed to dial %s: %w", endpoint, err) + return fmt.Errorf("failed to dial %s: %w", endpointFlag, err) } defer conn.Close() From 283bbe4ee3a4a99a8d7da27213457a6bce39a544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M=2E=20Requena=20Plens?= Date: Sat, 3 Oct 2026 18:41:53 +0200 Subject: [PATCH 10/10] docs(search): extend the --insecure note to 8.1.x [docs-only] 8.1.0 ships before this fix, so it still needs the flag too. --- services/search/MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/search/MIGRATION.md b/services/search/MIGRATION.md index 12ad80a673..9d9589be0f 100644 --- a/services/search/MIGRATION.md +++ b/services/search/MIGRATION.md @@ -8,7 +8,7 @@ until you remove it. ## v7.x.x to v8.x.x > [!IMPORTANT] -> On 8.0.0 and 8.0.1, `opencloud search index` needs `--insecure` in the +> On 8.0.x and 8.1.x, `opencloud search index` needs `--insecure` in the > default setup, where the search service runs gRPC without TLS: add it to the > commands below. Later releases don't need it.