mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-28 07:11:36 +05:30
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9d3010165 | |||
| e7bd3957ae | |||
| cfd6d21216 | |||
| ab29a6c87b | |||
| 576efaa084 | |||
| 9d6cdddf25 | |||
| d8e34f8743 | |||
| b1d5bf0326 | |||
| 50b162c340 | |||
| 833d2c334f | |||
| 9651bf7802 | |||
| 26274da6cf | |||
| f4cdabf916 | |||
| c7514a0999 |
@@ -44,8 +44,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
|
- ubuntu-24.04
|
||||||
- ubuntu-22.04
|
- ubuntu-22.04
|
||||||
- ubuntu-20.04
|
|
||||||
- macOS-15
|
- macOS-15
|
||||||
- macOS-14
|
- macOS-14
|
||||||
- macOS-13
|
- macOS-13
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ module github.com/docker/docker-credential-helpers
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
|
retract (
|
||||||
|
v0.9.1 // osxkeychain: a regression caused backward-incompatibility with earlier versions
|
||||||
|
v0.9.0 // osxkeychain: a regression caused backward-incompatibility with earlier versions
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/danieljoos/wincred v1.2.2
|
github.com/danieljoos/wincred v1.2.2
|
||||||
github.com/keybase/go-keychain v0.0.1
|
github.com/keybase/go-keychain v0.0.1
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker-credential-helpers/credentials"
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
@@ -44,6 +46,17 @@ func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
|||||||
item.SetLabel(credentials.CredsLabel)
|
item.SetLabel(credentials.CredsLabel)
|
||||||
item.SetAccount(creds.Username)
|
item.SetAccount(creds.Username)
|
||||||
item.SetData([]byte(creds.Secret))
|
item.SetData([]byte(creds.Secret))
|
||||||
|
// Prior to v0.9, the credential helper was searching for credentials with
|
||||||
|
// the "dflt" authentication type (see [1]). Since v0.9.0, Get doesn't use
|
||||||
|
// that attribute anymore, and v0.9.0 - v0.9.2 were not setting it here
|
||||||
|
// either.
|
||||||
|
//
|
||||||
|
// In order to keep compatibility with older versions, we need to store
|
||||||
|
// credentials with this attribute set. This way, credentials stored with
|
||||||
|
// newer versions can be retrieved by older versions.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/docker/docker-credential-helpers/blob/v0.8.2/osxkeychain/osxkeychain.c#L66
|
||||||
|
item.SetAuthenticationType("dflt")
|
||||||
if err := splitServer(creds.ServerURL, item); err != nil {
|
if err := splitServer(creds.ServerURL, item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -117,28 +130,44 @@ func (h Osxkeychain) List() (map[string]string, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if len(res) == 0 {
|
|
||||||
return nil, credentials.NewErrCredentialsNotFound()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := make(map[string]string)
|
resp := make(map[string]string)
|
||||||
for _, r := range res {
|
for _, r := range res {
|
||||||
if r.Path == "" {
|
proto := "http"
|
||||||
continue
|
if r.Protocol == kSecProtocolTypeHTTPS {
|
||||||
|
proto = "https"
|
||||||
}
|
}
|
||||||
resp[r.Path] = r.Account
|
host := r.Server
|
||||||
|
if r.Port != 0 {
|
||||||
|
host = net.JoinHostPort(host, strconv.Itoa(int(r.Port)))
|
||||||
|
}
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: proto,
|
||||||
|
Host: host,
|
||||||
|
Path: r.Path,
|
||||||
|
}
|
||||||
|
resp[u.String()] = r.Account
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Hardcoded protocol types matching their Objective-C equivalents.
|
||||||
|
// https://developer.apple.com/documentation/security/ksecattrprotocolhttps?language=objc
|
||||||
|
kSecProtocolTypeHTTPS = "htps" // This is NOT a typo.
|
||||||
|
// https://developer.apple.com/documentation/security/ksecattrprotocolhttp?language=objc
|
||||||
|
kSecProtocolTypeHTTP = "http"
|
||||||
|
)
|
||||||
|
|
||||||
func splitServer(serverURL string, item keychain.Item) error {
|
func splitServer(serverURL string, item keychain.Item) error {
|
||||||
u, err := registryurl.Parse(serverURL)
|
u, err := registryurl.Parse(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
item.SetProtocol("https")
|
item.SetProtocol(kSecProtocolTypeHTTPS)
|
||||||
if u.Scheme == "http" {
|
if u.Scheme == "http" {
|
||||||
item.SetProtocol("http")
|
item.SetProtocol(kSecProtocolTypeHTTP)
|
||||||
}
|
}
|
||||||
item.SetServer(u.Hostname())
|
item.SetServer(u.Hostname())
|
||||||
if p := u.Port(); p != "" {
|
if p := u.Port(); p != "" {
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ func TestOSXKeychainHelper(t *testing.T) {
|
|||||||
Username: "foobar",
|
Username: "foobar",
|
||||||
Secret: "foobarbaz",
|
Secret: "foobarbaz",
|
||||||
}
|
}
|
||||||
creds1 := &credentials.Credentials{
|
|
||||||
ServerURL: "https://foobar.example.com:2376/v2",
|
|
||||||
Username: "foobarbaz",
|
|
||||||
Secret: "foobar",
|
|
||||||
}
|
|
||||||
helper := Osxkeychain{}
|
helper := Osxkeychain{}
|
||||||
if err := helper.Add(creds); err != nil {
|
if err := helper.Add(creds); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -43,19 +38,49 @@ func TestOSXKeychainHelper(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.Add(creds1)
|
if _, ok := auths[creds.ServerURL]; !ok {
|
||||||
defer helper.Delete(creds1.ServerURL)
|
t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths)
|
||||||
newauths, err := helper.List()
|
|
||||||
if len(newauths)-len(auths) != 1 {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
|
|
||||||
}
|
|
||||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert another token and check if it is in the list
|
||||||
|
creds1 := &credentials.Credentials{
|
||||||
|
ServerURL: "https://foobar.example.com:2376/v2",
|
||||||
|
Username: "foobarbaz",
|
||||||
|
Secret: "foobar",
|
||||||
|
}
|
||||||
|
helper.Add(creds1)
|
||||||
|
defer helper.Delete(creds1.ServerURL)
|
||||||
|
|
||||||
|
auths, err = helper.List()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("operation List failed: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := auths[creds.ServerURL]; !ok {
|
||||||
|
t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths)
|
||||||
|
}
|
||||||
|
if _, ok := auths[creds1.ServerURL]; !ok {
|
||||||
|
t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the 1st token inserted
|
||||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auths, err = helper.List()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("operation List failed: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First token should have been deleted
|
||||||
|
if _, ok := auths[creds.ServerURL]; ok {
|
||||||
|
t.Fatalf("server %s was not deleted, got: %+v", creds.ServerURL, auths)
|
||||||
|
}
|
||||||
|
// Second token should still be there
|
||||||
|
if _, ok := auths[creds1.ServerURL]; !ok {
|
||||||
|
t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
|
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
|
||||||
@@ -116,6 +141,39 @@ func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOSXKeychainHelperStoreWithUncleanPath(t *testing.T) {
|
||||||
|
helper := Osxkeychain{}
|
||||||
|
creds := &credentials.Credentials{
|
||||||
|
ServerURL: "https://::1:8080//////location/../../hello",
|
||||||
|
Username: "testuser",
|
||||||
|
Secret: "testsecret",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean store before and after the test.
|
||||||
|
defer helper.Delete(creds.ServerURL)
|
||||||
|
if err := helper.Delete(creds.ServerURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||||
|
t.Errorf("prepare: failed to delete '%s': %v", creds.ServerURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the credentials
|
||||||
|
if err := helper.Add(creds); err != nil {
|
||||||
|
t.Fatalf("Error: failed to store credentials with unclean path %q: %s", creds.ServerURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve and verify credentials
|
||||||
|
username, secret, err := helper.Get(creds.ServerURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: failed to retrieve credentials with unclean path %q: %s", creds.ServerURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != creds.Username {
|
||||||
|
t.Errorf("Error: expected username %s, got %s", creds.Username, username)
|
||||||
|
}
|
||||||
|
if secret != creds.Secret {
|
||||||
|
t.Errorf("Error: expected secret %s, got %s", creds.Secret, secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
|
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
|
||||||
// returned.
|
// returned.
|
||||||
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
|
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user