Share accounts: per-site Samba passwords from the webapp

Tools > Share accounts sets the pxe-upload, blancco and clonezilla Samba
passwords. Saving one runs smbpasswd and writes it everywhere it is used:
startnet.cmd in boot.wim (pxe-upload), the served Blancco preferences and
the live config.img (blancco), the iPXE menu's Clonezilla entries
(clonezilla). Refused while a PC is imaging unless forced. Stored 0600 in
UPDATES_DIR/shares/accounts.json, never shown back.

- The stored values are the authority: the playbook only creates the
  Samba accounts, seeds the store from site-vars (pxe_upload_password,
  blancco_smb_password, clonezilla_smb_password) for accounts it does not
  hold, and runs "share_accounts apply" at the end of every run.
- Every startnet save re-pins SHAREPASS, so a restored snapshot cannot
  bring an old password back; new menu blocks use %SHAREUSER%/%SHAREPASS%.
- Shop floor unattend Order 4 reads the login from fetch-source.txt;
  Invoke-PostPpkg deletes that file once the PPKG is applied.
- Upload-Image.ps1 asks for the password unless -Pass is given.
- pxe_admin_password: the Linux pxe login. Required while the box still
  has the shipped "pxe"; set with chpasswd when it differs.
- Blancco site settings now also reach the live config.img (BIOS path).
This commit is contained in:
cproudlock
2026-10-17 12:29:06 -04:00
parent 5f7954ac88
commit fa79f76951
20 changed files with 1526 additions and 671 deletions

View File

@@ -78,6 +78,7 @@
{{ navlink('Boot menu', url_for('startnet_editor'), ep == 'startnet_editor') }}
{{ navlink('Enrollment packages', url_for('enrollment'), ep == 'enrollment') }}
{{ navlink('Clonezilla backups', url_for('clonezilla_backups'), ep == 'clonezilla_backups') }}
{{ navlink('Share accounts', url_for('share_accounts.page'), ep.startswith('share_accounts.')) }}
{{ navlink('Audit log', url_for('audit_log'), ep == 'audit_log') }}
</div>
</div>

View File

@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% import '_ui.html' as ui %}
{% block title %}Share accounts - PXE Manager{% endblock %}
{# Tools > Share accounts: one row per Samba account the imaging clients use,
each with its own password form. Passwords are never shown back.
services/share_accounts.py. #}
{% block extra_head %}
<style>
.sec-h { padding: 18px 24px 12px; }
.sec-h .sub { margin-top: 2px; }
.acct { padding: 14px 24px 16px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 10px; }
.acct-top { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
.acct-top .name { font-weight: 600; }
.acct-top .sub { flex: 1 1 18rem; min-width: 0; }
.acct form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; margin: 0; }
.acct .fld { display: flex; flex-direction: column; gap: 4px; flex: 1 1 14rem; min-width: 0; max-width: 20rem; }
.acct label { font-size: 13px; color: var(--muted); margin: 0; }
.acct .force { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.busy { padding: 12px 24px; }
@media (max-width: 760px) {
.sec-h, .acct, .busy { padding-left: 16px; padding-right: 16px; }
.acct .fld { max-width: none; }
}
</style>
{% endblock %}
{% block content %}
<header class="ui-head">
<div>
<div class="eyebrow-t">Tools</div>
<h1>Share accounts</h1>
<div class="sub">The Samba logins the imaging PCs, Blancco and Clonezilla use on this server. Give each site its own: the shipped defaults are the same at every site.</div>
</div>
</header>
<div class="ui-stack">
{% if busy %}
<div class="notice busy" role="status">
{{ busy | length }} PC{{ 's' if busy | length != 1 }} imaging now ({{ busy[:5] | join(', ') }}{{ ', ...' if busy | length > 5 }}). A password change now breaks their next share connection, so it is refused unless you tick <strong>Change anyway</strong>.
</div>
{% endif %}
<section class="panel ui-list" aria-labelledby="acct-h">
<div class="sec-h"><h2 id="acct-h" class="sec">Accounts</h2>
<div class="sub">Passwords: {{ rule }}. Saving changes Samba and every file that carries the password (boot.wim, the PXE boot menu, the Blancco preferences) at once. They are never shown here.</div></div>
{% for a in accounts %}
<div class="acct">
<div class="acct-top">
<span class="name mono">{{ a.account }}</span>
{% if a.default %}{{ ui.chip('Default password', 'warn') }}{% else %}{{ ui.chip('Site password', 'ok') }}{% endif %}
<span class="sub">{{ used_by[a.account] }}</span>
</div>
<form method="post" action="{{ url_for('share_accounts.save', account=a.account) }}" autocomplete="off"
onsubmit="return confirm('Change the {{ a.account }} password? Anything that still uses the old one (a PC imaging now, a saved login) stops working.');">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="fld">
<label for="pw-{{ loop.index }}">New password</label>
<input type="password" class="form-control" id="pw-{{ loop.index }}" name="password" required
minlength="8" maxlength="64" pattern="[A-Za-z0-9._~\-]{8,64}" autocomplete="new-password">
</div>
<div class="fld">
<label for="cf-{{ loop.index }}">Again</label>
<input type="password" class="form-control" id="cf-{{ loop.index }}" name="confirm" required
minlength="8" maxlength="64" autocomplete="new-password">
</div>
{% if busy %}<label class="force"><input type="checkbox" name="force" value="1"> Change anyway</label>{% endif %}
<button type="submit" class="ui-btn pri sm">Save</button>
</form>
</div>
{% endfor %}
</section>
<section class="panel" style="padding: 16px 24px" aria-labelledby="note-h">
<h2 id="note-h" class="sec" style="font-size: 15px">Where else to change it</h2>
<ul class="sub" style="margin: 6px 0 0; padding-left: 20px">
<li><strong>pxe-upload</strong>: anyone who maps the shares by hand, and <span class="mono">Upload-Image.ps1</span> (it asks for the password).</li>
<li>The server's own Linux login (<span class="mono">pxe</span>) is not set here: it is <span class="mono">pxe_admin_password</span> in <span class="mono">site-vars.yml</span>.</li>
</ul>
</section>
</div>
{% endblock %}