//go:build windows package wincred import ( "bytes" "net/url" "strings" winc "github.com/danieljoos/wincred" "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/registryurl" ) // Wincred handles secrets using the Windows credential service. type Wincred struct{} // Add adds new credentials to the windows credentials manager. func (h Wincred) Add(creds *credentials.Credentials) error { credsLabels := []byte(credentials.CredsLabel) g := winc.NewGenericCredential(creds.ServerURL) g.UserName = creds.Username g.CredentialBlob = []byte(creds.Secret) g.Persist = winc.PersistLocalMachine g.Attributes = []winc.CredentialAttribute{{Keyword: "label", Value: credsLabels}} return g.Write() } // Delete removes credentials from the windows credentials manager. func (h Wincred) Delete(serverURL string) error { g, err := winc.GetGenericCredential(serverURL) if g == nil { return nil } if err != nil { return err } return g.Delete() } // Get retrieves credentials from the windows credentials manager. func (h Wincred) Get(serverURL string) (string, string, error) { target, err := getTarget(serverURL) if err != nil { return "", "", err } else if target == "" { return "", "", credentials.NewErrCredentialsNotFound() } g, _ := winc.GetGenericCredential(target) if g == nil { return "", "", credentials.NewErrCredentialsNotFound() } for _, attr := range g.Attributes { if strings.Compare(attr.Keyword, "label") == 0 && bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 { return g.UserName, string(g.CredentialBlob), nil } } return "", "", credentials.NewErrCredentialsNotFound() } func getTarget(serverURL string) (string, error) { s, err := registryurl.Parse(serverURL) if err != nil { return serverURL, nil } creds, err := winc.List() if err != nil { return "", err } var targets []string for i := range creds { attrs := creds[i].Attributes for _, attr := range attrs { if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) { targets = append(targets, creds[i].TargetName) } } } if target, found := findMatch(s, targets, exactMatch); found { return target, nil } if target, found := findMatch(s, targets, approximateMatch); found { return target, nil } return "", nil } func findMatch(serverUrl *url.URL, targets []string, matches func(url.URL, url.URL) bool) (string, bool) { for _, target := range targets { tURL, err := registryurl.Parse(target) if err != nil { continue } if matches(*serverUrl, *tURL) { return target, true } } return "", false } func exactMatch(serverURL, target url.URL) bool { return serverURL.String() == target.String() } func approximateMatch(serverURL, target url.URL) bool { // if scheme is missing assume it is the same as target if serverURL.Scheme == "" { serverURL.Scheme = target.Scheme } // if port is missing assume it is the same as target if serverURL.Port() == "" && target.Port() != "" { serverURL.Host = serverURL.Host + ":" + target.Port() } // if path is missing assume it is the same as target if serverURL.Path == "" { serverURL.Path = target.Path } return serverURL.String() == target.String() } // List returns the stored URLs and corresponding usernames for a given credentials label. func (h Wincred) List() (map[string]string, error) { creds, err := winc.List() if err != nil { return nil, err } resp := make(map[string]string) for i := range creds { attrs := creds[i].Attributes for _, attr := range attrs { if strings.Compare(attr.Keyword, "label") == 0 && bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 { resp[creds[i].TargetName] = creds[i].UserName } } } return resp, nil }