Lesson 01 / 12
Cloud Service Models and Responsibility
Translating the shared-responsibility boundary into an audit list: 21 items that can be misconfigured are classified across three service models, the items that can be automatically audited drop from 15 to 12 and 7 while the items left outside the audit list rise from 6 to 9 and 14, the share of seed weight goes from 35% to 51% and 66%, unowned items rise from 0 to 2 and 4, and the 28-unit blind spot that no list can read stays fixed across all three models.
Published Updated
In this lesson
- Separates configuration items that can be misconfigured into four classes: audited, known by declaration, blind spot, and unowned
- Counts how the items and seed weight covered by the audit list narrow as the service model goes deeper
- Shows separately the weight that taking ownership of an unowned item closes and the blind spot it cannot close
Contents
The previous course built the evidence chain all the way through, and at every step it carried the same silent assumption: the machine under examination is reachable. In rented infrastructure that assumption falls away. The hardware belongs to someone else, the host operating system is invisible, and the physical machine the instance runs on cannot even be named. This pushes the evidence question to later and brings forward a question that comes earlier: where the misconfiguration can be, and whose list that misconfiguration is on.
This course uses a single measure: blast radius — how many items are reached from a single misconfigured item. Measuring the radius first requires a list of the items where a misconfiguration can sit. Shared responsibility splits that list in two; this lesson turns the split into an audit list and counts what falls outside it. The responsibility matrix itself was measured in the Cloud Fundamentals course of the Cloud Computing curriculum and is not re-derived here — it is taken as input.
- CM1. Everything below belongs to a fictional regional measurement network: meter collection,
verification, invoice generation, and the subscriber portal all run on rented infrastructure. No
real provider, service, region, or organization name appears; what is measured is a model
written with
node. - CM2. A misconfigurable item is a configuration item that opens access when its value is set incorrectly. Twenty-one items are counted; the list is illustrative, not exhaustive.
- CM3. In each of the three service models, every item is set either by the customer, by the provider, or by no one at all. The third case does not come from the contract; it comes from each side assuming the other one handles it.
- CM4. Readability is a separate property: an item’s effective value is either readable from the management plane or not readable at all. An item the provider sets is known to the customer only by declaration.
- CM5. Seed weight is the number of items directly opened up if an item is misconfigured. It is a fictional number, used in this lesson only to weight the items; the walk itself is run in the next lesson.
- CM6. A row in the audit list can only run automatically if the item is both owned and readable. In every other case, the cost of the row changes.
Items That Can Be Misconfigured
The model writes items with three columns — who sets it, per service model — and carries two more properties: readability and seed weight. Four classes are derived from these three.
// cloud/item.mjs — the MODEL of items that can be misconfigured in rented infrastructure. // Fictional regional measurement network; no real provider, service, region, or contract name. export const MODEL = ['infrastructure', 'platform', 'function']; // Columns: who sets the item (C customer, P provider, N nobody) x three service models. // read: how the item's effective value is read by the customer. // interface = read from the management plane, none = read from neither an interface nor a document. // touch: number of items directly opened if the item is misconfigured (seed weight, fictional). export const ITEM = ` physical-access P P P interface 6 hypervisor-patch P P P interface 14 co-tenancy P P P interface 11 host-kernel-patch C P P interface 9 runtime-version C C P interface 5 image-source C C P interface 7 instance-metadata-version C N P interface 8 management-endpoint-exposure C N N interface 10 network-access-rule C C N interface 6 private-endpoint-mapping C C N interface 4 log-stream-destination C C N interface 3 role-trust-list C C C interface 12 identity-role-definition C C C interface 9 storage-permission-set C C C interface 7 storage-public-flag C C C interface 5 key-ownership C C C interface 8 secret-store-access C C C interface 13 audit-log-enabled C C C interface 2 template-default C C C none 9 dependency-version C C C none 4 in-workload-secret C C C none 15` .trim().split('\n').map((s) => { const [name, ...k] = s.trim().split(/\s+/); return { name, read: k[3], touch: Number(k[4]), side: Object.fromEntries(MODEL.map((m, i) => [m, k[i]])) }; }); // Audit-list class: who sees this if the item is misconfigured, and with what. export const classOf = (k, m) => k.side[m] === 'N' ? 'unowned' : k.side[m] === 'P' ? 'declared' : k.read === 'interface' ? 'audited' : 'blind'; export const CLASS = ['audited', 'declared', 'blind', 'unowned']; // Cost of the audit row: rule that runs automatically, periodic question round, extra mechanism, owner assignment. export const COST = { audited: 'rule', declared: 'question round', blind: 'extra mechanism', unowned: 'owner first' }; export const group = (m, c) => ITEM.filter((k) => classOf(k, m) === c); export const weight = (l) => l.reduce((a, k) => a + k.touch, 0); export const TOTAL = weight(ITEM);
// cloud/run1.mjs — how many items are on the customer, how many are declared, how many are unowned; how many are outside the audit list import { ITEM, MODEL, CLASS, TOTAL, group, weight, classOf } from './item.mjs'; 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 = [-30, 16, 16, 16]; const changed = ITEM.filter((k) => new Set(MODEL.map((m) => classOf(k, m))).size > 1); console.log(`${ITEM.length} items that can be misconfigured, total seed weight ${TOTAL}; ` + `${changed.length} items whose class changes across the three models:\n`); print(A, 'item', ...MODEL); for (const k of changed) print(A, k.name, ...MODEL.map((m) => classOf(k, m))); console.log(''); print(A, 'class (21-item breakdown)', ...MODEL); for (const c of CLASS) print(A, c, ...MODEL.map((m) => group(m, c).length)); print(A, 'outside the audit list', ...MODEL.map((m) => CLASS.slice(1).reduce((a, c) => a + group(m, c).length, 0))); print(A, ' seed weight', ...MODEL.map((m) => CLASS.slice(1).reduce((a, c) => a + weight(group(m, c)), 0))); print(A, ' weight percentage', ...MODEL.map((m) => Math.round(CLASS.slice(1).reduce((a, c) => a + weight(group(m, c)), 0) / TOTAL * 100)));
21 items that can be misconfigured, total seed weight 167; 8 items whose class changes across the three models: item infrastructure platform function host-kernel-patch audited declared declared runtime-version audited audited declared image-source audited audited declared instance-metadata-version audited unowned declared management-endpoint-exposure audited unowned unowned network-access-rule audited audited unowned private-endpoint-mapping audited audited unowned log-stream-destination audited audited unowned class (21-item breakdown) infrastructure platform function audited 15 12 7 declared 3 4 7 blind 3 3 3 unowned 0 2 4 outside the audit list 6 9 14 seed weight 59 86 111 weight percentage 35 51 66
The top table shows only the eight items whose class changes; the remaining thirteen items stay in the same class across all three models. The bottom table gives the actual counts: the items the customer can read from the management plane and audit automatically drop from fifteen to twelve, then to seven. In the same direction, the items left outside the audit list rise from six to nine, then to fourteen.
The seed-weight row shows why this shift is not a flat trade. The share of the total weight of
167 units that the audit list cannot see goes from 35% to 51%, then to 66%. The item count grows
by a little more than double, while the weight share nearly triples — because the items that drop
off the list are the heavy ones. hypervisor-patch carries fourteen, in-workload-secret fifteen,
secret-store-access thirteen units.
The List Narrows as the Boundary Shifts
The top table separates four groups of items. host-kernel-patch, runtime-version, and
image-source move to the provider explicitly: the customer does not set them, the provider
takes them over, and the audit turns into a declaration. This is not a loss but a trade — the item
can still be misconfigured, but the party that can fix the misconfiguration is the same party that
made it.
network-access-rule, private-endpoint-mapping, and log-stream-destination take a different
path. The customer sets them in the infrastructure and platform models; in the function model, the
execution environment abstracts these items away and the provider does not take them over under
contract either. The result is unownedness: the item can still be misconfigured, but because no one
sets it, no one audits it.
management-endpoint-exposure stays unowned in two models at once, and at ten units of weight it
is the heaviest unowned item on the list. instance-metadata-version follows an interesting path:
the customer sets it in infrastructure, no one sets it in platform, and the provider takes it over
in function. As the boundary shifts deeper, an item does not always cross in a straight line — it
can fall into a gap in one model along the way.
The Cost of the Audit List
Each of the four classes enters the audit list at a different cost. For an owned, readable item, a rule is written and it runs continuously. For an item known by declaration, a periodic question is put to the provider; the accuracy of the answer cannot be tested, only recorded. A blind spot needs an extra mechanism to pull its value out of the running system. An unowned item fits none of these: before a row can be written, the item’s owner must first be assigned.
// cloud/run2.mjs — audit list: how many rows run automatically, how many need a question round import { MODEL, CLASS, COST, TOTAL, group, weight } from './item.mjs'; 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 B = [-13, -18, 16, 16, 16]; print(B, 'class', 'row cost', ...MODEL); for (const c of CLASS) print(B, c, COST[c], ...MODEL.map((m) => `${group(m, c).length} / ${weight(group(m, c))}`)); console.log(' (cell: item count / seed weight)\n'); const C = [-16, 9, 10, 9, 14, 12]; print(C, 'model', 'audit', 'covered', 'question', 'no owner', 'blind'); print(C, '', 'rows', 'weight', 'round', 'item/weight', 'weight'); for (const m of MODEL) print(C, m, group(m, 'audited').length, `${Math.round(weight(group(m, 'audited')) / TOTAL * 100)}%`, group(m, 'declared').length, `${group(m, 'unowned').length} / ${weight(group(m, 'unowned'))}`, weight(group(m, 'blind')));
class row cost infrastructure platform function
audited rule 15 / 108 12 / 81 7 / 56
declared question round 3 / 31 4 / 40 7 / 60
blind extra mechanism 3 / 28 3 / 28 3 / 28
unowned owner first 0 / 0 2 / 18 4 / 23
(cell: item count / seed weight)
model audit covered question no owner blind
rows weight round item/weight weight
infrastructure 15 65% 3 0 / 0 28
platform 12 49% 4 2 / 18 28
function 7 34% 7 4 / 23 28
The first table breaks the cost down class by class. In the infrastructure model, fifteen rows run automatically and cover a hundred and eight units of weight; in the function model, only seven rows remain and the covered weight drops to fifty-six. The question round rises from three to seven: each round is an item that is asked of the provider, whose answer is awaited, and that cannot be tested.
The second table reads the same three models in a single row. The automatically covered weight drops from 65% to 34%. Unowned items rise from zero to two and four, and their weight from zero to eighteen and twenty-three. For these items the customer has one option — take on ownership — and when it is taken on, eighteen and twenty-three units return to the list. The cost is that work not in the contract gets written onto the customer’s own team.
How Many Days Later Is the Misconfiguration Seen
Row count gives the width of the list, not its delay. A rule that runs automatically sees the misconfiguration on the day it is set; a periodic question round sees it only on the day the round comes around; if an extra mechanism collects weekly, it sees it within the week. An unowned item is never asked at all.
// cloud/run3.mjs — how many days later a misconfigured item is noticed; which weight is never noticed import { MODEL, CLASS, TOTAL, group, weight } from './item.mjs'; // Detection delay (days): a rule runs continuously, a question round is periodic, // an extra mechanism collects weekly once installed, an unowned item is never asked. const DELAY = { audited: 0, declared: 90, blind: 7, unowned: null }; 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 D = [-16, 9, 15, 16, 17]; print(D, 'model', 'observed', 'weighted', 'never observed', 'blind mechanism'); print(D, '', 'weight', 'delay (days)', 'weight', 'if absent'); for (const m of MODEL) { const g = CLASS.filter((c) => DELAY[c] !== null); const seen = g.reduce((a, c) => a + weight(group(m, c)), 0); const sum = g.reduce((a, c) => a + weight(group(m, c)) * DELAY[c], 0); print(D, m, seen, (sum / seen).toFixed(1), TOTAL - seen, TOTAL - seen + weight(group(m, 'blind'))); } console.log('\ncontribution to delay from weight falling under question round instead of rule:'); for (const m of MODEL) console.log(` ${m.padEnd(15)}declared weight ${String(weight(group(m, 'declared'))).padStart(3)}` + `, ${Math.round(weight(group(m, 'declared')) * 90 / (weight(group(m, 'declared')) * 90 + weight(group(m, 'blind')) * 7) * 100)}% of the delay comes from here`);
model observed weighted never observed blind mechanism
weight delay (days) weight if absent
infrastructure 167 17.9 0 28
platform 149 25.5 18 46
function 144 38.9 23 51
contribution to delay from weight falling under question round instead of rule:
infrastructure declared weight 31, 93% of the delay comes from here
platform declared weight 40, 95% of the delay comes from here
function declared weight 60, 96% of the delay comes from here
The weighted delay goes from seventeen point nine days to twenty-five point five, then to thirty-eight point nine. In other words, the same misconfigured item sits in place, on average, more than twice as long in the function model. The bottom block says where the delay comes from: across all three models, the share running from 93% to 96% comes from the declared class. The blind spot’s weekly mechanism contributes almost nothing to the delay, because seven days is small next to ninety.
The last column carries a warning. The never-observed weight is zero, eighteen, and twenty-three — but that number assumes an extra mechanism has been installed for the blind spot. If the mechanism is not installed, the never-observed weight rises to twenty-eight, forty-six, and fifty-one; in the function model, roughly a third of the total weight never lands on any list. This is not a gap in the audit list’s width: the list can be written as twenty-one rows and some of those rows will still never read a value.
The Blind Spot Does Not Change With the Boundary
The last column carries this lesson’s decisive number: the weight no list can read is twenty-eight units in all three models. Three items — template default, dependency version, and the in-workload secret — belong to the customer in all three models, can be misconfigured in all three, and cannot be read from the management plane in any of the three.
This means deepening the service model does not shrink the blind spot. The boundary moves items between customer and provider; it does not move readability. Taking on an unowned item does not close the blind spot either — even after the platform model’s two unowned items are taken on, twenty-eight units remain in place, because ownership was already with the customer; what was missing was visibility.
The audit list must therefore do three separate jobs at once: continuously test an owned, visible item with a rule, record the provider’s item through a question round, and pull the invisible item out of the running system. The list at the end of this lesson has twenty-one rows, and in the function model only seven of those rows run on their own.
Summary
- The 21 items that can be misconfigured split into four classes: audited, known by declaration, blind spot, unowned. Total seed weight is 167 units.
- Automatically audited items go 15 → 12 → 7; items left outside the audit list go 6 → 9 → 14. Covered weight goes 65% → 49% → 34%.
- The weight share drops faster than the item count, because the items that drop off the list are
the heavy ones:
hypervisor-patchcarries 14,in-workload-secret15,secret-store-access13 units. - Unowned items go 0 → 2 → 4 and their weight 0 → 18 → 23. During the shift, an item can fall into
a gap in one model instead of crossing over;
instance-metadata-versionis an example of this. - Weighted detection delay goes 17.9 → 25.5 → 38.9 days. 93% to 96% of the delay comes from the declared class; the blind spot’s weekly mechanism contributes almost nothing to the delay.
- The weight no list can read is 28 units in all three models. The boundary moves items, not readability; taking on an unowned item does not lower these 28 units either.
Next Step
The list is now written: twenty-one items, four classes, and a seed weight for each item. But seed
weight was given in this lesson as a number, not computed. secret-store-access was said to carry
thirteen, but where the thirteen comes from, and how many of those thirteen items are reached
directly and how many through another identity, was never asked. The next lesson builds this
course’s unit of measure: identities and items are written as an access graph, the walk from a
single leaked identity is actually run, and the radius becomes a number.
To keep your progress and take notes, Log in
My notes
Log in to take notes.