init
This commit is contained in:
commit
23c7133fa9
33 files changed
+2896
No files matched your search
@@ -0,0 +1,89 @@
|
||||
package keymgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"ssh-access-tui/internal/model"
|
||||
)
|
||||
|
||||
// BuildResult describes one user's finished delivery archive.
|
||||
type BuildResult struct {
|
||||
Username string
|
||||
ArchivePath string
|
||||
Fingerprint string
|
||||
PublicKeyLine string // capture before the scratch dir (and its .pub file) is wiped
|
||||
}
|
||||
|
||||
// BuildUserArchive generates a fresh keypair for username, writes the
|
||||
// client config and Windows instructions alongside it in a scratch
|
||||
// directory, packs everything into a single AES-256-encrypted 7z archive
|
||||
// under outDir, and then wipes the scratch directory (including the
|
||||
// plaintext private key) from disk. password is shared across one export
|
||||
// batch and must be communicated to the recipient out-of-band.
|
||||
func BuildUserArchive(scratchDir, outDir, jumpHost string, jumpPort int, username, password string, hosts []model.Host) (*BuildResult, error) {
|
||||
userScratch := filepath.Join(scratchDir, username)
|
||||
if err := os.MkdirAll(userScratch, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create scratch dir: %w", err)
|
||||
}
|
||||
defer Wipe(userScratch)
|
||||
|
||||
kp, err := GeneratePair(userScratch, username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate keypair: %w", err)
|
||||
}
|
||||
keyFileName := filepath.Base(kp.PrivateKeyPath)
|
||||
|
||||
cfg := RenderClientConfig(jumpHost, jumpPort, username, keyFileName, hosts)
|
||||
if err := os.WriteFile(filepath.Join(userScratch, "config"), []byte(cfg), 0o600); err != nil {
|
||||
return nil, fmt.Errorf("write config: %w", err)
|
||||
}
|
||||
|
||||
instr := RenderInstructions(username, jumpHost, keyFileName, hosts)
|
||||
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+"_ssh_access.7z")
|
||||
_ = os.Remove(archivePath) // 7z refuses to overwrite silently-appends; start clean
|
||||
|
||||
cmd := exec.Command("7z", "a",
|
||||
"-p"+password,
|
||||
"-mhe=on", // encrypt file names/headers too, not just contents
|
||||
"-mx=9",
|
||||
archivePath,
|
||||
filepath.Join(userScratch, keyFileName),
|
||||
filepath.Join(userScratch, keyFileName+".pub"),
|
||||
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 &BuildResult{
|
||||
Username: username,
|
||||
ArchivePath: archivePath,
|
||||
Fingerprint: kp.Fingerprint,
|
||||
PublicKeyLine: kp.PublicKeyLine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Wipe securely removes a scratch directory (best-effort shred of file
|
||||
// contents, then unlink) so the private key does not linger in free disk
|
||||
// space or in any backup that might sweep the tool's working directory.
|
||||
func Wipe(dir string) {
|
||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
_ = exec.Command("shred", "-u", "-n", "1", path).Run()
|
||||
return nil
|
||||
})
|
||||
_ = os.RemoveAll(dir)
|
||||
}
|
||||
Reference in new issue
Block a user