diff --git a/pkg/docker/config/config_linux.go b/pkg/docker/config/config_linux.go index 4d66a50df5..b9f3322b85 100644 --- a/pkg/docker/config/config_linux.go +++ b/pkg/docker/config/config_linux.go @@ -6,8 +6,11 @@ import ( "github.com/containers/image/pkg/keyctl" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) +const keyDescribePrefix = "container-registry-login:" + func getAuthFromKernelKeyring(registry string) (string, string, error) { userkeyring, err := keyctl.UserKeyring() if err != nil { @@ -41,6 +44,37 @@ func deleteAuthFromKernelKeyring(registry string) error { return key.Unlink() } +func removeAllAuthFromKernelKeyring() error { + userkeyring, err := keyctl.UserKeyring() + if err != nil { + return err + } + keyIDs, err := userkeyring.ReadUserKeyring() + if err != nil { + return err + } + for _, kID := range keyIDs { + keyAttr, err := keyctl.Describe(kID) + if err != nil { + return nil + } + // split string "type;uid;gid;perm;description" + keyAttrs := strings.SplitN(keyAttr, ";", 5) + if len(keyAttrs) == 0 { + return errors.Errorf("Key attributes of %d are not avaliable", kID.ID()) + } + keyDescribe := keyAttrs[len(keyAttrs)-1] + if strings.HasPrefix(keyDescribe, keyDescribePrefix) { + err := keyctl.Unlink(userkeyring, kID) + if err != nil { + return errors.Wrapf(err, "error unlinking key %d", kID.ID()) + } + logrus.Debugf("unlink key %d:%s", kID.ID(), keyAttr) + } + } + return nil +} + func setAuthToKernelKeyring(registry, username, password string) error { keyring, err := keyctl.SessionKeyring() if err != nil { diff --git a/pkg/keyctl/key.go b/pkg/keyctl/key.go index e4396a9df7..9f929e9d3e 100644 --- a/pkg/keyctl/key.go +++ b/pkg/keyctl/key.go @@ -62,3 +62,12 @@ func (k *Key) Unlink() error { _, err := unix.KeyctlInt(unix.KEYCTL_UNLINK, int(k.id), int(k.ring), 0, 0) return err } + +// Describe returns a string describing the attributes of a specified key +func Describe(kID ID) (string, error) { + keyDescribe, err := unix.KeyctlString(unix.KEYCTL_DESCRIBE, int(kID.ID())) + if err != nil { + return "", err + } + return keyDescribe, nil +} diff --git a/pkg/keyctl/keyring.go b/pkg/keyctl/keyring.go index 6e029c9235..da63ac5377 100644 --- a/pkg/keyctl/keyring.go +++ b/pkg/keyctl/keyring.go @@ -11,6 +11,10 @@ package keyctl import ( + "encoding/binary" + "log" + "unsafe" + "golang.org/x/sys/unix" ) @@ -19,10 +23,12 @@ type Keyring interface { ID Add(string, []byte) (*Key, error) Search(string) (*Key, error) + ReadUserKeyring() ([]*Key, error) } type keyring struct { - id keyID + id keyID + size int } // ID is unique 32-bit serial number identifiers for all Keys and Keyrings have. @@ -77,3 +83,69 @@ func Link(parent Keyring, child ID) error { _, err := unix.KeyctlInt(unix.KEYCTL_LINK, int(child.ID()), int(parent.ID()), 0, 0) return err } + +// ReadUserKeyring reads user keyring and returns slice of Key with id field(key_serial_t) representing the IDs of all the keys that are linked to it +func (kr *keyring) ReadUserKeyring() ([]*Key, error) { + var ( + b []byte + err error + sizeRead int + keyArr []*Key + ) + + if kr.size == 0 { + kr.size = 4 + } + + size := kr.size + + b = make([]byte, size) + sizeRead = size + 1 + for sizeRead > size { + r1, err := unix.KeyctlBuffer(unix.KEYCTL_READ, unix.KEY_SPEC_USER_KEYRING, b, size) + + if err != nil { + return nil, err + } + + if sizeRead = int(r1); sizeRead > size { + b = make([]byte, sizeRead) + size = sizeRead + sizeRead = size + 1 + } else { + kr.size = sizeRead + } + } + + keyIDs := getKeyIDsFromByte(b[:kr.size]) + for _, kid := range keyIDs { + keyArr = append(keyArr, &Key{id: kid}) + } + return keyArr, err +} + +func getKeyIDsFromByte(byteKeyIDs []byte) []keyID { + idSize := 4 + + // check the endianness + var nativeEndian binary.ByteOrder + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + switch buf { + case [2]byte{0xCD, 0xAB}: + nativeEndian = binary.LittleEndian + case [2]byte{0xAB, 0xCD}: + log.Println("big") + nativeEndian = binary.BigEndian + default: + panic("Could not determine native endianness.") + } + + var keyIDs []keyID + for idx := 0; idx+idSize <= len(byteKeyIDs); idx = idx + idSize { + tempID := byteKeyIDs[idx : idx+idSize] + keyIDs = append(keyIDs, keyID(nativeEndian.Uint32(tempID))) + } + return keyIDs +} diff --git a/pkg/keyctl/keyring_test.go b/pkg/keyctl/keyring_test.go index a610584deb..7aa24e82f9 100644 --- a/pkg/keyctl/keyring_test.go +++ b/pkg/keyctl/keyring_test.go @@ -4,6 +4,7 @@ package keyctl import ( "crypto/rand" + "strings" "testing" ) @@ -124,3 +125,73 @@ func TestUnlink(t *testing.T) { t.Fatal(err) } } + +func TestReadUserKeyring(t *testing.T) { + token := make([]byte, 20) + rand.Read(token) + + testname := "testread" + + userKeyring, err := UserKeyring() + if err != nil { + t.Fatal(err) + } + + sessionKeyring, err := SessionKeyring() + if err != nil { + t.Fatal(err) + } + + key, err := sessionKeyring.Add(testname, token) + if err != nil { + t.Fatal(err) + } + + expected := true + err = Link(userKeyring, key) + if err != nil { + t.Fatal(err) + } + keys, err := userKeyring.ReadUserKeyring() + if err != nil { + t.Fatal(err) + } + keyExists := false + for _, k := range keys { + if key.ID() == k.ID() { + keyExists = true + break + } + } + if keyExists != expected { + t.Errorf("ReadUserKeyring error: expected ID %d does not exist", key.ID()) + } +} + +func TestDescribe(t *testing.T) { + token := make([]byte, 20) + rand.Read(token) + + testname := "testdescribe" + + sessionKeyring, err := SessionKeyring() + if err != nil { + t.Fatal(err) + } + + key, err := sessionKeyring.Add(testname, token) + if err != nil { + t.Fatal(err) + } + + describe, err := Describe(key) + if err != nil { + t.Fatal(err) + } + + expected := true + getDescribe := strings.Contains(describe, testname) + if getDescribe != expected { + t.Errorf("Describe error: expect to get %s, but get %s", testname, describe) + } +}