Lesson 03 / 12
Network Isolation
Intersecting the permission radius with the network path: the same 9-item walk drops to 6 from outside, 8 from the workload network, and 2 from the management network. Moving 10 items with no legitimate outside access to a private endpoint brings the outside radius down from 6 to 4 and stops zero legitimate jobs; pulling the threshold to 50 a day zeroes out the outside radius and stops 101 outside accesses a day. The workload network column stays at 8 across all four audits.
Published Updated
In this lesson
- Intersects the permission radius with the network path and computes the real radius separately for each attacker location
- Counts together the path that switching to a private endpoint closes and the legitimate outside access it stops
- Shows in the same graph the location at which network isolation never lowers the radius
Contents
The previous lesson turned the radius into a number: nine items from the leaked service account. Throughout that walk, one thing was never asked — does an identity having authorization to an item mean it can actually reach that item. There was an edge, and the walk passed over the edge; whether the network the item sits on is reachable from where the credential is used was never looked at.
This lesson adds a second layer to the same graph. Every item has a network path: which locations it is reachable from at the network level. The real radius is the intersection of two sets — the items the permission walk reaches, and the items reachable at the network level from that location. The Cloud Governance, Security and Cost course measured this same surface through an access chart and name resolution; here, address plans, subnet arithmetic, and filter-rule syntax never appear — only the closed path is counted.
- CM13. There are three locations:
outside(outside the rented network),workload(the network a compromised workload instance sits on), andmanagement(the network deployment jobs run on). The leaked credential is used from one of these locations. - CM14. An item’s path is which locations it is reachable from at the network level. Path is independent of permission; a path can be open for an identity that has no authorization, and closed for an identity that does.
- CM15. Role assumption happens through identity and access management, and can be done from any of the three locations. This is a model decision: if assumption were restricted, the walk would shorten, and the network layer’s effect could not be measured.
- CM16. Switching to a private endpoint means an item’s public endpoint is closed and it
becomes reachable only from within the network; in the model, this is the
outsidelocation dropping out of the item’s path. - CM17. How many legitimate outside accesses each item gets per day is a fictional number, and when the public endpoint is closed, those accesses are assumed to stop.
The Permission Radius Is an Upper Bound
// cloud/network.mjs — the previous lesson's access graph + each item's NETWORK PATH. This is a // MODEL; a fictional regional measurement network. Address plans, subnet arithmetic, and // filter-rule syntax are not present in this lesson. export const ACCESS = { 'account-collector': 'measurement-store measurement-queue', 'account-validator': 'measurement-store measurement-queue raw-archive', 'account-portal': 'subscriber-database portal-asset-store work-order-queue', 'account-report': 'report-store billing-output-store log-store', 'role-billing': 'billing-database billing-output-store mail-dispatch subscriber-database', 'role-archive': 'raw-archive backup-store', 'role-backup': 'backup-store billing-database subscriber-database measurement-store', 'role-distribution': 'distribution-configuration template-store instance-management', 'role-log': 'log-store audit-log', 'role-management': 'instance-management key-management secret-store', }; export const ASSUME = [['account-collector', 'role-archive'], ['account-validator', 'role-archive'], ['role-archive', 'role-backup'], ['role-backup', 'role-management'], ['account-portal', 'role-billing'], ['role-billing', 'role-log'], ['account-report', 'role-log'], ['role-distribution', 'role-management']]; // Location: where the leaked credential is used from. An item's path tells which location it is // reachable from at the network level. outsideAccess = how many legitimate outside accesses a day // pass through that item's public endpoint. export const LOCATION = ['outside', 'workload', 'management']; export const ITEM = ` measurement-store outside workload 40 measurement-queue workload 0 raw-archive outside workload 6 backup-store outside workload management 2 subscriber-database workload 0 billing-database workload 0 billing-output-store outside workload 120 portal-asset-store outside workload 8600 work-order-queue workload 0 log-store workload management 0 audit-log management 0 report-store outside management 30 mail-dispatch outside workload 0 secret-store outside workload 0 key-management outside workload 0 instance-management outside management 14 distribution-configuration management 0 template-store outside management 9` .trim().split('\n').map((s) => { const p = s.trim().split(/\s+/); return { name: p[0], path: p.slice(1, -1), outsideAccess: Number(p.at(-1)) }; }); export const find = (name) => ITEM.find((k) => k.name === name); // Permission walk: the set of items reached in the identity graph (same as the previous lesson). export function permit(start) { const g = new Set([start]), item = new Set(); let queue = [start]; while (queue.length) { const next = []; for (const k of queue) { for (const r of ACCESS[k].split(' ')) item.add(r); for (const [a, b] of ASSUME) if (a === k && !g.has(b)) { g.add(b); next.push(b); } } queue = next; } return [...item]; } // Real radius = permission AND network path. closed = names of items whose public endpoint is closed. export const radius = (start, location, closed = new Set()) => permit(start).filter((r) => find(r).path.includes(location) && !(location === 'outside' && closed.has(r))).length;
// cloud/run1.mjs — permission radius vs. radius passed through the network path: how many items by location import { ITEM, LOCATION, find, permit, radius } from './network.mjs'; const u = permit('account-collector'); console.log(`leaked identity account-collector; permission walk ${u.length}/${ITEM.length} items\n`); const print = (g, ...s) => console.log(s.map((v, i) => g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i])).join('')); const A = [-24, 7, 9, 10, 11]; print(A, 'item entering the walk', ...LOCATION, 'legit ext.'); for (const r of u.sort()) print(A, r, ...LOCATION.map((k) => find(r).path.includes(k) ? 'yes' : '-'), find(r).outsideAccess); print(A, 'radius', ...LOCATION.map((k) => radius('account-collector', k)), ''); console.log('\nitem reachable at the network level (independent of permission), permission radius 9:'); for (const k of LOCATION) console.log(` ${k.padEnd(9)}network surface ${String(ITEM.filter((r) => r.path.includes(k)).length) .padStart(2)}/${ITEM.length}, intersection with permission ${radius('account-collector', k)}`);
leaked identity account-collector; permission walk 9/18 items item entering the walk outside workloadmanagement legit ext. backup-store yes yes yes 2 billing-database - yes - 0 instance-management yes - yes 14 key-management yes yes - 0 measurement-queue - yes - 0 measurement-store yes yes - 40 raw-archive yes yes - 6 secret-store yes yes - 0 subscriber-database - yes - 0 radius 6 8 2 item reachable at the network level (independent of permission), permission radius 9: outside network surface 11/18, intersection with permission 6 workload network surface 13/18, intersection with permission 8 managementnetwork surface 7/18, intersection with permission 2
The nine-item permission radius splits into three separate numbers: six from outside, eight from the workload network, two from the management network. Same leaked identity, same authorizations, three different radii. Radius is not a property of an identity — it is a pair of an identity and a location.
The lower block shows how independent the two layers are from each other. Eleven items are reachable from the outside location at the network level, but the leaked identity’s permission covers only six of them. On the workload network, the network surface is thirteen, the intersection is eight. The network layer alone is not a boundary; the permission layer alone is not one either. What is measured is the intersection of the two.
The sharpest row in the table is instance-management. That item is not reachable from the
workload network, but it is reachable from outside — meaning the leaked identity cannot be
reached when used from inside the network, but can when used from outside. subscriber-database
and billing-database are the reverse. The two locations’ radii are not in a subset relationship;
neither is simply larger or smaller than the other.
The Path Closed by the Private Endpoint
Three audits are run on the same graph. Each closes the public endpoint of a set of items, and its cost is the legitimate outside access that passes through those items.
// cloud/run2.mjs — three audits: which item's public endpoint closes, what happens to the radius by location import { ACCESS, ITEM, LOCATION, radius } from './network.mjs'; const set = (f) => new Set(ITEM.filter(f).map((k) => k.name)); const AUDIT = [ ['no audit', new Set()], ['items with no legitimate outside access to private endpoint', set((k) => k.outsideAccess === 0)], ['items under 50 daily outside accesses too', set((k) => k.outsideAccess < 50)], ['only the management endpoint closes', set((k) => k.name === 'instance-management')], ]; const print = (g, ...s) => console.log(s.map((v, i) => g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i])).join('')); const A = [-42, 8, 7, 9, 10, 14]; print(A, 'audit', 'closed', ...LOCATION, 'ext. stopped'); for (const [name, closed] of AUDIT) print(A, name, closed.size, ...LOCATION.map((k) => radius('account-collector', k, closed)), ITEM.filter((k) => closed.has(k.name)).reduce((a, k) => a + k.outsideAccess, 0)); console.log('\nwidest radius (all 10 identities are scanned):'); const B = [-42, 8, 9, 10]; print(B, 'audit', ...LOCATION); for (const [name, closed] of AUDIT) print(B, name, ...LOCATION.map((k) => Math.max(...Object.keys(ACCESS).map((s) => radius(s, k, closed)))));
audit closedoutside workloadmanagement ext. stopped no audit 0 6 8 2 0 items with no legitimate outside access to private endpoint 10 4 8 2 0 items under 50 daily outside accesses too 16 0 8 2 101 only the management endpoint closes 1 5 8 2 14 widest radius (all 10 identities are scanned): audit outside workloadmanagement no audit 6 8 3 items with no legitimate outside access to private endpoint 4 8 3 items under 50 daily outside accesses too 2 8 3 only the management endpoint closes 5 8 3
The first audit is free. Ten of the eighteen items get no legitimate outside access at all; closing
their public endpoints brings the outside radius down from six to four and stops zero legitimate
jobs a day. The two items that drop are secret-store and key-management — the two items that
appeared at the third step of the previous lesson’s walk. The deepest point of the walk is the
cheapest point to close at the network layer.
The second audit pulls the threshold down to fifty outside accesses a day. The number of closed
items rises to sixteen, the outside radius drops to zero; its cost is a hundred and one outside
accesses a day. Most of these are field crews loading directly into measurement-store (forty) and
thirty accesses pulled from the report store. Bringing the outside radius down from four to zero
costs a hundred and one accesses a day; the first audit gained two items in the same direction while
stopping zero legitimate access.
The third audit targets a single item. Closing the management endpoint brings the outside radius down from six to five and stops fourteen outside accesses a day. It pays fourteen accesses per item; after the threshold audit’s seventeen per item, this is the second most expensive audit.
All Thirty Pairs
A single leaked identity is one example. The whole graph is thirty pairs of ten identities and three locations.
// cloud/run3.mjs — radius per (identity, location) pair: the free audit before and after import { ACCESS, ITEM, LOCATION, radius } from './network.mjs'; const free = new Set(ITEM.filter((k) => k.outsideAccess === 0).map((k) => k.name)); const print = (g, ...s) => console.log(s.map((v, i) => g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i])).join('')); const A = [-19, 6, 9, 9, 3, 6, 9, 9]; console.log(''.padEnd(19) + 'no audit'.padStart(24) + 'after the free audit'.padStart(27)); print(A, 'identity', ...LOCATION, '', ...LOCATION); for (const s of Object.keys(ACCESS)) print(A, s, ...LOCATION.map((k) => radius(s, k)), '', ...LOCATION.map((k) => radius(s, k, free))); const pair = (closed) => Object.keys(ACCESS).flatMap((s) => LOCATION.map((k) => radius(s, k, closed))); const before = pair(new Set()), after = pair(free); console.log(`\n30 (identity, location) pairs; total radius ${before.reduce((a, x) => a + x, 0)} -> ` + `${after.reduce((a, x) => a + x, 0)}, pairs with a radius over 5 ` + `${before.filter((x) => x > 5).length} -> ${after.filter((x) => x > 5).length}`); console.log(`widest radius ${Math.max(...before)} -> ${Math.max(...after)}; ` + `cost 0 legitimate outside access a day`);
no audit after the free audit identity outside workloadmanagement outside workloadmanagement account-collector 6 8 2 4 8 2 account-validator 6 8 2 4 8 2 account-portal 3 7 2 2 7 2 account-report 2 2 3 2 2 3 role-billing 2 5 2 1 5 2 role-archive 6 7 2 4 7 2 role-backup 5 6 2 3 6 2 role-distribution 4 2 3 2 2 3 role-log 0 1 2 0 1 2 role-management 3 2 1 1 2 1 30 (identity, location) pairs; total radius 106 -> 92, pairs with a radius over 5 8 -> 5 widest radius 8 -> 8; cost 0 legitimate outside access a day
The thirty pairs’ total radius drops from a hundred and six to ninety-two — about thirteen percent. Pairs with a radius over five drop from eight to five. All of this is the result of the free audit; not a single legitimate outside access a day stops.
The table also shows where an identity is dangerous. role-distribution reaches four items from
outside, two from the workload network, three from the management network — its widest radius is
in the outside location, and the free audit brings it down from four to two. account-portal is
the reverse: three from outside, seven from the workload network. The same audit can close one
identity’s most dangerous location while never touching another’s.
The last row gives this its limit: the widest radius goes from eight to eight — it does not change at all.
The Radius Isolation Does Not Lower
The workload network column is eight in all four rows. This is the number that carries this lesson’s verdict: private endpoints close the outside location, not the location inside the network. Removing an item’s public endpoint leaves access to that item from within the network open by definition — that is exactly what a private endpoint does.
The consequence is this: if the leaked credential is used from a workload instance, all of the private-endpoint work lowers the radius by zero items. The lower table confirms the same result for all ten identities — the widest radius on the workload network is eight across all four audits, three on the management network. Only the outside column drops, to four, two, and five.
Network isolation therefore does not replace an identity audit — it complements it. Zeroing out the radius at the outside location leaves the radius at eight inside the network; the previous lesson’s removal of a trust edge, by contrast, was effective at every location at once. The two audits do not lower the same number, and one’s measurement does not stand in for the other’s.
The practical takeaway of the measurement is an ordering. The ten free items are closed first: it lowers the outside radius by two items, stops zero legitimate work, and needs no debate. Pulling the threshold to fifty zeroes out the outside radius but stops a hundred and one accesses a day; this is no longer a network decision but one about how field crews and report consumers do their work, and it cannot be made without talking to those teams. The eight items on the workload network, on the other hand, do not drop through any network decision — lowering that number requires going back to the previous lesson’s trust edges.
One wrong conclusion should specifically not be drawn from this table: zeroing out the outside radius does not mean the breach becomes impossible. What goes to zero is the radius in the case where the credential is used from outside. If the same credential is used through a path that reaches a workload inside the network, the radius is eight again, and in the model this transition is nothing more than a change of location.
Summary
- The real radius is the intersection of the permission walk and the network path. The same 9-item walk drops to 6 from outside, 8 from the workload network, 2 from the management network; radius is a property of the pair of identity and location, not of the identity alone.
- The two locations’ radii are not in a subset relationship:
instance-managementis reachable only from outside,subscriber-databaseonly from the workload network. - Moving the 10 items with no legitimate outside access to a private endpoint brings the outside
radius down from 6 to 4 and stops 0 legitimate jobs a day. The two items that drop are
secret-storeandkey-management, at the deepest step of the walk. - Pulling the threshold to 50 daily outside accesses zeroes out the outside radius; its cost is 101 legitimate outside accesses a day. Closing the management endpoint alone lowers the radius by 1 item and stops 14 accesses a day.
- The 30
(identity, location)pairs’ total radius drops from 106 to 92 with the free audit, pairs with a radius over 5 drop from 8 to 5; the widest radius stays at 8. - The workload network column is 8 across all four audits. A private endpoint closes the outside location, not the location inside the network; if the leaked identity is used from a workload instance, this work has zero effect on the radius.
Next Step
In this lesson, the network path was treated as a precondition for reaching an item: if the path
was closed, permission was useless. But some items in the list have their outside path open on
purpose — portal-asset-store gets eight thousand six hundred legitimate outside accesses a day and
cannot be closed. For that item, the question is not the path but the permission set: which
identity a request coming from outside is served with, and what a request presenting no identity at
all can read. The next lesson scans the permission set of storage items, counts items open to public
access, and measures the type of data readable from that item.
To keep your progress and take notes, Log in
My notes
Log in to take notes.