Skip to content

Commit 5d33954

Browse files
committed
feat(searchengine): env-gated TLS skip verify for ES connections
Adds an opt-in tlsSkipVerify bool to searchengine.New, plumbed from each service's config: - search-service: SEARCH_TLS_SKIP_VERIFY (default false) - search-sync-worker: SEARCH_TLS_SKIP_VERIFY (default false) Default-off keeps prod safe; ops opts in per environment for self-signed/internal ES clusters. When false, the factory uses the standard ES client transport — same behavior as before this PR. When true, clones http.DefaultTransport (preserving ProxyFromEnvironment, dial/TLS-handshake timeouts, HTTP/2, idle-conn tuning) and overrides only TLSClientConfig with InsecureSkipVerify=true and MinVersion=TLS 1.2, guarding the type assertion on http.DefaultTransport so we error out cleanly if a middleware (e.g. OTel) has replaced it. Also enables gosec G402 narrowly in .golangci.yml so the //nolint:gosec annotation in pkg/oidc and pkg/searchengine actually suppresses a real rule, and any future unannotated InsecureSkipVerify is rejected at lint time. Includes a goimports-only struct alignment tweak in room-worker/integration_test.go picked up while running make fmt — no behavior change. https://claude.ai/code/session_01UkLD7hpaypxjeh5zbEWTjp
1 parent 31febfd commit 5d33954

5 files changed

Lines changed: 49 additions & 22 deletions

File tree

.golangci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@ linters:
99
- nilerr
1010
- bodyclose
1111
- exhaustive
12+
- gosec
1213
settings:
1314
exhaustive:
1415
default-signifies-exhaustive: true
1516
gocritic:
1617
enabled-tags:
1718
- diagnostic
1819
- performance
20+
gosec:
21+
# Narrowly scoped to G402 (TLS InsecureSkipVerify) so the existing
22+
# //nolint:gosec annotations in pkg/oidc and pkg/searchengine are
23+
# actually suppressing a real rule, and any new unannotated
24+
# InsecureSkipVerify is rejected at lint time.
25+
includes:
26+
- G402
1927
exclusions:
2028
presets:
2129
- std-error-handling

pkg/searchengine/factory.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,36 @@ package searchengine
22

33
import (
44
"context"
5+
"crypto/tls"
56
"fmt"
7+
"net/http"
68
"time"
79

810
"github.com/elastic/go-elasticsearch/v8"
911
)
1012

