Files
shopdb-flask/plugins/geenforce/frontend/views/GeEnforceSettings.vue
cproudlock 0860aa85c5 geenforce: IP allowlist for client endpoints + admin Settings tab
Fleet PCs on a trusted (vaulted) network can now reach the GE-Enforce client
endpoints (manifest, payload, report) without a per-PC token: the auth path
accepts a valid geenforce.fetch/report token OR a source IP in the configured
allowlist (setting geenforce_allowed_cidrs). Fail-closed; an empty allowlist
means the token stays the only path, so existing deployments are unchanged.

Rationale: the client token lives in HKLM on every kiosk, so it does not
defend against a compromised kiosk anyway - network-perimeter trust is the
same practical strength with far less provisioning + no token-rotation churn
on a DB wipe. Documented in-UI that this is perimeter trust, not per-device
identity.

- _ip_allowlisted() (ipaddress, X-Forwarded-For-aware via _client_ip)
- /geenforce/config GET/PUT extended with allowedcidrs, server-validated +
  normalized (bad CIDR -> 400)
- new GE-Enforce > Settings tab (GeEnforceSettings.vue) to edit the allowlist
  in admin, no SQL
- 3 regression tests (allow by IP, reject outside list, empty = token required)
2026-07-27 14:06:40 -04:00

110 lines
3.2 KiB
Vue

<template>
<div class="geenforce-settings">
<h2>Client Access</h2>
<p class="muted">
Fleet PCs on a trusted network can reach the GE-Enforce client endpoints
(manifest, payload, report) without a per-PC token. List the trusted
networks below. Leave it empty to require a <code>geenforce.fetch</code>
token instead.
</p>
<label class="field-label" for="cidrs">Allowed networks (CIDR)</label>
<textarea
id="cidrs"
v-model="cidrs"
class="cidr-input"
rows="5"
spellcheck="false"
placeholder="10.134.48.0/23&#10;10.48.249.0/26"
></textarea>
<small class="input-hint">
One CIDR (or a plain IP) per line, or comma-separated. Example:
<code>10.134.48.0/23, 10.48.249.0/26</code>. A caller from any of these
networks may pull manifests + payloads and post reports with no token.
</small>
<div class="actions">
<button class="btn btn-primary" :disabled="saving" @click="save">
{{ saving ? 'Saving...' : 'Save' }}
</button>
<span v-if="message" :class="['status', ok ? 'ok' : 'err']">{{ message }}</span>
</div>
<div class="note">
<strong>Note:</strong> this is network-perimeter trust. Any host on a
listed network is trusted - including a compromised one. It does not
replace per-device identity if you need tamper-proof report integrity.
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import api from '@/api'
const cidrs = ref('')
const saving = ref(false)
const message = ref('')
const ok = ref(false)
function unwrap(response) { return response.data.data }
async function load() {
try {
const config = unwrap(await api.get('/geenforce/config'))
cidrs.value = config.allowedcidrs || ''
} catch (e) {
message.value = 'Could not load config.'
ok.value = false
}
}
async function save() {
saving.value = true
message.value = ''
try {
const config = unwrap(await api.put('/geenforce/config', { allowedcidrs: cidrs.value }))
cidrs.value = config.allowedcidrs || '' // server returns the normalized list
ok.value = true
message.value = 'Saved.'
} catch (e) {
ok.value = false
message.value = e?.response?.data?.data?.error?.message || 'Save failed.'
} finally {
saving.value = false
}
}
onMounted(load)
</script>
<style scoped>
.geenforce-settings { max-width: 640px; }
.muted { color: var(--text-light); }
.field-label { display: block; font-weight: 600; margin: 1rem 0 0.35rem; }
.cidr-input {
width: 100%;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 0.9rem;
padding: 0.6rem 0.7rem;
border: 1px solid var(--border);
border-radius: 0.25rem;
background: var(--bg-card-solid);
color: var(--text);
resize: vertical;
}
.input-hint { display: block; color: var(--text-light); margin-top: 0.35rem; }
.actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 1rem; }
.status.ok { color: var(--success); font-weight: 600; }
.status.err { color: var(--danger); font-weight: 600; }
.note {
margin-top: 1.5rem;
padding: 0.75rem 1rem;
border-left: 4px solid var(--warning);
background: var(--bg-card);
border-radius: 0.25rem;
color: var(--text-light);
font-size: 0.9rem;
}
</style>