78 lines
2.7 KiB
Go
78 lines
2.7 KiB
Go
package keymgr
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"ssh-access-tui/internal/model"
|
|
)
|
|
|
|
// ConfigOnlyResult describes a lightweight config-refresh archive: no
|
|
// keypair is generated or touched.
|
|
type ConfigOnlyResult struct {
|
|
Username string
|
|
ArchivePath string
|
|
}
|
|
|
|
// BuildConfigOnlyArchive packages just a fresh client config + instructions
|
|
// (no key files at all) reflecting the user's CURRENT allowed forwards.
|
|
//
|
|
// This exists for the common case where only a user's group membership
|
|
// changed: server-side access is already updated immediately (see
|
|
// sysadm.WriteAuthorizedKeys / resyncGroupUsers, called the moment groups
|
|
// change), and their existing private key is still perfectly valid — only
|
|
// the local ssh config listing which services are reachable is now stale.
|
|
// Reissuing a whole new keypair for that would be needless churn (and
|
|
// needless key material handed around). The config's IdentityFile line
|
|
// references the same deterministic filename GeneratePair always used
|
|
// (username + "_jump_key"), so it matches the key the user already has.
|
|
func BuildConfigOnlyArchive(scratchDir, outDir, jumpHost string, jumpPort int, username, password string, forwards []model.Forward, proxyJump bool) (*ConfigOnlyResult, error) {
|
|
userScratch := filepath.Join(scratchDir, username+"_configonly")
|
|
if err := os.MkdirAll(userScratch, 0o700); err != nil {
|
|
return nil, fmt.Errorf("create scratch dir: %w", err)
|
|
}
|
|
defer Wipe(userScratch)
|
|
|
|
keyFileName := username + "_jump_key"
|
|
|
|
var cfg, instr string
|
|
if proxyJump {
|
|
cfg = RenderClientConfigProxyJump(jumpHost, jumpPort, username, keyFileName, forwards)
|
|
instr = RenderConfigUpdateInstructionsProxyJump(username, forwards)
|
|
} else {
|
|
allocated := AllocateLocalPorts(forwards)
|
|
cfg = RenderClientConfig(jumpHost, jumpPort, username, keyFileName, allocated)
|
|
instr = RenderConfigUpdateInstructions(username, allocated)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(userScratch, "config"), []byte(cfg), 0o600); err != nil {
|
|
return nil, fmt.Errorf("write config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(userScratch, "INSTRUCTIONS.txt"), []byte(instr), 0o600); err != nil {
|
|
return nil, fmt.Errorf("write instructions: %w", err)
|
|
}
|
|
|
|
if err := os.MkdirAll(outDir, 0o750); err != nil {
|
|
return nil, fmt.Errorf("create output dir: %w", err)
|
|
}
|
|
archivePath := filepath.Join(outDir, username+"_config_update.7z")
|
|
_ = os.Remove(archivePath)
|
|
|
|
cmd := exec.Command("7z", "a",
|
|
"-p"+password,
|
|
"-mhe=on",
|
|
"-mx=9",
|
|
archivePath,
|
|
filepath.Join(userScratch, "config"),
|
|
filepath.Join(userScratch, "INSTRUCTIONS.txt"),
|
|
)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return nil, fmt.Errorf("7z: %w: %s", err, out)
|
|
}
|
|
|
|
return &ConfigOnlyResult{Username: username, ArchivePath: archivePath}, nil
|
|
}
|