diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go
index caa77c144c892eab59f1266f47ac4e199454926b..af7ff2b3fd1003d79d1d85ca0f3a3b6873fe13d3 100644
--- a/storage/conformance/conformance.go
+++ b/storage/conformance/conformance.go
@@ -285,6 +285,22 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) {
 	password.Username = "jane doe"
 	getAndCompare("jane@example.com", password)
 
+	var passwordList []storage.Password
+	passwordList = append(passwordList, password)
+
+	listAndCompare := func(want []storage.Password) {
+		passwords, err := s.ListPasswords()
+		if err != nil {
+			t.Errorf("list password: %v", err)
+			return
+		}
+		if diff := pretty.Compare(want, passwords); diff != "" {
+			t.Errorf("password list retrieved from storage did not match: %s", diff)
+		}
+	}
+
+	listAndCompare(passwordList)
+
 	if err := s.DeletePassword(password.Email); err != nil {
 		t.Fatalf("failed to delete password: %v", err)
 	}
@@ -292,6 +308,7 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) {
 	if _, err := s.GetPassword(password.Email); err != storage.ErrNotFound {
 		t.Errorf("after deleting password expected storage.ErrNotFound, got %v", err)
 	}
+
 }
 
 func testKeysCRUD(t *testing.T, s storage.Storage) {
diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go
index 08df0161c7710e982d6310975af6c8ffd2f0800c..5471265264d736a0cf84fbe238ac686fff81300b 100644
--- a/storage/kubernetes/storage.go
+++ b/storage/kubernetes/storage.go
@@ -260,6 +260,25 @@ func (cli *client) ListRefreshTokens() ([]storage.RefreshToken, error) {
 	return nil, errors.New("not implemented")
 }
 
+func (cli *client) ListPasswords() (passwords []storage.Password, err error) {
+	var passwordList PasswordList
+	if err = cli.list(resourcePassword, &passwordList); err != nil {
+		return passwords, fmt.Errorf("failed to list passwords: %v", err)
+	}
+
+	for _, password := range passwordList.Passwords {
+		p := storage.Password{
+			Email:    password.Email,
+			Hash:     password.Hash,
+			Username: password.Username,
+			UserID:   password.UserID,
+		}
+		passwords = append(passwords, p)
+	}
+
+	return
+}
+
 func (cli *client) DeleteAuthRequest(id string) error {
 	return cli.delete(resourceAuthRequest, id)
 }
diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go
index 900a8187b2be0023d4b236ff259545116b57c9e9..61c2c29589f7fc3e102eb11c63cc12c4cbc5bc0e 100644
--- a/storage/kubernetes/types.go
+++ b/storage/kubernetes/types.go
@@ -259,6 +259,13 @@ type Password struct {
 	UserID   string `json:"userID,omitempty"`
 }
 
+// PasswordList is a list of Passwords.
+type PasswordList struct {
+	k8sapi.TypeMeta `json:",inline"`
+	k8sapi.ListMeta `json:"metadata,omitempty"`
+	Passwords       []Password `json:"items"`
+}
+
 func (cli *client) fromStoragePassword(p storage.Password) Password {
 	email := strings.ToLower(p.Email)
 	return Password{
diff --git a/storage/memory/memory.go b/storage/memory/memory.go
index df88b4425876627ed2cbeddc4f5744ea6e329232..e8f2ce9a7da175239f74cbbe93bf2a20dd5c6506 100644
--- a/storage/memory/memory.go
+++ b/storage/memory/memory.go
@@ -192,6 +192,15 @@ func (s *memStorage) ListRefreshTokens() (tokens []storage.RefreshToken, err err
 	return
 }
 
+func (s *memStorage) ListPasswords() (passwords []storage.Password, err error) {
+	s.tx(func() {
+		for _, password := range s.passwords {
+			passwords = append(passwords, password)
+		}
+	})
+	return
+}
+
 func (s *memStorage) DeletePassword(email string) (err error) {
 	email = strings.ToLower(email)
 	s.tx(func() {
diff --git a/storage/sql/crud.go b/storage/sql/crud.go
index 1b444670c7d7c6657ca1097540da5acf28a47cb8..1bc36ccdc01c34a0185ecb0b7b9f2178b0a35796 100644
--- a/storage/sql/crud.go
+++ b/storage/sql/crud.go
@@ -532,12 +532,39 @@ func (c *conn) GetPassword(email string) (storage.Password, error) {
 }
 
 func getPassword(q querier, email string) (p storage.Password, err error) {
-	email = strings.ToLower(email)
-	err = q.QueryRow(`
+	return scanPassword(q.QueryRow(`
 		select
 			email, hash, username, user_id
 		from password where email = $1;
-	`, email).Scan(
+	`, strings.ToLower(email)))
+}
+
+func (c *conn) ListPasswords() ([]storage.Password, error) {
+	rows, err := c.Query(`
+		select
+			email, hash, username, user_id
+		from password;
+	`)
+	if err != nil {
+		return nil, err
+	}
+
+	var passwords []storage.Password
+	for rows.Next() {
+		p, err := scanPassword(rows)
+		if err != nil {
+			return nil, err
+		}
+		passwords = append(passwords, p)
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return passwords, nil
+}
+
+func scanPassword(s scanner) (p storage.Password, err error) {
+	err = s.Scan(
 		&p.Email, &p.Hash, &p.Username, &p.UserID,
 	)
 	if err != nil {
diff --git a/storage/static.go b/storage/static.go
index 8274c5f80482cd8ce722109798a2222b37b41063..4076a6133eaae6d8e136dfc584589f92851cf19a 100644
--- a/storage/static.go
+++ b/storage/static.go
@@ -60,6 +60,8 @@ func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (
 type staticPasswordsStorage struct {
 	Storage
 
+	// A read-only set of passwords.
+	passwords        []Password
 	passwordsByEmail map[string]Password
 }
 
@@ -71,7 +73,7 @@ func WithStaticPasswords(s Storage, staticPasswords []Password) Storage {
 		p.Email = strings.ToLower(p.Email)
 		passwordsByEmail[p.Email] = p
 	}
-	return staticPasswordsStorage{s, passwordsByEmail}
+	return staticPasswordsStorage{s, staticPasswords, passwordsByEmail}
 }
 
 func (s staticPasswordsStorage) GetPassword(email string) (Password, error) {
@@ -81,6 +83,12 @@ func (s staticPasswordsStorage) GetPassword(email string) (Password, error) {
 	return Password{}, ErrNotFound
 }
 
+func (s staticPasswordsStorage) ListPasswords() ([]Password, error) {
+	passwords := make([]Password, len(s.passwords))
+	copy(passwords, s.passwords)
+	return passwords, nil
+}
+
 func (s staticPasswordsStorage) CreatePassword(p Password) error {
 	return errors.New("static passwords: read-only cannot create password")
 }
diff --git a/storage/storage.go b/storage/storage.go
index 0d5b1f7ff68626ef703cc9f1ac65d070fd4c544b..cd480326b41ae9e7f231322af478fab6ea84a00a 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -70,6 +70,7 @@ type Storage interface {
 
 	ListClients() ([]Client, error)
 	ListRefreshTokens() ([]RefreshToken, error)
+	ListPasswords() ([]Password, error)
 
 	// Delete methods MUST be atomic.
 	DeleteAuthRequest(id string) error