Act on the wj architectural review: races, limits, pool and contract gaps
Findings F2, F4, F5, F6, F7, F8, F9, F10, F11 and F13 from wj-architectural-review.md. F1/F1a (the non-sargable LOWER pattern) and F3 (the warranty sync holding a pooled connection) are being handled separately and are untouched here. - A collector check-in that raced another for the same PC lost everything: the unique index on the application fingerprint raised, nothing caught it, and the whole check-in rolled back, discarding state, events and hardware, answering the agent with form copy meant for a person. The insert now runs in a savepoint, reconciles what the other transaction already wrote, one row per savepoint so a second collision skips a row rather than the batch, and the check-in commits with a warning. This is the shape the delete side of the same function already had. - Enrollment is deliberately unauthenticated, so it now carries a per-address rate limit and a cap on pending requests, and the cap is a setting an admin can see and change. A flood can no longer bury the approval queue or grow the table without bound. - The fleet alert poll no longer formats debug strings on every request. - BusinessUnit is on the contract surface (0.34.0), so the endpoints plugin stops reaching into a core table with raw SQL. - A test walks every asset type descriptor and proves the endpoint it names really refuses a caller without the declared permission. - The plugin settings routes now need a permission; they were telling anyone which modules a site runs and where its components live. - The connection pool is sized explicitly with a short timeout, and the arithmetic against the server's connection ceiling is written next to the worker count instead of being left to defaults nobody chose. - Fleet alerts carry both the correct lowercase keys and the old camelCase pair. Trays older than 1.7.0 match keys case-sensitively and would have gone quiet on every PC not yet upgraded; the old pair goes when they are. - The dev server proxy points at localhost again, with environment variables for the remote-box workflow, and one developer's address is out of the server certificate page.
This commit is contained in:
@@ -229,6 +229,26 @@
|
||||
on record matches. Anything else waits below for an admin.
|
||||
</span>
|
||||
</label>
|
||||
<label class="number-row">
|
||||
<span>
|
||||
<strong>Pending request limit.</strong> How many requests may wait
|
||||
here at once (setting
|
||||
<code>plugin.endpoints.maxpendingenrollments</code>). At the limit,
|
||||
a PC we have never seen is told to try later, while PCs already in
|
||||
this list keep asking and keep being approved. 0 removes the limit.
|
||||
/enroll takes no login, so this is what stops the queue growing
|
||||
without bound.
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input-maxpending"
|
||||
v-model.number="pendingCapInput"
|
||||
:disabled="!isadmin || busy"
|
||||
@change="onPendingCapChange()"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="enroll-tiles">
|
||||
@@ -645,7 +665,16 @@ function wavePercent(wave) {
|
||||
|
||||
// ---- Credentials ------------------------------------------------------
|
||||
|
||||
const enrollmentSettings = ref({ sharedkeyallowed: true, autoenroll: true })
|
||||
const DEFAULT_PENDING_CAP = 500
|
||||
|
||||
const enrollmentSettings = ref({
|
||||
sharedkeyallowed: true,
|
||||
autoenroll: true,
|
||||
maxpendingenrollments: DEFAULT_PENDING_CAP
|
||||
})
|
||||
// Bound with v-model rather than :value so the field holds what the operator
|
||||
// typed until it is accepted or rejected; a one-way binding fights the edit.
|
||||
const pendingCapInput = ref(DEFAULT_PENDING_CAP)
|
||||
const enrollmentCounts = ref({})
|
||||
const credentialRows = ref([])
|
||||
const requests = ref([])
|
||||
@@ -667,8 +696,12 @@ async function loadEnrollment({ quiet = false } = {}) {
|
||||
const data = response.data.data || {}
|
||||
enrollmentSettings.value = {
|
||||
sharedkeyallowed: data.settings?.sharedkeyallowed !== false,
|
||||
autoenroll: data.settings?.autoenroll !== false
|
||||
autoenroll: data.settings?.autoenroll !== false,
|
||||
maxpendingenrollments: Number.isFinite(Number(data.settings?.maxpendingenrollments))
|
||||
? Number(data.settings.maxpendingenrollments)
|
||||
: DEFAULT_PENDING_CAP
|
||||
}
|
||||
pendingCapInput.value = enrollmentSettings.value.maxpendingenrollments
|
||||
enrollmentCounts.value = data.counts || {}
|
||||
credentialRows.value = data.computers || []
|
||||
requests.value = data.requests || []
|
||||
@@ -738,6 +771,16 @@ function onAutoEnrollToggle(event) {
|
||||
})
|
||||
}
|
||||
|
||||
function onPendingCapChange() {
|
||||
const wanted = Math.trunc(Number(pendingCapInput.value))
|
||||
if (!Number.isFinite(wanted) || wanted < 0) {
|
||||
pendingCapInput.value = enrollmentSettings.value.maxpendingenrollments
|
||||
toast.error('The pending request limit must be 0 or a whole number')
|
||||
return
|
||||
}
|
||||
return saveSettings({ maxpendingenrollments: wanted })
|
||||
}
|
||||
|
||||
function openApprove(request) {
|
||||
const options = []
|
||||
if (request.matchedassetid) {
|
||||
@@ -1126,6 +1169,18 @@ onBeforeUnmount(() => {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.number-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.number-row input {
|
||||
width: 6rem;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
|
||||
Reference in New Issue
Block a user