diff --git a/storage/memory/static_clients_test.go b/storage/memory/static_clients_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e34b070c818aaa1e3a2a9888eee22622bcf4cc93
--- /dev/null
+++ b/storage/memory/static_clients_test.go
@@ -0,0 +1,48 @@
+package memory
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/coreos/poke/storage"
+)
+
+func TestStaticClients(t *testing.T) {
+	s := New()
+
+	c1 := storage.Client{ID: "foo", Secret: "foo_secret"}
+	c2 := storage.Client{ID: "bar", Secret: "bar_secret"}
+	s.CreateClient(c1)
+	s2 := storage.WithStaticClients(s, []storage.Client{c2})
+
+	tests := []struct {
+		id         string
+		s          storage.Storage
+		wantErr    bool
+		wantClient storage.Client
+	}{
+		{"foo", s, false, c1},
+		{"bar", s, true, storage.Client{}},
+		{"foo", s2, true, storage.Client{}},
+		{"bar", s2, false, c2},
+	}
+
+	for i, tc := range tests {
+		gotClient, err := tc.s.GetClient(tc.id)
+		if err != nil {
+			if !tc.wantErr {
+				t.Errorf("case %d: GetClient(%q) %v", i, tc.id, err)
+			}
+			continue
+		}
+
+		if tc.wantErr {
+			t.Errorf("case %d: GetClient(%q) expected error", i, tc.id)
+			continue
+		}
+
+		if !reflect.DeepEqual(tc.wantClient, gotClient) {
+			t.Errorf("case %d: expected=%#v got=%#v", i, tc.wantClient, gotClient)
+		}
+	}
+}
diff --git a/storage/static_clients.go b/storage/static_clients.go
new file mode 100644
index 0000000000000000000000000000000000000000..d793239341174c679b7d6f3e3075e4e8adcf58d5
--- /dev/null
+++ b/storage/static_clients.go
@@ -0,0 +1,55 @@
+package storage
+
+import "errors"
+
+// Tests for this code are in the "memory" package, since this package doesn't
+// define a concrete storage implementation.
+
+// staticClientsStorage is a storage that only allow read-only actions on clients.
+// All read actions return from the list of clients stored in memory, not the
+// underlying
+type staticClientsStorage struct {
+	Storage
+
+	// A read-only set of clients.
+	clients     []Client
+	clientsByID map[string]Client
+}
+
+// WithStaticClients returns a storage with a read-only set of clients. Write actions,
+// such as creating other clients, will fail.
+//
+// In the future the returned storage may allow creating and storing additional clients
+// in the underlying storage.
+func WithStaticClients(s Storage, staticClients []Client) Storage {
+	clientsByID := make(map[string]Client, len(staticClients))
+	for _, client := range staticClients {
+		clientsByID[client.ID] = client
+	}
+	return staticClientsStorage{s, staticClients, clientsByID}
+}
+
+func (s staticClientsStorage) GetClient(id string) (Client, error) {
+	if client, ok := s.clientsByID[id]; ok {
+		return client, nil
+	}
+	return Client{}, ErrNotFound
+}
+
+func (s staticClientsStorage) ListClients() ([]Client, error) {
+	clients := make([]Client, len(s.clients))
+	copy(clients, s.clients)
+	return clients, nil
+}
+
+func (s staticClientsStorage) CreateClient(c Client) error {
+	return errors.New("static clients: read-only cannot create client")
+}
+
+func (s staticClientsStorage) DeleteClient(id string) error {
+	return errors.New("static clients: read-only cannot delete client")
+}
+
+func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (Client, error)) error {
+	return errors.New("static clients: read-only cannot update client")
+}