v0.2.0
This commit is contained in:
1 parent
23c7133fa9
commit
8c1b93c14e
16 files changed
+1187
-295
No files matched your search
+115
-9
@@ -62,12 +62,54 @@ func (m Model) updateExportSelect(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.err = "выберите хотя бы одного пользователя (space)"
|
||||
return m, nil
|
||||
}
|
||||
return m.runExport(chosen)
|
||||
m.exportChosen = chosen
|
||||
m.screen = scrExportConfirm
|
||||
m.err = ""
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// updateExportConfirm guards against the accidental case this screen exists
|
||||
// for: selecting users who already have a key issued re-exports them, which
|
||||
// generates a brand new keypair and immediately invalidates whatever archive
|
||||
// they were already given (their old private key stops matching
|
||||
// authorized_keys). Nothing is generated or written until this is confirmed.
|
||||
func (m Model) updateExportConfirm(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if key, ok := msg.(tea.KeyMsg); ok {
|
||||
switch key.String() {
|
||||
case "esc", "n":
|
||||
m.screen = scrExportSelect
|
||||
return m, nil
|
||||
case "enter", "y":
|
||||
return m.runExport(m.exportChosen)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) viewExportConfirm() string {
|
||||
st := m.loadState()
|
||||
body := "Подтвердите экспорт\n\n"
|
||||
anyRotate := false
|
||||
for _, username := range m.exportChosen {
|
||||
u := st.FindUser(username)
|
||||
if u != nil && u.PublicKey != "" {
|
||||
body += fmt.Sprintf(" %-20s %s\n", username, warnStyle.Render("ключ уже выдан — будет ПЕРЕВЫПУЩЕН, старый архив перестанет работать"))
|
||||
anyRotate = true
|
||||
} else {
|
||||
body += fmt.Sprintf(" %-20s новый ключ\n", username)
|
||||
}
|
||||
}
|
||||
if anyRotate {
|
||||
body += "\n" + warnStyle.Render("Внимание: для отмеченных выше пользователей текущий ключ будет заменён.") + "\n"
|
||||
body += warnStyle.Render("Все, у кого сейчас работает старый архив, потеряют доступ, пока им не выдадут новый.") + "\n"
|
||||
}
|
||||
body += "\n[enter/y] подтвердить и сгенерировать [esc/n] отмена, ничего не создано"
|
||||
return borderStyle.Render(body)
|
||||
}
|
||||
|
||||
// runExport performs the actual batch export: for each chosen user it
|
||||
// (re)generates a keypair, rewrites authorized_keys with that user's
|
||||
// currently-allowed hosts, builds the encrypted archive, and records the new
|
||||
@@ -91,13 +133,13 @@ func (m Model) runExport(usernames []string) (tea.Model, tea.Cmd) {
|
||||
failures = append(failures, username+": не найден в состоянии")
|
||||
continue
|
||||
}
|
||||
hosts := st.AllowedHosts(u)
|
||||
res, err := keymgr.BuildUserArchive(m.cfg.ScratchDir, m.cfg.ExportDir, m.cfg.JumpHost, m.cfg.JumpPort, username, password, hosts)
|
||||
forwards := st.AllowedForwards(u)
|
||||
res, err := keymgr.BuildUserArchive(m.cfg.ScratchDir, m.cfg.ExportDir, m.cfg.JumpHost, m.cfg.JumpPort, username, password, forwards)
|
||||
if err != nil {
|
||||
failures = append(failures, username+": "+err.Error())
|
||||
continue
|
||||
}
|
||||
if err := sysadm.WriteAuthorizedKeys(username, res.PublicKeyLine, hosts); err != nil {
|
||||
if err := sysadm.WriteAuthorizedKeys(username, res.PublicKeyLine, forwards); err != nil {
|
||||
failures = append(failures, username+": authorized_keys: "+err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -123,23 +165,87 @@ func (m Model) runExport(usernames []string) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// runConfigUpdate is the lightweight alternative to a full export: when only
|
||||
// a user's group membership changed, their access is already updated
|
||||
// server-side immediately (authorized_keys permitopen=) — this just hands
|
||||
// them a fresh client config reflecting that, without touching their key at
|
||||
// all. See keymgr.BuildConfigOnlyArchive.
|
||||
func (m Model) runConfigUpdate(username string) (tea.Model, tea.Cmd) {
|
||||
st := m.loadState()
|
||||
u := st.FindUser(username)
|
||||
if u == nil {
|
||||
m.err = fmt.Sprintf("пользователь %q не найден", username)
|
||||
return m, nil
|
||||
}
|
||||
if u.PublicKey == "" {
|
||||
m.err = fmt.Sprintf("у пользователя %q ещё нет ключа — используйте «Экспорт архивов»", username)
|
||||
return m, nil
|
||||
}
|
||||
password, err := keymgr.GenerateBatchPassword(20)
|
||||
if err != nil {
|
||||
m.err = err.Error()
|
||||
return m, nil
|
||||
}
|
||||
forwards := st.AllowedForwards(u)
|
||||
res, err := keymgr.BuildConfigOnlyArchive(m.cfg.ScratchDir, m.cfg.ExportDir, m.cfg.JumpHost, m.cfg.JumpPort, username, password, forwards)
|
||||
if err != nil {
|
||||
m.err = err.Error()
|
||||
return m, nil
|
||||
}
|
||||
m.logAudit(username, "admin", "config-only update issued (key unchanged)")
|
||||
m.configUpdateUser = username
|
||||
m.configUpdateArchive = res.ArchivePath
|
||||
m.configUpdatePass = password
|
||||
m.err = ""
|
||||
m.screen = scrConfigUpdateResult
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) updateConfigUpdateResult(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if key, ok := msg.(tea.KeyMsg); ok {
|
||||
if key.String() == "esc" || key.String() == "enter" {
|
||||
m.screen = scrUsers
|
||||
m.err, m.status = "", ""
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) viewConfigUpdateResult() string {
|
||||
body := fmt.Sprintf("Конфиг для %s обновлён — ключ НЕ менялся.\n\n", m.configUpdateUser)
|
||||
body += "Пароль архива (покажется только сейчас):\n\n"
|
||||
body += titleStyle.Render(m.configUpdatePass) + "\n\n"
|
||||
body += warnStyle.Render("Сообщите пароль отдельным от архива каналом.") + "\n\n"
|
||||
body += fmt.Sprintf("Архив: %s\n\n", m.configUpdateArchive)
|
||||
body += "Пользователю нужно только заменить файл config тем, что внутри — ключ и\n"
|
||||
body += "остальные файлы трогать не нужно.\n"
|
||||
body += "\n[enter/esc] в меню"
|
||||
return borderStyle.Render(body)
|
||||
}
|
||||
|
||||
func (m Model) viewExportSelect() string {
|
||||
body := fmt.Sprintf("Экспорт архивов — выберите пользователей (общий пароль архива на всю партию)\n\n")
|
||||
st := m.loadState()
|
||||
body := "Экспорт архивов — выберите пользователей (общий пароль архива на всю партию)\n\n"
|
||||
if len(m.exportOrder) == 0 {
|
||||
body += warnStyle.Render("Нет активных пользователей. Сначала создайте пользователя.") + "\n"
|
||||
}
|
||||
for i, u := range m.exportOrder {
|
||||
for i, uname := range m.exportOrder {
|
||||
cursor := " "
|
||||
if i == m.exportCursor {
|
||||
cursor = "> "
|
||||
}
|
||||
check := "[ ]"
|
||||
if m.exportSel[u] {
|
||||
if m.exportSel[uname] {
|
||||
check = "[x]"
|
||||
}
|
||||
body += fmt.Sprintf("%s%s %s\n", cursor, check, u)
|
||||
status := "ключ не выдан — новый"
|
||||
if u := st.FindUser(uname); u != nil && u.PublicKey != "" {
|
||||
status = warnStyle.Render("ключ уже выдан — будет ПЕРЕВЫПУЩЕН")
|
||||
}
|
||||
body += fmt.Sprintf("%s%s %-20s %s\n", cursor, check, uname, status)
|
||||
}
|
||||
body += "\n[space] отметить [a] отметить всех [enter] сгенерировать архивы [esc] назад"
|
||||
body += "\n[space] отметить [a] отметить всех [enter] далее (спросит подтверждение) [esc] назад"
|
||||
return borderStyle.Render(body)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user