Skip to content
Merged
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
26 changes: 26 additions & 0 deletions pkg/proxy/discovery/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,35 @@ func (p *Proxy) CollectMetadata(ctx context.Context) (map[string]any, error) {
if versions := p.packVersions(); len(versions) > 0 {
meta["pack_versions"] = versions
}
if scope := p.scope(); len(scope) > 0 {
meta["allowed_cidrs"] = scope
}
return meta, nil
}

// scope reports the CIDRs this datasource will accept work for.
//
// Without it the server knows a discovery datasource exists but not what it
// covers, which breaks two things downstream. A scheduler has to be told the
// ranges separately, so server and agent can disagree — the server asks for a
// sweep the agent refuses as out of scope, and the refusal looks like a bug in
// discovery rather than a configuration mismatch. And the coverage report cannot be
// computed at all: "how many machines are there" needs the intended
// denominator, not just whatever happened to answer.
func (p *Proxy) scope() []string {
p.mu.RLock()
defer p.mu.RUnlock()

out := make([]string, 0, len(p.allowedNets)+len(p.allowedHosts))
for _, n := range p.allowedNets {
out = append(out, n.String())
}
// Bare hostnames are kept as configured; they are resolved per request
// rather than at configure time, so there is no address to report.
out = append(out, p.allowedHosts...)
return out
}

// packVersionPattern matches cached pack filenames (linux-inventory-v<N>.yaml),
// mirroring the path resolvePack reads.
var packVersionPattern = regexp.MustCompile(`^linux-inventory-v(\d+)\.yaml$`)
Expand Down
45 changes: 45 additions & 0 deletions pkg/proxy/discovery/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,3 +640,48 @@ func TestDiscoveryActionsReturnStructuredResult(t *testing.T) {
})
}
}

// The server needs to know what each collector covers. Without it a scheduler
// must be told the ranges separately and can drift out of step with the agent,
// and the coverage report has no denominator to work from.
func TestCollectMetadata_ReportsScope(t *testing.T) {
pubB64, priv := packPubKeyB64(t)
p, _ := newTestProxy(t, map[string]any{
"allowed_cidrs": []any{"10.0.1.0/24", "10.0.2.5", "db.corp.local"},
"pack_public_key": pubB64,
"pack_dir": writePackDir(t, validBody, priv, 3),
}, map[string]string{"username": "nudgebee-ro", "password": "x"})

meta, err := p.CollectMetadata(context.Background())
if err != nil {
t.Fatalf("collecting metadata: %v", err)
}

scope, ok := meta["allowed_cidrs"].([]string)
if !ok {
t.Fatalf("allowed_cidrs missing or wrong type: %#v", meta["allowed_cidrs"])
}

got := strings.Join(scope, ",")
for _, want := range []string{"10.0.1.0/24", "10.0.2.5/32", "db.corp.local"} {
if !strings.Contains(got, want) {
t.Errorf("scope %v is missing %s", scope, want)
}
}
Comment thread
mayankpande88 marked this conversation as resolved.
}

// An unrestricted datasource reports no scope rather than an empty list, so
// the server can tell "covers everything" from "covers nothing".
func TestCollectMetadata_OmitsScopeWhenUnrestricted(t *testing.T) {
p, _ := newTestProxy(t, map[string]any{}, map[string]string{
"username": "nudgebee-ro", "password": "x",
})

meta, err := p.CollectMetadata(context.Background())
if err != nil {
t.Fatalf("collecting metadata: %v", err)
}
if _, present := meta["allowed_cidrs"]; present {
t.Errorf("unrestricted datasource reported a scope: %#v", meta["allowed_cidrs"])
}
}
Loading