diff --git a/cli/cmd/smcli/main.go b/cli/cmd/smcli/main.go index af447ac..c31e87e 100644 --- a/cli/cmd/smcli/main.go +++ b/cli/cmd/smcli/main.go @@ -424,6 +424,11 @@ func main() { for _, f := range sync.Folders { fmt.Println(f.ID, f.Name) } + case "setfield": + if len(os.Args) < 5 { + fatal("setfield ") + } + err = cmdSetField(os.Args[2], os.Args[3], os.Args[4]) case "convert": if len(os.Args) < 3 { fatal("convert ") @@ -548,3 +553,98 @@ func cmdConvert(name string) error { } func urisOf(pc *PlainCipher) []string { return pc.URIs } + +// cmdSetField surgically updates one key on an existing item: login.password, +// login.username, an existing custom field, or appends a new hidden field. +func cmdSetField(name, key, value string) error { + s, err := loadState() + if err != nil { + return err + } + c, err := newClientFromState(s) + if err != nil { + return err + } + raw, err := c.Sync() + if err != nil { + return err + } + var sync struct { + Ciphers []json.RawMessage `json:"ciphers"` + } + if err := json.Unmarshal(raw, &sync); err != nil { + return err + } + var cr *cipherRaw + for _, r := range sync.Ciphers { + var x cipherRaw + if json.Unmarshal(r, &x) == nil && x.ID != "" { + pc2, derr := DecryptCipher(c.UserSymKey, r) + if derr == nil && pc2.Name == name { + cr = &x + break + } + } + } + if cr == nil { + return fmt.Errorf("item not found: %s", name) + } + ck, err := cipherKeyFor(c.UserSymKey, cr.Key) + if err != nil { + return err + } + switch strings.ToLower(key) { + case "password": + p, err := encStr(c.UserSymKey, value) + if err != nil { + return err + } + if cr.Login == nil { + cr.Login = &CipherLogin{} + } + cr.Login.Password = &p + case "username": + p, err := encStr(c.UserSymKey, value) + if err != nil { + return err + } + if cr.Login == nil { + cr.Login = &CipherLogin{} + } + cr.Login.Username = &p + default: + found := false + for i := range cr.Fields { + fn, derr := decStr(ck, ck, cr.Fields[i].Name) + if derr == nil && fn == key { + fv, err := encStr(c.UserSymKey, value) + if err != nil { + return err + } + cr.Fields[i].Value = &fv + found = true + break + } + } + if !found { + fn, _ := encStr(c.UserSymKey, key) + fv, _ := encStr(c.UserSymKey, value) + cr.Fields = append(cr.Fields, CipherField{Type: 1, Name: &fn, Value: &fv}) + } + } + payload := map[string]any{"type": cr.Type, "name": cr.Name} + if cr.Login != nil { + payload["login"] = cr.Login + } + if cr.Fields != nil { + payload["fields"] = cr.Fields + } + if cr.Notes != nil { + payload["notes"] = *cr.Notes + } + if _, err := c.EditCipher(cr.ID, payload); err != nil { + return err + } + fmt.Println("updated:", name, key) + return nil +} diff --git a/cli/smcli b/cli/smcli index 80e42cc..f42fb64 100755 Binary files a/cli/smcli and b/cli/smcli differ diff --git a/docker/compose.yaml b/docker/compose.yaml index 8c1b13f..9272a78 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -10,7 +10,7 @@ name: knel-secretsmanager services: smcli: - image: git.knownelement.com/knel/knel-secretsmanager-cli@sha256:554de767afbf80ca2db772edda350200b741c211495dda3fde96c6496a7a13cc + image: git.knownelement.com/knel/knel-secretsmanager-cli@sha256:46d80c0a0ef53a9303dd54b3799a64a61cd4e4dc892282bc4d8a220378117ce9 container_name: ukrrs-secretsmgr-cli restart: unless-stopped entrypoint: ["sleep", "infinity"]