1113
// New creates a SearchEngine for the given backend ("elasticsearch" or "opensearch").
12-
// It verifies connectivity via Ping before returning.
13-
func New(ctx context.Context, backend, url string) (SearchEngine, error) {
14+
// It verifies connectivity via Ping before returning. When tlsSkipVerify is
15+
// true, server certificate verification is disabled — intended for
16+
// self-signed/internal ES clusters; MUST stay false in production.
17+
func New(ctx context.Context, backend, url string, tlsSkipVerify bool) (SearchEngine, error) {
1418
var transport Transporter
1519
switch backend {
1620
case "elasticsearch":
17-
client, err := elasticsearch.NewClient(elasticsearch.Config{Addresses: []string{url}})
21+
esCfg := elasticsearch.Config{Addresses: []string{url}}
22+
if tlsSkipVerify {
23+
dt, ok := http.DefaultTransport.(*http.Transport)
24+
if !ok {
25+
return nil, fmt.Errorf("create elasticsearch client: http.DefaultTransport is not *http.Transport")
26+
}
27+
httpTransport := dt.Clone()
28+
httpTransport.TLSClientConfig = &tls.Config{
29+
InsecureSkipVerify: true, //nolint:gosec // intentional: opt-in via config for self-signed ES certs
30+
MinVersion: tls.VersionTLS12,
31+
}
32+
esCfg.Transport = httpTransport
33+
}
34+
client, err := elasticsearch.NewClient(esCfg)
1835
if err != nil {
1936
return nil, fmt.Errorf("create elasticsearch client: %w", err)
2037
}

room-worker/integration_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -752,9 +752,9 @@ func TestProcessAddMembers_OutboxPerRemoteSite(t *testing.T) {
752752
})
753753
// Owner sub.
754754
mustInsertSub(t, db, &model.Subscription{
755-
ID: idgen.GenerateUUIDv7(),
756-
User: model.SubscriptionUser{ID: "u_alice", Account: "alice"},
757-
RoomID: roomID, SiteID: "site-A", Name: roomName, RoomType: model.RoomTypeChannel,
755+
ID: idgen.GenerateUUIDv7(),
756+
User: model.SubscriptionUser{ID: "u_alice", Account: "alice"},
757+
RoomID: roomID, SiteID: "site-A", Name: roomName, RoomType: model.RoomTypeChannel,
758758
Roles: []model.Role{model.RoleOwner},
759759
JoinedAt: time.Now().UTC(),
760760
})
@@ -872,9 +872,9 @@ func TestProcessAddMembers_PublishesLocalInbox_Integration(t *testing.T) {
872872
CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
873873
})
874874
mustInsertSub(t, db, &model.Subscription{
875-
ID: idgen.GenerateUUIDv7(),
876-
User: model.SubscriptionUser{ID: "u_alice", Account: "alice"},
877-
RoomID: roomID, SiteID: "site-A", Name: roomName, RoomType: model.RoomTypeChannel,
875+
ID: idgen.GenerateUUIDv7(),
876+
User: model.SubscriptionUser{ID: "u_alice", Account: "alice"},
877+
RoomID: roomID, SiteID: "site-A", Name: roomName, RoomType: model.RoomTypeChannel,
878878
Roles: []model.Role{model.RoleOwner},
879879
JoinedAt: time.Now().UTC(),
880880
})
@@ -933,7 +933,7 @@ func TestProcessRemoveIndividual_PublishesLocalInbox_Integration(t *testing.T) {
933933
mustInsertSub(t, db, &model.Subscription{
934934
ID: idgen.GenerateUUIDv7(), User: model.SubscriptionUser{ID: "u_bob", Account: "bob"},
935935
RoomID: roomID, SiteID: "site-A", Name: "fed-room", RoomType: model.RoomTypeChannel,
936-
Roles: []model.Role{model.RoleMember}, JoinedAt: time.Now().UTC(),
936+
Roles: []model.Role{model.RoleMember}, JoinedAt: time.Now().UTC(),
937937
})
938938
_, err := db.Collection("room_members").InsertOne(ctx, model.RoomMember{
939939
ID: idgen.GenerateUUIDv7(), RoomID: roomID, Ts: time.Now().UTC(),

search-service/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
// ESConfig bundles the search backend knobs. BACKEND is the key
2323
// `pkg/searchengine.New` reads to choose between elasticsearch/opensearch.
2424
type ESConfig struct {
25-
URL string `env:"URL,required"`
26-
Backend string `env:"BACKEND" envDefault:"elasticsearch"`
25+
URL string `env:"URL,required"`
26+
Backend string `env:"BACKEND" envDefault:"elasticsearch"`
27+
TLSSkipVerify bool `env:"TLS_SKIP_VERIFY" envDefault:"false"`
2728
}
2829

2930
type ValkeyConfig struct {
@@ -80,7 +81,7 @@ func main() {
8081
os.Exit(1)
8182
}
8283

83-
engine, err := searchengine.New(ctx, cfg.ES.Backend, cfg.ES.URL)
84+
engine, err := searchengine.New(ctx, cfg.ES.Backend, cfg.ES.URL, cfg.ES.TLSSkipVerify)
8485
if err != nil {
8586
slog.Error("search engine connect failed", "error", err)
8687
os.Exit(1)

search-sync-worker/main.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ type bootstrapConfig struct {
4040
}
4141

4242
type config struct {
43-
NatsURL string `env:"NATS_URL,required"`
44-
NatsCredsFile string `env:"NATS_CREDS_FILE" envDefault:""`
45-
SiteID string `env:"SITE_ID,required"`
46-
SearchURL string `env:"SEARCH_URL,required"`
47-
SearchBackend string `env:"SEARCH_BACKEND" envDefault:"elasticsearch"`
48-
MsgIndexPrefix string `env:"MSG_INDEX_PREFIX,required"`
49-
SpotlightIndex string `env:"SPOTLIGHT_INDEX" envDefault:""`
50-
UserRoomIndex string `env:"USER_ROOM_INDEX" envDefault:""`
43+
NatsURL string `env:"NATS_URL,required"`
44+
NatsCredsFile string `env:"NATS_CREDS_FILE" envDefault:""`
45+
SiteID string `env:"SITE_ID,required"`
46+
SearchURL string `env:"SEARCH_URL,required"`
47+
SearchBackend string `env:"SEARCH_BACKEND" envDefault:"elasticsearch"`
48+
SearchTLSSkipVerify bool `env:"SEARCH_TLS_SKIP_VERIFY" envDefault:"false"`
49+
MsgIndexPrefix string `env:"MSG_INDEX_PREFIX,required"`
50+
SpotlightIndex string `env:"SPOTLIGHT_INDEX" envDefault:""`
51+
UserRoomIndex string `env:"USER_ROOM_INDEX" envDefault:""`
5152

5253
// FetchBatchSize is the maximum number of JetStream messages to pull
5354
// per Fetch() round-trip. Smaller values give lower latency per message
@@ -118,7 +119,7 @@ func main() {
118119
os.Exit(1)
119120
}
120121

121-
engine, err := searchengine.New(ctx, cfg.SearchBackend, cfg.SearchURL)
122+
engine, err := searchengine.New(ctx, cfg.SearchBackend, cfg.SearchURL, cfg.SearchTLSSkipVerify)
122123
if err != nil {
123124
slog.Error("search engine connect failed", "error", err)
124125
os.Exit(1)

0 commit comments

Comments
 (0)