{"record":{"id":"f7c19863754d41d8","repo":"kubernetes/kops","slug":"server-side-client-does-not-support-storekeyset","errorCode":null,"errorMessage":"server-side client does not support StoreKeyset","messagePattern":"server-side client does not support StoreKeyset","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/kops-controller/pkg/server/keystore.go","lineNumber":65,"sourceCode":"\tentry, ok := k.keys[name]\n\tif !ok {\n\t\treturn nil, nil, fmt.Errorf(\"unknown CA %q\", name)\n\t}\n\treturn entry.certificate, entry.key, nil\n}\n\n// FindKeyset finds a Keyset.  If the keyset is not found, it returns (nil, nil).\nfunc (k *keystore) FindKeyset(ctx context.Context, name string) (*fi.Keyset, error) {\n\tkeySet, ok := k.keySets[name]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\treturn keySet, nil\n}\n\n// StoreKeyset writes a Keyset to the store.\nfunc (k *keystore) StoreKeyset(ctx context.Context, name string, keyset *fi.Keyset) error {\n\treturn fmt.Errorf(\"server-side client does not support StoreKeyset\")\n}\n\n// MirrorTo will copy secrets to a vfs.Path, which is often easier for a machine to read\nfunc (k *keystore) MirrorTo(ctx context.Context, basedir vfs.Path) error {\n\treturn fmt.Errorf(\"server-side client does not support MirrorTo\")\n}\n\n// ListKeysets will return all the KeySets.\nfunc (k *keystore) ListKeysets() (map[string]*fi.Keyset, error) {\n\treturn nil, fmt.Errorf(\"server-side client does not support ListKeysets\")\n}\n\nfunc newKeystore(basePath string, cas []string) (*keystore, map[string]string, error) {\n\tkeystore := &keystore{\n\t\tkeys:    map[string]keystoreEntry{},\n\t\tkeySets: map[string]*fi.Keyset{},\n\t}\n\tfor _, name := range cas {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/cmd/kops-controller/pkg/server/keystore.go#L47-L83","documentation":"The server-side keystore in kops-controller is a read-only, in-memory implementation of pki.Keystore/fi.CAStore. StoreKeyset is part of the interface it must satisfy, but persisting new keysets is intentionally unsupported in the controller process, so the method unconditionally returns this stub error.","triggerScenarios":"Any code path that calls keystore.StoreKeyset on the server-side keystore — e.g. PKI issue/rotate logic that expects a writable store (like the vfs/cluster-based CAStore) is invoked inside kops-controller.","commonSituations":"Reusing kops-controller server code with a component that tries to write generated or rotated keysets back to the store; a code refactor routes keyset persistence through the server keystore instead of the cluster store.","solutions":["Do not call StoreKeyset on the server-side keystore; persist keysets through the cluster's regular keystore instead","If keyset writing is needed in the controller, use a store implementation backed by the cluster state (e.g. vfs-based CAStore)","Refactor caller to treat the server keystore as read-only and handle the not-supported error explicitly"],"exampleFix":"// before\nerr := serverKeystore.StoreKeyset(ctx, name, keyset)\n\n// after\nif err := serverKeystore.StoreKeyset(ctx, name, keyset); err != nil {\n\t// persist via cluster store instead\n\terr = clusterStore.Keystore().StoreKeyset(ctx, name, keyset)\n}","handlingStrategy":"validation","validationCode":"// Detect the read-only server keystore before attempting a write\ntype readOnlyKeystore interface{ StoreKeyset(ctx context.Context, name string, ks *fi.Keyset) error }\nif _, ok := store.(readOnlyKeystore); ok && isServerSideKeystore(store) {\n\treturn fmt.Errorf(\"refusing StoreKeyset on read-only server-side keystore\")\n}","typeGuard":"func isServerSideKeystore(store pki.Keystore) bool {\n\t_, ro := store.(interface{ MirrorTo(ctx context.Context, p vfs.Path) error })\n\treturn ro // server keystore is the only impl lacking write support markers\n}","tryCatchPattern":"if err := ks.StoreKeyset(ctx, name, keyset); err != nil {\n\tif strings.Contains(err.Error(), \"does not support StoreKeyset\") {\n\t\treturn persistViaClusterStore(ctx, name, keyset)\n\t}\n\treturn err\n}","preventionTips":["Treat the kops-controller server keystore as read-only by design","Persist keysets via the cluster's vfs/etcd-backed CAStore","Add an interface marker (e.g. ReadOnlyKeystore) so callers can detect write capability","Never route key-rotation write paths through the controller's in-memory store"],"tags":["keystore","read-only","unsupported-operation","kops-controller"],"backgroundTag":"unsupported-operation","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}