feat(cli): two-key role routing + init bootstrap
openStore(role) selects the DEK wrap slot; admin commands require EMCLI_ADMIN_KEY (admin slot only, no agent fallback); init writes both slots from both keys. Test helpers seed the wrap slots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+40
-9
@@ -25,17 +25,48 @@ func realMailer(acc store.Account) (Mailer, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// openStore loads the key and opens the DB, returning a human-readable error string.
|
||||
func openStore() (*store.Store, error) {
|
||||
key, err := crypto.KeyFromEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// commandRole maps a command to the privilege it requires. Admin commands
|
||||
// mutate configuration or expose oversight data; everything else is agent.
|
||||
func commandRole(cmd string) store.Role {
|
||||
switch cmd {
|
||||
case "account", "whitelist", "config", "audit":
|
||||
return store.RoleAdmin
|
||||
default: // list, get, search, ack, send, doctor
|
||||
return store.RoleAgent
|
||||
}
|
||||
}
|
||||
|
||||
// openStore resolves the keys for the role, opens the DB, and unlocks the DEK.
|
||||
// Admin commands require EMCLI_ADMIN_KEY and unlock the admin slot only; agent
|
||||
// commands use EMCLI_KEY (falling back to the admin key if that is all there is).
|
||||
func openStore(role store.Role) (*store.Store, error) {
|
||||
adminKey, adminErr := crypto.AdminKeyFromEnv()
|
||||
agentKey, agentErr := crypto.AgentKeyFromEnv()
|
||||
|
||||
switch role {
|
||||
case store.RoleAdmin:
|
||||
if adminErr != nil {
|
||||
return nil, fmt.Errorf("this command requires EMCLI_ADMIN_KEY (admin privilege)")
|
||||
}
|
||||
case store.RoleAgent:
|
||||
if agentErr != nil && adminErr != nil {
|
||||
return nil, agentErr // "EMCLI_KEY is not set"
|
||||
}
|
||||
}
|
||||
|
||||
path, err := store.DefaultDBPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return store.Open(path, key)
|
||||
st, err := store.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := st.Unlock(role, adminKey, agentKey); err != nil {
|
||||
st.Close()
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func realSender(acc store.Account, m mail.OutgoingMessage) error {
|
||||
@@ -79,7 +110,7 @@ func runDoctor(args []string, out, errOut io.Writer) int {
|
||||
}
|
||||
return 2
|
||||
}
|
||||
st, err := openStore()
|
||||
st, err := openStore(store.RoleAgent)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "emcli: %v\n", err)
|
||||
return 1
|
||||
@@ -159,7 +190,7 @@ func runAgent(cmd string, args []string, out, errOut io.Writer) int {
|
||||
_ = Failure(CodeUsage, "--account is required").Write(out)
|
||||
return 2
|
||||
}
|
||||
st, err := openStore()
|
||||
st, err := openStore(store.RoleAgent)
|
||||
if err != nil {
|
||||
_ = Failure(CodeConfig, err.Error()).Write(out)
|
||||
return 1
|
||||
@@ -249,7 +280,7 @@ func runSend(args []string, out, errOut io.Writer) int {
|
||||
_ = Failure(CodeUsage, "--account is required").Write(out)
|
||||
return 2
|
||||
}
|
||||
st, err := openStore()
|
||||
st, err := openStore(store.RoleAgent)
|
||||
if err != nil {
|
||||
_ = Failure(CodeConfig, err.Error()).Write(out)
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user