package internal import ( `errors` `fmt` `math` `net/http` `strconv` `strings` `github.com/creachadair/otp/otpauth` `github.com/hashicorp/vault-client-go` `github.com/hashicorp/vault-client-go/schema` `r00t2.io/gosecret` `r00t2.io/goutils/multierr` ) func (c *Client) Close() (err error) { if err = c.ssvc.Close(); err != nil { c.errChan <- err } close(c.errChan) <-c.errsDone return } func (c *Client) DeleteAllVaultKeys() { var name string for name, _ = range c.inVault { c.wg.Add(1) go func(nm string) { var vErr error var resp *vault.Response[map[string]interface{}] defer c.wg.Done() if resp, vErr = c.vc.Secrets.TotpDeleteKey( c.ctx, nm, vault.WithMountPath(c.vmnt), ); vErr != nil { c.errChan <- vErr return } _ = resp }(name) } c.wg.Wait() } func (c *Client) Errors() (errs []error) { errs = make([]error, 0) if c.mErr == nil || c.mErr.IsEmpty() { return } errs = c.mErr.Errors c.mErr = multierr.NewMultiError(nil) return } // currently it doesn't update out-of-sync keys and only syncs from SecretService to Vault. TODO. func (c *Client) Sync() { c.addVault() c.wg.Wait() } func (c *Client) addVault() { var name string var wsec wrappedSsSecret for name, wsec = range c.inSS { c.wg.Add(1) go func(nm string, sec wrappedSsSecret) { var vErr error var i int var ok bool var digSize int var numSeconds int // var digSize int32 var otpUrl *otpauth.URL var req schema.TotpCreateKeyRequest var resp *vault.Response[map[string]interface{}] defer c.wg.Done() if _, ok = c.inVault[nm]; ok { return } if i, vErr = strconv.Atoi(sec.secret.Attrs["digits"]); vErr != nil { c.errChan <- vErr return } if i > math.MaxInt32 { c.errChan <- fmt.Errorf("digits too large (%d > %d)", i, math.MaxInt32) return } // digSize = int32(i) digSize = i if i, vErr = strconv.Atoi(sec.secret.Attrs["period"]); vErr != nil { c.errChan <- vErr return } if i > math.MaxInt32 { c.errChan <- fmt.Errorf("period too large (%d > %d)", i, math.MaxInt32) return } numSeconds = i otpUrl = &otpauth.URL{ Type: "TOTP", // TODO: https://github.com/dkosmari/gnome-shell-extension-totp/issues/15 Issuer: sec.secret.Attrs["issuer"], Account: sec.secret.Attrs["name"], RawSecret: string(sec.secret.Secret.Value), Algorithm: strings.ToUpper(strings.ReplaceAll(sec.secret.Attrs["algorithm"], "-", "")), Digits: digSize, Period: numSeconds, Counter: 0, } fmt.Printf("Adding '%s' to '%s'\n", nm, c.vmnt) // https://developer.hashicorp.com/vault/api-docs/secret/totp#create-key // https://pkg.go.dev/github.com/hashicorp/vault-client-go/schema#TotpCreateKeyRequest req = schema.TotpCreateKeyRequest{ // AccountName: sec.secret.Attrs["name"], // Algorithm: strings.ToUpper(strings.ReplaceAll(sec.secret.Attrs["algorithm"], "-", "")), // Digits: digSize, // Exported: false, // Generate: false, // Issuer: sec.secret.Attrs["issuer"], // Key: string(sec.secret.Secret.Value), // // KeySize: 0, // Period: sec.secret.Attrs["period"], // // QrSize: 0, // // Skew: 0, Url: otpUrl.String(), } if resp, vErr = c.vc.Secrets.TotpCreateKey( c.ctx, nm, req, vault.WithMountPath(c.vmnt), ); vErr != nil { c.errChan <- fmt.Errorf("error adding '%s': %v", nm, vErr) return } _ = resp }(name, wsec) } } func (c *Client) getSS() { var err error var idx int var nm string var spl []string var item *gosecret.Item var secrets []*gosecret.Item defer c.wg.Done() if secrets, err = c.scoll.Items(); err != nil { c.errChan <- err return } c.inSS = make(map[string]wrappedSsSecret) for _, item = range secrets { // Unlike KVv2 names, Vault is *very* picky about key names. Boo. // nm = fmt.Sprintf("%s/%s", item.Attrs["issuer"], item.Attrs["name"]) nm = fmt.Sprintf( "%s"+ "."+ "%s", normalizeVaultNm(item.Attrs["issuer"]), normalizeVaultNm(item.Attrs["name"]), ) fmt.Printf("'%s' from '%s' => '%s'\n", item.Attrs["name"], item.Attrs["issuer"], nm) spl = strings.SplitN(item.LabelName, ":", 2) if idx, err = strconv.Atoi(spl[0]); err != nil { c.errChan <- err continue } c.inSS[nm] = wrappedSsSecret{ id: idx, strippedNm: nm, nm: item.LabelName, secret: item, } if idx > c.lastIdx { c.lastIdx = idx } } return } func (c *Client) getVault() { var err error var nm string var nms []string var vErr *vault.ResponseError var totpKey *vault.Response[map[string]interface{}] defer c.wg.Done() var resp *vault.Response[schema.StandardListResponse] resp, err = c.vc.Secrets.TotpListKeys( c.ctx, vault.WithMountPath(c.vmnt), ) if err != nil { // no TOTP yet if errors.As(err, &vErr) && vErr.StatusCode == http.StatusNotFound { c.inVault = make(map[string]map[string]interface{}) err = nil return } c.errChan <- err return } nms = resp.Data.Keys c.inVault = make(map[string]map[string]interface{}) for _, nm = range nms { if totpKey, err = c.vc.Secrets.TotpReadKey( c.ctx, nm, vault.WithMountPath(c.vmnt), ); err != nil { c.errChan <- err continue } c.inVault[nm] = totpKey.Data } return } func (c *Client) readErrs() { var err error for err = range c.errChan { if err != nil { c.mErr.AddError(err) } } c.errsDone <- true }