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
4 changes: 3 additions & 1 deletion docs/egress.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ A missing policy on either side is an empty allow-list, not a pass: a tenant
without its own `egress` block reaches nothing — upgrading, a tenant that
relied on inheriting its pool's policy must now declare one — so granting a tenant egress —
and the secret injection that rides it — is always an explicit act. Root
claims have no tenant layer and take the pool's policy whole. Secrets are
claims have no tenant layer and take the pool's policy whole; a promoted
template has no pool layer, so a tenant's claim of one takes the tenant's
policy alone (a root claim of a promoted template stays denied). Secrets are
registered separately and referenced by name — the value comes from the
environment, never the config file.

Expand Down
10 changes: 7 additions & 3 deletions sandboxd/pool/egress.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,22 @@ func (m *Manager) poolIntercepts(key types.PoolKey) bool {
return m.poolEgress[key].Intercepts()
}

// effectivePolicy resolves pool ∩ tenant; root has no tenant layer.
// effectivePolicy resolves pool ∩ tenant; root has no tenant layer, and a
// promoted template — a key no pool is configured for — has no pool layer.
func (m *Manager) effectivePolicy(sb *types.Sandbox) (egress.Evaluator, bool) {
m.mu.Lock()
defer m.mu.Unlock()
poolPol := m.poolEgress[sb.Key]
tenantPol := m.tenantEgress[sb.Tenant]
if poolPol == nil {
return nil, false
if m.pools[sb.Key] != nil || sb.Tenant == "" || tenantPol == nil {
return nil, false
}
return *tenantPol, true
}
if sb.Tenant == "" {
return *poolPol, true
}
tenantPol := m.tenantEgress[sb.Tenant]
if tenantPol == nil {
return nil, false
}
Expand Down
19 changes: 13 additions & 6 deletions sandboxd/pool/egress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,26 @@ func TestEffectivePolicyComposition(t *testing.T) {
cases := []struct {
name string
tenant string
pooled bool
pool, tnPol *egress.Policy
allow, deny string
wantArmed bool
}{
{"root takes the pool policy whole", "", both, nil, "a.test", "z.test", true},
{"root without a pool policy", "", nil, nil, "", "", false},
{"tenant intersects", "acme", both, tenantOnly, "b.test", "a.test", true}, // a.test allowed by pool, denied by tenant
{"tenant declaring no policy", "acme", both, nil, "", "", false},
{"tenant on a policyless pool", "acme", nil, tenantOnly, "", "", false},
{"neither", "acme", nil, nil, "", "", false},
{"root takes the pool policy whole", "", true, both, nil, "a.test", "z.test", true},
{"root without a pool policy", "", true, nil, nil, "", "", false},
{"tenant intersects", "acme", true, both, tenantOnly, "b.test", "a.test", true}, // a.test allowed by pool, denied by tenant
{"tenant declaring no policy", "acme", true, both, nil, "", "", false},
{"tenant on a policyless pool", "acme", true, nil, tenantOnly, "", "", false},
{"tenant on a promoted template", "acme", false, nil, tenantOnly, "b.test", "a.test", true},
{"root on a promoted template", "", false, nil, nil, "", "", false},
{"neither", "acme", false, nil, nil, "", "", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
m.pools = map[types.PoolKey]*pool{}
if tc.pooled {
m.pools[testKey] = &pool{key: testKey}
}
m.poolEgress = map[types.PoolKey]*egress.Policy{}
if tc.pool != nil {
m.poolEgress[testKey] = tc.pool
Expand Down