From a67d58c1e874ce3a061cb83817721933bcd4563e Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Mon, 21 Aug 2023 09:32:11 -0700
Subject: [PATCH 01/20] add plugin runtime API

---
 api/plugin_runtime_types.go               |  43 +++++++
 api/sys_plugins_runtimes.go               | 138 ++++++++++++++++++++
 api/sys_plugins_runtimes_test.go          |  92 ++++++++++++++
 sdk/helper/consts/plugin_runtime_types.go |  44 +++++++
 sdk/helper/pluginruntimeutil/runner.go    |  19 +++
 vault/core.go                             |   6 +
 vault/logical_system.go                   | 131 +++++++++++++++++++
 vault/logical_system_paths.go             | 120 ++++++++++++++++++
 vault/plugin_runtime_catalog.go           | 147 ++++++++++++++++++++++
 vault/plugin_runtime_catalog_test.go      |  84 +++++++++++++
 10 files changed, 824 insertions(+)
 create mode 100644 api/plugin_runtime_types.go
 create mode 100644 api/sys_plugins_runtimes.go
 create mode 100644 api/sys_plugins_runtimes_test.go
 create mode 100644 sdk/helper/consts/plugin_runtime_types.go
 create mode 100644 sdk/helper/pluginruntimeutil/runner.go
 create mode 100644 vault/plugin_runtime_catalog.go
 create mode 100644 vault/plugin_runtime_catalog_test.go

diff --git a/api/plugin_runtime_types.go b/api/plugin_runtime_types.go
new file mode 100644
index 000000000000..adb444b22c07
--- /dev/null
+++ b/api/plugin_runtime_types.go
@@ -0,0 +1,43 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package api
+
+// NOTE: this file was copied from
+// https://github.com/hashicorp/vault/blob/main/sdk/helper/consts/plugin_runtime_types.go
+// Any changes made should be made to both files at the same time.
+
+import "fmt"
+
+var PluginRuntimeTypes = []PluginRuntimeType{
+	PluginRuntimeTypeUnknown,
+	PluginRuntimeTypeContainer,
+}
+
+type PluginRuntimeType uint32
+
+// This is a list of PluginRuntimeTypes used by Vault.
+const (
+	PluginRuntimeTypeUnknown PluginRuntimeType = iota
+	PluginRuntimeTypeContainer
+)
+
+func (r PluginRuntimeType) String() string {
+	switch r {
+	case PluginRuntimeTypeContainer:
+		return "container"
+	case PluginRuntimeTypeUnknown:
+		fallthrough
+	default:
+		return "unsupported"
+	}
+}
+
+func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error) {
+	switch PluginRuntimeType {
+	case "container":
+		return PluginRuntimeTypeContainer, nil
+	default:
+		return PluginRuntimeTypeUnknown, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
+	}
+}
diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
new file mode 100644
index 000000000000..8e91bda31e96
--- /dev/null
+++ b/api/sys_plugins_runtimes.go
@@ -0,0 +1,138 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package api
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+)
+
+type PluginRuntimeDetails struct {
+	Type         string  `json:"type"`
+	Name         string  `json:"name"`
+	OCIRuntime   string  `json:"oci_runtime"`
+	ParentCGroup string  `json:"parent_cgroup"`
+	CPU          float32 `json:"cpu"`
+	Memory       uint64  `json:"memory"`
+}
+
+// GetPluginRuntimeInput is used as input to the GetPluginRuntime function.
+type GetPluginRuntimeInput struct {
+	Name string `json:"-"`
+
+	// Type of the plugin runtime. Required.
+	Type PluginRuntimeType `json:"type"`
+}
+
+// GetPluginRuntimeResponse is the response from the GetPluginRuntime call.
+type GetPluginRuntimeResponse struct {
+	Type         string  `json:"type"`
+	Name         string  `json:"name"`
+	OCIRuntime   string  `json:"oci_runtime"`
+	ParentCGroup string  `json:"parent_cgroup"`
+	CPU          float32 `json:"cpu"`
+	Memory       uint64  `json:"memory"`
+}
+
+// GetPluginRuntime wraps GetPluginRuntimeWithContext using context.Background.
+func (c *Sys) GetPluginRuntime(i *GetPluginRuntimeInput) (*GetPluginRuntimeResponse, error) {
+	return c.GetPluginRuntimeWithContext(context.Background(), i)
+}
+
+// GetPluginRuntimeWithContext retrieves information about the plugin.
+func (c *Sys) GetPluginRuntimeWithContext(ctx context.Context, i *GetPluginRuntimeInput) (*GetPluginRuntimeResponse, error) {
+	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
+	defer cancelFunc()
+
+	path := pluginRuntimeCatalogPathByType(i.Type, i.Name)
+	req := c.c.NewRequest(http.MethodGet, path)
+
+	resp, err := c.c.rawRequestWithContext(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	var result struct {
+		Data *GetPluginRuntimeResponse
+	}
+	err = resp.DecodeJSON(&result)
+	if err != nil {
+		return nil, err
+	}
+	return result.Data, err
+}
+
+// RegisterPluginRuntimeInput is used as input to the RegisterPluginRuntime function.
+type RegisterPluginRuntimeInput struct {
+	// Name is the name of the plugin. Required.
+	Name string `json:"-"`
+
+	// Type of the plugin. Required.
+	Type PluginRuntimeType `json:"type"`
+
+	OCIRuntime   string  `json:"oci_runtime,omitempty"`
+	ParentCGroup string  `json:"parent_cgroup,omitempty"`
+	CPU          float32 `json:"cpu,omitempty"`
+	Memory       uint64  `json:"memory,omitempty"`
+}
+
+// RegisterPluginRuntime wraps RegisterPluginWithContext using context.Background.
+func (c *Sys) RegisterPluginRuntime(i *RegisterPluginRuntimeInput) error {
+	return c.RegisterPluginRuntimeWithContext(context.Background(), i)
+}
+
+// RegisterPluginRuntimeWithContext registers the plugin with the given information.
+func (c *Sys) RegisterPluginRuntimeWithContext(ctx context.Context, i *RegisterPluginRuntimeInput) error {
+	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
+	defer cancelFunc()
+
+	path := pluginRuntimeCatalogPathByType(i.Type, i.Name)
+	req := c.c.NewRequest(http.MethodPut, path)
+
+	if err := req.SetJSONBody(i); err != nil {
+		return err
+	}
+
+	resp, err := c.c.rawRequestWithContext(ctx, req)
+	if err == nil {
+		defer resp.Body.Close()
+	}
+	return err
+}
+
+// DeregisterPluginRuntimeInput is used as input to the DeregisterPluginRuntime function.
+type DeregisterPluginRuntimeInput struct {
+	// Name is the name of the plugin runtime. Required.
+	Name string `json:"-"`
+
+	// Type of the plugin. Required.
+	Type PluginRuntimeType `json:"type"`
+}
+
+// DeregisterPluginRuntime wraps DeregisterPluginRuntimeWithContext using context.Background.
+func (c *Sys) DeregisterPluginRuntime(i *DeregisterPluginRuntimeInput) error {
+	return c.DeregisterPluginRuntimeWithContext(context.Background(), i)
+}
+
+// DeregisterPluginRuntimeWithContext removes the plugin with the given name from the plugin
+// catalog.
+func (c *Sys) DeregisterPluginRuntimeWithContext(ctx context.Context, i *DeregisterPluginRuntimeInput) error {
+	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
+	defer cancelFunc()
+
+	path := pluginRuntimeCatalogPathByType(i.Type, i.Name)
+	req := c.c.NewRequest(http.MethodDelete, path)
+	resp, err := c.c.rawRequestWithContext(ctx, req)
+	if err == nil {
+		defer resp.Body.Close()
+	}
+	return err
+}
+
+// pluginRuntimeCatalogPathByType is a helper to construct the proper API path by plugin type
+func pluginRuntimeCatalogPathByType(runtimeType PluginRuntimeType, name string) string {
+	return fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", runtimeType, name)
+}
diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
new file mode 100644
index 000000000000..4fddb046e041
--- /dev/null
+++ b/api/sys_plugins_runtimes_test.go
@@ -0,0 +1,92 @@
+package api
+
+import (
+	"context"
+	"net/http"
+	"net/http/httptest"
+	"reflect"
+	"testing"
+)
+
+func TestRegisterPluginRuntime(t *testing.T) {
+	mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerRegister))
+	defer mockVaultServer.Close()
+
+	cfg := DefaultConfig()
+	cfg.Address = mockVaultServer.URL
+	client, err := NewClient(cfg)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = client.Sys().RegisterPluginRuntimeWithContext(context.Background(), &RegisterPluginRuntimeInput{
+		Name:         "gvisor",
+		Type:         PluginRuntimeTypeContainer,
+		OCIRuntime:   "runsc",
+		ParentCGroup: "/cpulimit-cgroup/",
+		CPU:          1,
+		Memory:       10000,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestGetPluginRuntime(t *testing.T) {
+	for name, tc := range map[string]struct {
+		body     string
+		expected GetPluginRuntimeResponse
+	}{
+		"gvisor": {
+			body: getPluginRuntimeResponse,
+			expected: GetPluginRuntimeResponse{
+				Name:         "gvisor",
+				Type:         PluginRuntimeTypeContainer.String(),
+				OCIRuntime:   "runsc",
+				ParentCGroup: "/cpulimit-cgroup/",
+				CPU:          1,
+				Memory:       10000,
+			},
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerInfo(tc.body)))
+			defer mockVaultServer.Close()
+
+			cfg := DefaultConfig()
+			cfg.Address = mockVaultServer.URL
+			client, err := NewClient(cfg)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			input := GetPluginRuntimeInput{
+				Name: "gvisor",
+				Type: PluginRuntimeTypeContainer,
+			}
+
+			info, err := client.Sys().GetPluginRuntimeWithContext(context.Background(), &input)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if !reflect.DeepEqual(tc.expected, *info) {
+				t.Errorf("expected: %#v\ngot: %#v", tc.expected, info)
+			}
+		})
+	}
+}
+
+const getPluginRuntimeResponse = `{
+    "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
+    "data": {
+        "name": "gvisor",
+        "type": "container",
+        "oci_runtime": "runsc",
+        "parent_cgroup": "/cpulimit-cgroup/",
+        "cpu": 1,
+        "memory": 10000
+    },
+    "warnings": null,
+    "auth": null
+}`
diff --git a/sdk/helper/consts/plugin_runtime_types.go b/sdk/helper/consts/plugin_runtime_types.go
new file mode 100644
index 000000000000..8b81f4843c53
--- /dev/null
+++ b/sdk/helper/consts/plugin_runtime_types.go
@@ -0,0 +1,44 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package consts
+
+// TODO thy
+// NOTE: this file has been copied to
+// https://github.com/hashicorp/vault/blob/main/api/plugin_runtime_types.go
+// Any changes made should be made to both files at the same time.
+
+import "fmt"
+
+var PluginRuntimeTypes = []PluginRuntimeType{
+	PluginRuntimeTypeUnknown,
+	PluginRuntimeTypeContainer,
+}
+
+type PluginRuntimeType uint32
+
+// This is a list of PluginRuntimeTypes used by Vault.
+const (
+	PluginRuntimeTypeUnknown PluginRuntimeType = iota
+	PluginRuntimeTypeContainer
+)
+
+func (r PluginRuntimeType) String() string {
+	switch r {
+	case PluginRuntimeTypeContainer:
+		return "container"
+	case PluginRuntimeTypeUnknown:
+		fallthrough
+	default:
+		return "unsupported"
+	}
+}
+
+func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error) {
+	switch PluginRuntimeType {
+	case "container":
+		return PluginRuntimeTypeContainer, nil
+	default:
+		return PluginRuntimeTypeUnknown, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
+	}
+}
diff --git a/sdk/helper/pluginruntimeutil/runner.go b/sdk/helper/pluginruntimeutil/runner.go
new file mode 100644
index 000000000000..0a660abefb3d
--- /dev/null
+++ b/sdk/helper/pluginruntimeutil/runner.go
@@ -0,0 +1,19 @@
+package pluginruntimeutil
+
+import "github.com/hashicorp/vault/sdk/helper/consts"
+
+const (
+	DefaultOCIRuntime = "runsc"
+	DefaultCPU        = 0.5
+	DefaultMemory     = 10000
+)
+
+// PluginRuntimeRunner defines the metadata needed to run a plugin runtime
+type PluginRuntimeRunner struct {
+	Name         string                   `json:"name" structs:"name"`
+	Type         consts.PluginRuntimeType `json:"type" structs:"type"`
+	OCIRuntime   string                   `json:"oci_runtime" structs:"oci_runtime"`
+	ParentCGroup string                   `json:"parent_cgroup" structs:"parent_cgroup"`
+	CPU          float32                  `json:"cpu" structs:"cpu"`
+	Memory       uint64                   `json:"memory" structs:"memory"`
+}
diff --git a/vault/core.go b/vault/core.go
index dc76def7f883..3b35788d59d7 100644
--- a/vault/core.go
+++ b/vault/core.go
@@ -548,6 +548,9 @@ type Core struct {
 	// pluginCatalog is used to manage plugin configurations
 	pluginCatalog *PluginCatalog
 
+	// pluginRuntimeCatalog is used to manage plugin runtime configurations
+	pluginRuntimeCatalog *PluginRuntimeCatalog
+
 	// The userFailedLoginInfo map has user failed login information.
 	// It has user information (alias-name and mount accessor) as a key
 	// and login counter, last failed login time as value
@@ -2288,6 +2291,9 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
 			return err
 		}
 	}
+	if err := c.setupPluginRuntimeCatalog(ctx); err != nil {
+		return err
+	}
 	if err := c.setupPluginCatalog(ctx); err != nil {
 		return err
 	}
diff --git a/vault/logical_system.go b/vault/logical_system.go
index f81ed292bc6c..388b1c8bcfb5 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -43,6 +43,7 @@ import (
 	"github.com/hashicorp/vault/sdk/framework"
 	"github.com/hashicorp/vault/sdk/helper/consts"
 	"github.com/hashicorp/vault/sdk/helper/jsonutil"
+	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
 	"github.com/hashicorp/vault/sdk/helper/pluginutil"
 	"github.com/hashicorp/vault/sdk/helper/roottoken"
 	"github.com/hashicorp/vault/sdk/helper/wrapping"
@@ -113,6 +114,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
 				"config/auditing/*",
 				"config/ui/headers/*",
 				"plugins/catalog/*",
+				"plugins/runtimes/catalog/*",
 				"revoke-prefix/*",
 				"revoke-force/*",
 				"leases/revoke-prefix/*",
@@ -186,6 +188,9 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogListPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogCRUDPath())
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsReloadPath())
+	b.Backend.Paths = append(b.Backend.Paths, b.pluginsRuntimesCatalogCRUDPath())
+	// TODO thy
+	// b.Backend.Paths = append(b.Backend.Paths, b.pluginsRuntimesCatalogListPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.auditPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.mountPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.authPaths()...)
@@ -724,6 +729,104 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic
 	return &r, nil
 }
 
+func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) {
+	runtimeName := d.Get("name").(string)
+	if runtimeName == "" {
+		return logical.ErrorResponse("missing plugin runtime name"), nil
+	}
+
+	// TODO validate name
+
+	runtimeTypeStr := d.Get("type").(string)
+	if runtimeTypeStr == "" {
+		return logical.ErrorResponse("missing plugin runtime type"), nil
+	}
+
+	runtimeType, err := consts.ParsePluginRuntimeType(runtimeTypeStr)
+	if err != nil {
+		return logical.ErrorResponse(err.Error()), nil
+	}
+
+	switch runtimeType {
+	case consts.PluginRuntimeTypeContainer:
+		ociRuntime := d.Get("oci_runtime").(string)
+		if ociRuntime == "" {
+			ociRuntime = pluginruntimeutil.DefaultOCIRuntime
+		}
+		parentCGroup := d.Get("parent_cgroup").(string)
+		cpu := d.Get("cpu").(float32)
+		if cpu == 0 {
+			cpu = pluginruntimeutil.DefaultCPU
+		}
+		memory := d.Get("memory").(uint64)
+		if memory == 0 {
+			memory = pluginruntimeutil.DefaultMemory
+		}
+		err := b.Core.pluginRuntimeCatalog.Set(ctx, runtimeName, runtimeType, ociRuntime, parentCGroup, cpu, memory)
+		if err != nil {
+			return logical.ErrorResponse(err.Error()), nil
+		}
+	}
+	return nil, nil
+}
+
+func (b *SystemBackend) handlePluginRuntimeCatalogDelete(ctx context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) {
+	runtimeName := d.Get("name").(string)
+	if runtimeName == "" {
+		return logical.ErrorResponse("missing plugin runtime name"), nil
+	}
+
+	// TODO validate name
+
+	runtimeTypeStr := d.Get("type").(string)
+	if runtimeTypeStr == "" {
+		return logical.ErrorResponse("missing plugin runtime type"), nil
+	}
+
+	runtimeType, err := consts.ParsePluginRuntimeType(runtimeTypeStr)
+	if err != nil {
+		return logical.ErrorResponse(err.Error()), nil
+	}
+
+	err = b.Core.pluginRuntimeCatalog.Delete(ctx, runtimeName, runtimeType)
+	if err != nil {
+		return logical.ErrorResponse(err.Error()), nil
+	}
+	return nil, nil
+}
+
+func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) {
+	runtimeName := d.Get("name").(string)
+	if runtimeName == "" {
+		return logical.ErrorResponse("missing plugin runtime name"), nil
+	}
+
+	// TODO validate name
+
+	runtimeTypeStr := d.Get("type").(string)
+	if runtimeTypeStr == "" {
+		return logical.ErrorResponse("missing plugin runtime type"), nil
+	}
+
+	runtimeType, err := consts.ParsePluginRuntimeType(runtimeTypeStr)
+	if err != nil {
+		return logical.ErrorResponse(err.Error()), nil
+	}
+
+	runner, err := b.Core.pluginRuntimeCatalog.Get(ctx, runtimeName, runtimeType)
+	if err != nil {
+		return logical.ErrorResponse(err.Error()), nil
+	}
+	return &logical.Response{Data: map[string]interface{}{
+		"name":          runner.Name,
+		"type":          runner.Type,
+		"oci_runtime":   runner.OCIRuntime,
+		"parent_cgroup": runner.ParentCGroup,
+		"cpu":           runner.CPU,
+		"memory":        runner.Memory,
+	}}, nil
+}
+
 // handleAuditedHeaderUpdate creates or overwrites a header entry
 func (b *SystemBackend) handleAuditedHeaderUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
 	header := d.Get("header").(string)
@@ -5896,6 +5999,34 @@ Each entry is of the form "key=value".`,
 		"The semantic version of the plugin to use.",
 		"",
 	},
+	"plugin-runtime-catalog": {
+		"", // TODO thy def.
+		"", // TODO thy def.
+	},
+	"plugin-runtime-catalog_name": {
+		"", // TODO thy def.
+		"",
+	},
+	"plugin-runtime-catalog_type": {
+		"", // TODO thy def.
+		"",
+	},
+	"plugin-runtime-catalog_oci-runtime": {
+		"", // TODO thy def.
+		"",
+	},
+	"plugin-runtime-catalog_parent-cgroup": {
+		"", // TODO thy def.
+		"",
+	},
+	"plugin-runtime-catalog_cpu": {
+		"", // TODO thy def.
+		"",
+	},
+	"plugin-runtime-catalog_memory": {
+		"", // TODO thy def.
+		"",
+	},
 	"leases": {
 		`View or list lease metadata.`,
 		`
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index c71e5e85983f..2b8c887916ca 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -2093,6 +2093,126 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path {
 	}
 }
 
+func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
+	return &framework.Path{
+		Pattern: "plugins/runtimes/catalog(/(?P<type>container))?/(?P<name>.+)",
+
+		DisplayAttrs: &framework.DisplayAttributes{
+			OperationPrefix: "plugins-runtimes-catalog",
+		},
+
+		Fields: map[string]*framework.FieldSchema{
+			"name": {
+				Type:        framework.TypeString,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_name"][0]),
+			},
+			"type": {
+				Type:        framework.TypeString,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_type"][0]),
+			},
+			"oci_runtime": {
+				Type:        framework.TypeString,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
+			},
+			"parent_cgroup": {
+				Type:        framework.TypeString,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_parent-cgroup"][0]),
+			},
+			"cpu": {
+				Type:        framework.TypeFloat,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
+			},
+			"memory": {
+				Type:        framework.TypeInt64,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory"][0]),
+			},
+		},
+
+		Operations: map[logical.Operation]framework.OperationHandler{
+			logical.UpdateOperation: &framework.PathOperation{
+				Callback: b.handlePluginRuntimeCatalogUpdate,
+				DisplayAttrs: &framework.DisplayAttributes{
+					OperationVerb:   "register",
+					OperationSuffix: "plugin-runtime|plugin-runtime-with-type|plugin-runtime-with-type-and-name", // TODO
+				},
+				Responses: map[int][]framework.Response{
+					http.StatusOK: {{
+						Description: "OK",
+					}},
+				},
+				Summary: "Register a new plugin runtime, or updates an existing one with the supplied name.",
+			},
+			logical.DeleteOperation: &framework.PathOperation{
+				Callback: b.handlePluginRuntimeCatalogDelete,
+				DisplayAttrs: &framework.DisplayAttributes{
+					OperationVerb:   "remove",
+					OperationSuffix: "plugin-runtime|plugin-runtime-with-type|plugin-runtime-with-type-and-name", // TODO
+				},
+				Responses: map[int][]framework.Response{
+					http.StatusOK: {{
+						Description: "OK",
+						Fields:      map[string]*framework.FieldSchema{},
+					}},
+				},
+				Summary: "Remove the plugin runtime with the given name.",
+			},
+			logical.ReadOperation: &framework.PathOperation{
+				Callback: b.handlePluginRuntimeCatalogRead,
+				DisplayAttrs: &framework.DisplayAttributes{
+					OperationVerb:   "read",
+					OperationSuffix: "plugin-runtime-configuration|plugin-runtime-configuration-with-type|plugin-runtime-configuration-with-type-and-name",
+				},
+				Responses: map[int][]framework.Response{
+					http.StatusOK: {{
+						Description: "OK",
+						Fields: map[string]*framework.FieldSchema{
+							"name": {
+								Type:        framework.TypeString,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_name"][0]),
+								Required:    true,
+							},
+							"type": {
+								Type:        framework.TypeString,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_type"][0]),
+								Required:    true,
+							},
+							"oci_runtime": {
+								Type:        framework.TypeString,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
+								Required:    true,
+							},
+							"parent_cgroup": {
+								Type:        framework.TypeString,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_parent-cgroup"][0]),
+								Required:    true,
+							},
+							"cpu": {
+								Type:        framework.TypeFloat,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
+								Required:    true,
+							},
+							"memory": {
+								Type:        framework.TypeInt64,
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory"][0]),
+								Required:    true,
+							},
+						},
+					}},
+				},
+				Summary: "Return the configuration data for the plugin runtime with the given name.",
+			},
+		},
+
+		HelpSynopsis:    strings.TrimSpace(sysHelp["plugin-runtime-catalog"][0]),
+		HelpDescription: strings.TrimSpace(sysHelp["plugin-runtime-catalog"][1]),
+	}
+}
+
+// TODO thy
+//func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
+//	return []*framework.Path{}
+//}
+
 func (b *SystemBackend) toolsPaths() []*framework.Path {
 	return []*framework.Path{
 		{
diff --git a/vault/plugin_runtime_catalog.go b/vault/plugin_runtime_catalog.go
new file mode 100644
index 000000000000..d11ca2afa2e1
--- /dev/null
+++ b/vault/plugin_runtime_catalog.go
@@ -0,0 +1,147 @@
+package vault
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"path"
+	"sync"
+
+	log "github.com/hashicorp/go-hclog"
+
+	"github.com/hashicorp/vault/sdk/helper/consts"
+	"github.com/hashicorp/vault/sdk/helper/jsonutil"
+	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
+	"github.com/hashicorp/vault/sdk/logical"
+)
+
+var (
+	pluginRuntimeCatalogPath           = "core/plugin-runtime-catalog/"
+	ErrPluginRuntimeNotFound           = errors.New("plugin runtime not found")
+	ErrPluginRuntimeBadType            = errors.New("unable to determine plugin runtime type")
+	ErrPluginRuntimeBadContainerConfig = errors.New("bad container config")
+)
+
+// PluginRuntimeCatalog keeps a record of plugin runtimes. Plugin runtimes need
+// to be registered to the catalog before they can be used in backends when registering plugins with runtimes
+type PluginRuntimeCatalog struct {
+	catalogView *BarrierView
+	logger      log.Logger
+
+	lock sync.RWMutex
+}
+
+func (c *Core) setupPluginRuntimeCatalog(ctx context.Context) error {
+	c.pluginRuntimeCatalog = &PluginRuntimeCatalog{
+		catalogView: NewBarrierView(c.barrier, pluginRuntimeCatalogPath),
+		logger:      c.logger,
+	}
+
+	if c.logger.IsInfo() {
+		c.logger.Info("successfully setup plugin runtime catalog")
+	}
+
+	return nil
+}
+
+// Get retrieves a plugin runtime with the specified name from the catalog
+// It returns a PluginRuntimeRunner or an error if no plugin runtime was found.
+func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.PluginRuntimeType) (*pluginruntimeutil.PluginRuntimeRunner, error) {
+	storageKey := path.Join(prt.String(), name)
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	entry, err := c.catalogView.Get(ctx, storageKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve plugin runtime %q %q: %w", prt.String(), name, err)
+	}
+	if entry == nil {
+		return nil, fmt.Errorf("failed to retrieve plugin %q %q: %w", prt.String(), name, err)
+	}
+	runner := new(pluginruntimeutil.PluginRuntimeRunner)
+	if err := jsonutil.DecodeJSON(entry.Value, runner); err != nil {
+		return nil, fmt.Errorf("failed to decode plugin runtime entry: %w", err)
+	}
+	if runner.Type != prt {
+		return nil, nil
+	}
+	return runner, nil
+}
+
+// Set registers a new plugin with the catalog, or updates an existing plugin runtime
+func (c *PluginRuntimeCatalog) Set(ctx context.Context, name string, prt consts.PluginRuntimeType, ociRuntime string, parentCGroup string, cpu float32, memory uint64) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	entry := &pluginruntimeutil.PluginRuntimeRunner{
+		Name:         name,
+		Type:         prt,
+		OCIRuntime:   ociRuntime,
+		ParentCGroup: parentCGroup,
+		CPU:          cpu,
+		Memory:       memory,
+	}
+
+	// TODO what happens to the running existing container with(out) running plugins. Need to shut it down before setting it?
+	buf, err := json.Marshal(entry)
+	if err != nil {
+		return fmt.Errorf("failed to encode plugin entry: %w", err)
+	}
+
+	storageKey := path.Join(prt.String(), name)
+	logicalEntry := logical.StorageEntry{
+		Key:   storageKey,
+		Value: buf,
+	}
+
+	if err := c.catalogView.Put(ctx, &logicalEntry); err != nil {
+		return fmt.Errorf("failed to persist plugin runtime entry: %w", err)
+	}
+	return err
+}
+
+// Delete is used to remove an external plugin from the catalog. Builtin plugins
+// can not be deleted.
+func (c *PluginRuntimeCatalog) Delete(ctx context.Context, name string, prt consts.PluginRuntimeType) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	storageKey := path.Join(prt.String(), name)
+	out, err := c.catalogView.Get(ctx, storageKey)
+	if err != nil || out == nil {
+		return ErrPluginRuntimeNotFound
+	}
+
+	// TODO what happens to the running existing container with(out) running plugins. Need to shut it down before setting it?
+	return c.catalogView.Delete(ctx, storageKey)
+}
+
+func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntimeType) ([]string, error) {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+
+	var retList []string
+	keys, err := logical.CollectKeys(ctx, c.catalogView)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, key := range keys {
+		entry, err := c.catalogView.Get(ctx, key)
+		if err != nil || entry == nil {
+			continue
+		}
+
+		runner := new(pluginruntimeutil.PluginRuntimeRunner)
+		if err := jsonutil.DecodeJSON(entry.Value, runner); err != nil {
+			return nil, fmt.Errorf("failed to decode plugin runtime entry: %w", err)
+		}
+
+		if runner.Type != prt {
+			continue
+		}
+
+		retList = append(retList, runner.Name)
+	}
+	return retList, nil
+}
diff --git a/vault/plugin_runtime_catalog_test.go b/vault/plugin_runtime_catalog_test.go
new file mode 100644
index 000000000000..0fada7832d3c
--- /dev/null
+++ b/vault/plugin_runtime_catalog_test.go
@@ -0,0 +1,84 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: BUSL-1.1
+
+package vault
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	"github.com/hashicorp/vault/sdk/helper/consts"
+	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
+)
+
+func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
+	core, _, _ := TestCoreUnsealed(t)
+	ctx := context.Background()
+
+	expected := &pluginruntimeutil.PluginRuntimeRunner{
+		Name:         "gvisor",
+		Type:         consts.PluginRuntimeTypeContainer,
+		OCIRuntime:   "runsc",
+		ParentCGroup: "/cpulimit-cgroup/",
+		CPU:          1,
+		Memory:       10000,
+	}
+
+	// Set new plugin runtime
+	err := core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.ParentCGroup, expected.CPU, expected.Memory)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Get plugin runtime
+	runner, err := core.pluginRuntimeCatalog.Get(ctx, expected.Name, expected.Type)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	if !reflect.DeepEqual(expected, runner) {
+		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", runner, expected)
+	}
+
+	// Set existing plugin runtime
+	expected.ParentCGroup = "memorylimit-cgroup"
+	expected.CPU = 0.5
+	expected.Memory = 5000
+	err = core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.ParentCGroup, expected.CPU, expected.Memory)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Get plugin runtime again
+	runner, err = core.pluginRuntimeCatalog.Get(ctx, expected.Name, expected.Type)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if !reflect.DeepEqual(expected, runner) {
+		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", runner, expected)
+	}
+
+	names, err := core.pluginRuntimeCatalog.List(ctx, expected.Type)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	if len(names) != 1 {
+		t.Fatalf("expected plugin runtime catalog to have 1 container runtime but got %d", len(names))
+	}
+
+	// Delete plugin runtime
+	err = core.pluginRuntimeCatalog.Delete(ctx, expected.Name, expected.Type)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Assert the plugin runtime catalog is empty
+	names, err = core.pluginRuntimeCatalog.List(ctx, expected.Type)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	if len(names) != 0 {
+		t.Fatalf("expected plugin runtime catalog to have 0 container runtimes but got %d", len(names))
+	}
+}

From 2cfb2a685042b863449fcccb6b781d998af7b6e2 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Tue, 22 Aug 2023 18:32:06 -0700
Subject: [PATCH 02/20] Update sdk/helper/pluginruntimeutil/runner.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
---
 sdk/helper/pluginruntimeutil/runner.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sdk/helper/pluginruntimeutil/runner.go b/sdk/helper/pluginruntimeutil/runner.go
index 0a660abefb3d..40bbe6963552 100644
--- a/sdk/helper/pluginruntimeutil/runner.go
+++ b/sdk/helper/pluginruntimeutil/runner.go
@@ -9,7 +9,7 @@ const (
 )
 
 // PluginRuntimeRunner defines the metadata needed to run a plugin runtime
-type PluginRuntimeRunner struct {
+type PluginRuntimeConfig struct {
 	Name         string                   `json:"name" structs:"name"`
 	Type         consts.PluginRuntimeType `json:"type" structs:"type"`
 	OCIRuntime   string                   `json:"oci_runtime" structs:"oci_runtime"`

From 9c360b679efd818f6e2693a4099b1395631e1b48 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Tue, 22 Aug 2023 18:32:06 -0700
Subject: [PATCH 03/20] Update sdk/helper/pluginruntimeutil/runner.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
---
 api/plugin_runtime_types.go               |   8 +-
 api/sys_plugins_runtimes.go               |  90 ++++++++++++++++-
 api/sys_plugins_runtimes_test.go          | 115 +++++++++++++++++++++-
 sdk/helper/consts/plugin_runtime_types.go |   9 +-
 sdk/helper/pluginruntimeutil/runner.go    |  10 +-
 vault/logical_system.go                   | 108 ++++++++++++++------
 vault/logical_system_paths.go             |  87 ++++++++++++++--
 vault/plugin_runtime_catalog.go           |  12 +--
 vault/plugin_runtime_catalog_test.go      |  12 +--
 9 files changed, 378 insertions(+), 73 deletions(-)

diff --git a/api/plugin_runtime_types.go b/api/plugin_runtime_types.go
index adb444b22c07..d3acd0d002c4 100644
--- a/api/plugin_runtime_types.go
+++ b/api/plugin_runtime_types.go
@@ -10,7 +10,7 @@ package api
 import "fmt"
 
 var PluginRuntimeTypes = []PluginRuntimeType{
-	PluginRuntimeTypeUnknown,
+	PluginRuntimeTypeUnsupported,
 	PluginRuntimeTypeContainer,
 }
 
@@ -18,7 +18,7 @@ type PluginRuntimeType uint32
 
 // This is a list of PluginRuntimeTypes used by Vault.
 const (
-	PluginRuntimeTypeUnknown PluginRuntimeType = iota
+	PluginRuntimeTypeUnsupported PluginRuntimeType = iota
 	PluginRuntimeTypeContainer
 )
 
@@ -26,8 +26,6 @@ func (r PluginRuntimeType) String() string {
 	switch r {
 	case PluginRuntimeTypeContainer:
 		return "container"
-	case PluginRuntimeTypeUnknown:
-		fallthrough
 	default:
 		return "unsupported"
 	}
@@ -38,6 +36,6 @@ func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error)
 	case "container":
 		return PluginRuntimeTypeContainer, nil
 	default:
-		return PluginRuntimeTypeUnknown, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
+		return PluginRuntimeTypeUnsupported, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
 	}
 }
diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 8e91bda31e96..c68facd22e75 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -5,19 +5,34 @@ package api
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
+
+	"github.com/mitchellh/mapstructure"
 )
 
 type PluginRuntimeDetails struct {
 	Type         string  `json:"type"`
 	Name         string  `json:"name"`
 	OCIRuntime   string  `json:"oci_runtime"`
-	ParentCGroup string  `json:"parent_cgroup"`
+	CgroupParent string  `json:"cgroup_parent"`
 	CPU          float32 `json:"cpu"`
 	Memory       uint64  `json:"memory"`
 }
 
+// ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
+type ListPluginRuntimesInput struct {
+	// Type of the plugin. Required.
+	Type PluginRuntimeType `json:"type"`
+}
+
+// ListPluginRuntimesResponse is the response from the ListPluginRuntimes call.
+type ListPluginRuntimesResponse struct {
+	// RuntimesByType is the list of plugin runtimes by type.
+	RuntimesByType map[PluginRuntimeType][]string `json:"types"`
+}
+
 // GetPluginRuntimeInput is used as input to the GetPluginRuntime function.
 type GetPluginRuntimeInput struct {
 	Name string `json:"-"`
@@ -31,7 +46,7 @@ type GetPluginRuntimeResponse struct {
 	Type         string  `json:"type"`
 	Name         string  `json:"name"`
 	OCIRuntime   string  `json:"oci_runtime"`
-	ParentCGroup string  `json:"parent_cgroup"`
+	CgroupParent string  `json:"cgroup_parent"`
 	CPU          float32 `json:"cpu"`
 	Memory       uint64  `json:"memory"`
 }
@@ -74,7 +89,7 @@ type RegisterPluginRuntimeInput struct {
 	Type PluginRuntimeType `json:"type"`
 
 	OCIRuntime   string  `json:"oci_runtime,omitempty"`
-	ParentCGroup string  `json:"parent_cgroup,omitempty"`
+	CgroupParent string  `json:"cgroup_parent,omitempty"`
 	CPU          float32 `json:"cpu,omitempty"`
 	Memory       uint64  `json:"memory,omitempty"`
 }
@@ -132,6 +147,75 @@ func (c *Sys) DeregisterPluginRuntimeWithContext(ctx context.Context, i *Deregis
 	return err
 }
 
+// ListPluginRuntimes wraps ListPluginRuntimesWithContext using context.Background.
+func (c *Sys) ListPluginRuntimes(i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
+	return c.ListPluginRuntimesWithContext(context.Background(), i)
+}
+
+// ListPluginRuntimesWithContext lists all plugin runtimes in the catalog and returns their names as a
+// list of strings.
+func (c *Sys) ListPluginRuntimesWithContext(ctx context.Context, i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
+	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
+	defer cancelFunc()
+
+	if i != nil && i.Type == PluginRuntimeTypeUnsupported {
+		return nil, fmt.Errorf("%q is not a supported runtime type", i.Type.String())
+	}
+
+	resp, err := c.c.rawRequestWithContext(ctx, c.c.NewRequest(http.MethodGet, "/v1/sys/plugins/runtimes/catalog"))
+	if err != nil && resp == nil {
+		return nil, err
+	}
+	if resp == nil {
+		return nil, nil
+	}
+	defer resp.Body.Close()
+
+	secret, err := ParseSecret(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	if secret == nil || secret.Data == nil {
+		return nil, errors.New("data from server response is empty")
+	}
+
+	result := &ListPluginRuntimesResponse{
+		RuntimesByType: make(map[PluginRuntimeType][]string),
+	}
+
+	// return all runtimes in the catalog
+	if i == nil {
+		for _, runtimeType := range PluginRuntimeTypes {
+			runtimesRaw, ok := secret.Data[runtimeType.String()]
+			if !ok {
+				continue
+			}
+
+			var tmp []string
+			if err = mapstructure.Decode(runtimesRaw, &tmp); err != nil {
+				return nil, err
+			}
+			result.RuntimesByType[runtimeType] = tmp
+		}
+		return result, nil
+	}
+
+	switch i.Type {
+	default:
+		runtimesRaw, ok := secret.Data[i.Type.String()]
+		if !ok {
+			return nil, fmt.Errorf("no %s entry in returned data", i.Type.String())
+		}
+
+		var tmp []string
+		if err := mapstructure.Decode(runtimesRaw, &tmp); err != nil {
+			return nil, err
+		}
+		result.RuntimesByType[i.Type] = tmp
+	}
+	return result, nil
+}
+
 // pluginRuntimeCatalogPathByType is a helper to construct the proper API path by plugin type
 func pluginRuntimeCatalogPathByType(runtimeType PluginRuntimeType, name string) string {
 	return fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", runtimeType, name)
diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
index 4fddb046e041..d3d06af0da09 100644
--- a/api/sys_plugins_runtimes_test.go
+++ b/api/sys_plugins_runtimes_test.go
@@ -23,7 +23,7 @@ func TestRegisterPluginRuntime(t *testing.T) {
 		Name:         "gvisor",
 		Type:         PluginRuntimeTypeContainer,
 		OCIRuntime:   "runsc",
-		ParentCGroup: "/cpulimit-cgroup/",
+		CgroupParent: "/cpulimit/",
 		CPU:          1,
 		Memory:       10000,
 	})
@@ -43,7 +43,7 @@ func TestGetPluginRuntime(t *testing.T) {
 				Name:         "gvisor",
 				Type:         PluginRuntimeTypeContainer.String(),
 				OCIRuntime:   "runsc",
-				ParentCGroup: "/cpulimit-cgroup/",
+				CgroupParent: "/cpulimit/",
 				CPU:          1,
 				Memory:       10000,
 			},
@@ -77,16 +77,125 @@ func TestGetPluginRuntime(t *testing.T) {
 	}
 }
 
+func TestListPluginRuntimeTyped(t *testing.T) {
+	for _, tc := range []struct {
+		runtimeType      PluginRuntimeType
+		body             string
+		expectedResponse *ListPluginRuntimesResponse
+		expectedErrNil   bool
+	}{
+		{
+			runtimeType: PluginRuntimeTypeContainer,
+			body:        listPluginRuntimeTypedResponse,
+			expectedResponse: &ListPluginRuntimesResponse{
+				RuntimesByType: map[PluginRuntimeType][]string{
+					PluginRuntimeTypeContainer: {"gvisor"},
+				},
+			},
+			expectedErrNil: true,
+		},
+		{
+			runtimeType:      PluginRuntimeTypeUnsupported,
+			body:             listPluginRuntimeTypedResponse,
+			expectedResponse: nil,
+			expectedErrNil:   false,
+		},
+	} {
+		t.Run(tc.runtimeType.String(), func(t *testing.T) {
+			mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerInfo(tc.body)))
+			defer mockVaultServer.Close()
+
+			cfg := DefaultConfig()
+			cfg.Address = mockVaultServer.URL
+			client, err := NewClient(cfg)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			input := ListPluginRuntimesInput{
+				Type: tc.runtimeType,
+			}
+
+			list, err := client.Sys().ListPluginRuntimesWithContext(context.Background(), &input)
+			if tc.expectedErrNil && err != nil {
+				t.Fatal(err)
+			}
+
+			if (tc.expectedErrNil && !reflect.DeepEqual(tc.expectedResponse, list)) || (!tc.expectedErrNil && list != nil) {
+				t.Errorf("expected: %#v\ngot: %#v", tc.expectedResponse, list)
+			}
+		})
+	}
+}
+
+func TestListPluginRuntimeUntyped(t *testing.T) {
+	for _, tc := range []struct {
+		body             string
+		expectedResponse *ListPluginRuntimesResponse
+		expectedErrNil   bool
+	}{
+		{
+			body: listPluginRuntimeUntypedResponse,
+			expectedResponse: &ListPluginRuntimesResponse{
+				RuntimesByType: map[PluginRuntimeType][]string{
+					PluginRuntimeTypeContainer: {"gvisor", "foo", "bar"},
+				},
+			},
+			expectedErrNil: true,
+		},
+	} {
+		t.Run("", func(t *testing.T) {
+			mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerInfo(tc.body)))
+			defer mockVaultServer.Close()
+
+			cfg := DefaultConfig()
+			cfg.Address = mockVaultServer.URL
+			client, err := NewClient(cfg)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			info, err := client.Sys().ListPluginRuntimesWithContext(context.Background(), nil)
+			if tc.expectedErrNil && err != nil {
+				t.Fatal(err)
+			}
+
+			if !reflect.DeepEqual(tc.expectedResponse, info) {
+				t.Errorf("expected: %#v\ngot: %#v", tc.expectedResponse, info)
+			}
+		})
+	}
+}
+
 const getPluginRuntimeResponse = `{
     "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
     "data": {
         "name": "gvisor",
         "type": "container",
         "oci_runtime": "runsc",
-        "parent_cgroup": "/cpulimit-cgroup/",
+        "cgroup_parent": "/cpulimit/",
         "cpu": 1,
         "memory": 10000
     },
     "warnings": null,
     "auth": null
 }`
+
+const listPluginRuntimeTypedResponse = `{
+    "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
+    "data": {
+        "container": ["gvisor"]
+    },
+    "warnings": null,
+    "auth": null
+}
+`
+
+const listPluginRuntimeUntypedResponse = `{
+    "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
+    "data": {
+        "container": ["gvisor", "foo", "bar"]
+    },
+    "warnings": null,
+    "auth": null
+}`
diff --git a/sdk/helper/consts/plugin_runtime_types.go b/sdk/helper/consts/plugin_runtime_types.go
index 8b81f4843c53..63b8127ec2c9 100644
--- a/sdk/helper/consts/plugin_runtime_types.go
+++ b/sdk/helper/consts/plugin_runtime_types.go
@@ -3,7 +3,6 @@
 
 package consts
 
-// TODO thy
 // NOTE: this file has been copied to
 // https://github.com/hashicorp/vault/blob/main/api/plugin_runtime_types.go
 // Any changes made should be made to both files at the same time.
@@ -11,7 +10,7 @@ package consts
 import "fmt"
 
 var PluginRuntimeTypes = []PluginRuntimeType{
-	PluginRuntimeTypeUnknown,
+	PluginRuntimeTypeUnsupported,
 	PluginRuntimeTypeContainer,
 }
 
@@ -19,7 +18,7 @@ type PluginRuntimeType uint32
 
 // This is a list of PluginRuntimeTypes used by Vault.
 const (
-	PluginRuntimeTypeUnknown PluginRuntimeType = iota
+	PluginRuntimeTypeUnsupported PluginRuntimeType = iota
 	PluginRuntimeTypeContainer
 )
 
@@ -27,8 +26,6 @@ func (r PluginRuntimeType) String() string {
 	switch r {
 	case PluginRuntimeTypeContainer:
 		return "container"
-	case PluginRuntimeTypeUnknown:
-		fallthrough
 	default:
 		return "unsupported"
 	}
@@ -39,6 +36,6 @@ func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error)
 	case "container":
 		return PluginRuntimeTypeContainer, nil
 	default:
-		return PluginRuntimeTypeUnknown, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
+		return PluginRuntimeTypeUnsupported, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
 	}
 }
diff --git a/sdk/helper/pluginruntimeutil/runner.go b/sdk/helper/pluginruntimeutil/runner.go
index 0a660abefb3d..1deaa6d195ba 100644
--- a/sdk/helper/pluginruntimeutil/runner.go
+++ b/sdk/helper/pluginruntimeutil/runner.go
@@ -4,16 +4,16 @@ import "github.com/hashicorp/vault/sdk/helper/consts"
 
 const (
 	DefaultOCIRuntime = "runsc"
-	DefaultCPU        = 0.5
-	DefaultMemory     = 10000
+	DefaultCPU        = 0.1
+	DefaultMemory     = 100000000
 )
 
-// PluginRuntimeRunner defines the metadata needed to run a plugin runtime
-type PluginRuntimeRunner struct {
+// PluginRuntimeConfig defines the metadata needed to run a plugin runtime
+type PluginRuntimeConfig struct {
 	Name         string                   `json:"name" structs:"name"`
 	Type         consts.PluginRuntimeType `json:"type" structs:"type"`
 	OCIRuntime   string                   `json:"oci_runtime" structs:"oci_runtime"`
-	ParentCGroup string                   `json:"parent_cgroup" structs:"parent_cgroup"`
+	CgroupParent string                   `json:"cgroup_parent" structs:"cgroup_parent"`
 	CPU          float32                  `json:"cpu" structs:"cpu"`
 	Memory       uint64                   `json:"memory" structs:"memory"`
 }
diff --git a/vault/logical_system.go b/vault/logical_system.go
index 388b1c8bcfb5..27d192c082d9 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -189,8 +189,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogCRUDPath())
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsReloadPath())
 	b.Backend.Paths = append(b.Backend.Paths, b.pluginsRuntimesCatalogCRUDPath())
-	// TODO thy
-	// b.Backend.Paths = append(b.Backend.Paths, b.pluginsRuntimesCatalogListPaths()...)
+	b.Backend.Paths = append(b.Backend.Paths, b.pluginsRuntimesCatalogListPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.auditPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.mountPaths()...)
 	b.Backend.Paths = append(b.Backend.Paths, b.authPaths()...)
@@ -735,8 +734,6 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 		return logical.ErrorResponse("missing plugin runtime name"), nil
 	}
 
-	// TODO validate name
-
 	runtimeTypeStr := d.Get("type").(string)
 	if runtimeTypeStr == "" {
 		return logical.ErrorResponse("missing plugin runtime type"), nil
@@ -753,19 +750,21 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 		if ociRuntime == "" {
 			ociRuntime = pluginruntimeutil.DefaultOCIRuntime
 		}
-		parentCGroup := d.Get("parent_cgroup").(string)
-		cpu := d.Get("cpu").(float32)
+		cgroupParent := d.Get("cgroup_parent").(string)
+		cpu := float32(d.Get("cpu").(float64))
 		if cpu == 0 {
 			cpu = pluginruntimeutil.DefaultCPU
 		}
-		memory := d.Get("memory").(uint64)
+		memory := uint64(d.Get("memory").(int64))
 		if memory == 0 {
 			memory = pluginruntimeutil.DefaultMemory
 		}
-		err := b.Core.pluginRuntimeCatalog.Set(ctx, runtimeName, runtimeType, ociRuntime, parentCGroup, cpu, memory)
+		err := b.Core.pluginRuntimeCatalog.Set(ctx, runtimeName, runtimeType, ociRuntime, cgroupParent, cpu, memory)
 		if err != nil {
 			return logical.ErrorResponse(err.Error()), nil
 		}
+	default:
+		logical.ErrorResponse(fmt.Sprintf("%s is not a supported plugin runtime type", runtimeTypeStr))
 	}
 	return nil, nil
 }
@@ -776,8 +775,6 @@ func (b *SystemBackend) handlePluginRuntimeCatalogDelete(ctx context.Context, _
 		return logical.ErrorResponse("missing plugin runtime name"), nil
 	}
 
-	// TODO validate name
-
 	runtimeTypeStr := d.Get("type").(string)
 	if runtimeTypeStr == "" {
 		return logical.ErrorResponse("missing plugin runtime type"), nil
@@ -801,8 +798,6 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 		return logical.ErrorResponse("missing plugin runtime name"), nil
 	}
 
-	// TODO validate name
-
 	runtimeTypeStr := d.Get("type").(string)
 	if runtimeTypeStr == "" {
 		return logical.ErrorResponse("missing plugin runtime type"), nil
@@ -813,20 +808,60 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 		return logical.ErrorResponse(err.Error()), nil
 	}
 
-	runner, err := b.Core.pluginRuntimeCatalog.Get(ctx, runtimeName, runtimeType)
+	conf, err := b.Core.pluginRuntimeCatalog.Get(ctx, runtimeName, runtimeType)
 	if err != nil {
 		return logical.ErrorResponse(err.Error()), nil
 	}
 	return &logical.Response{Data: map[string]interface{}{
-		"name":          runner.Name,
-		"type":          runner.Type,
-		"oci_runtime":   runner.OCIRuntime,
-		"parent_cgroup": runner.ParentCGroup,
-		"cpu":           runner.CPU,
-		"memory":        runner.Memory,
+		"name":          conf.Name,
+		"type":          conf.Type.String(),
+		"oci_runtime":   conf.OCIRuntime,
+		"cgroup_parent": conf.CgroupParent,
+		"cpu":           conf.CPU,
+		"memory":        conf.Memory,
 	}}, nil
 }
 
+func (b *SystemBackend) handlePluginRuntimeCatalogTypedList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
+	runtimeType, err := consts.ParsePluginRuntimeType(d.Get("type").(string))
+	if err != nil {
+		return nil, err
+	}
+
+	runtimes, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
+	if err != nil {
+		return nil, err
+	}
+	sort.Strings(runtimes)
+	return logical.ListResponse(runtimes), nil
+}
+
+func (b *SystemBackend) handlePluginRuntimeCatalogUntypedList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
+	data := make(map[string]interface{})
+	for _, runtimeType := range consts.PluginRuntimeTypes {
+		if runtimeType == consts.PluginRuntimeTypeUnsupported {
+			continue
+		}
+		runtimes, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
+		if err != nil {
+			return nil, err
+		}
+		if len(runtimes) > 0 {
+			sort.Strings(runtimes)
+			data[runtimeType.String()] = runtimes
+		}
+	}
+
+	resp := &logical.Response{
+		Data: map[string]interface{}{},
+	}
+	if len(data) > 0 {
+		resp.Data["types"] = data
+	}
+
+	return resp, nil
+}
+
 // handleAuditedHeaderUpdate creates or overwrites a header entry
 func (b *SystemBackend) handleAuditedHeaderUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
 	header := d.Get("header").(string)
@@ -6000,31 +6035,48 @@ Each entry is of the form "key=value".`,
 		"",
 	},
 	"plugin-runtime-catalog": {
-		"", // TODO thy def.
-		"", // TODO thy def.
+		"Configures plugin runtimes",
+		`
+This path responds to the following HTTP methods.
+		LIST /
+			Returns a list of names of configured plugin runtimes.
+
+		GET /<type>/<name>
+			Retrieve the metadata for the named plugin runtime.
+
+		PUT /<type>/<name>
+			Add or update plugin runtime.
+
+		DELETE /<type>/<name>
+			Delete the plugin runtime with the given name.
+		`,
+	},
+	"plugin-runtime-catalog-list-all": {
+		"List all plugin runtimes in the catalog as a map of type to names.",
+		"",
 	},
 	"plugin-runtime-catalog_name": {
-		"", // TODO thy def.
+		"The name of the plugin runtime",
 		"",
 	},
 	"plugin-runtime-catalog_type": {
-		"", // TODO thy def.
+		"The type of the plugin runtime",
 		"",
 	},
 	"plugin-runtime-catalog_oci-runtime": {
-		"", // TODO thy def.
+		"The OCI-compatible runtime (default \"runsc\")",
 		"",
 	},
-	"plugin-runtime-catalog_parent-cgroup": {
-		"", // TODO thy def.
+	"plugin-runtime-catalog_cgroup-parent": {
+		"Optional parent cgroup for the container",
 		"",
 	},
 	"plugin-runtime-catalog_cpu": {
-		"", // TODO thy def.
+		"The limit of runtime CPU (default 0.1)",
 		"",
 	},
 	"plugin-runtime-catalog_memory": {
-		"", // TODO thy def.
+		"The limit of runtime memory in bytes (default 100000000)",
 		"",
 	},
 	"leases": {
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index 2b8c887916ca..f88bfbb5810d 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -2095,7 +2095,7 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path {
 
 func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 	return &framework.Path{
-		Pattern: "plugins/runtimes/catalog(/(?P<type>container))?/(?P<name>.+)",
+		Pattern: "plugins/runtimes/catalog/(?P<type>container)/" + framework.GenericNameRegex("name"),
 
 		DisplayAttrs: &framework.DisplayAttributes{
 			OperationPrefix: "plugins-runtimes-catalog",
@@ -2114,9 +2114,9 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 				Type:        framework.TypeString,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
 			},
-			"parent_cgroup": {
+			"cgroup_parent": {
 				Type:        framework.TypeString,
-				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_parent-cgroup"][0]),
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
 			},
 			"cpu": {
 				Type:        framework.TypeFloat,
@@ -2151,7 +2151,6 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 				Responses: map[int][]framework.Response{
 					http.StatusOK: {{
 						Description: "OK",
-						Fields:      map[string]*framework.FieldSchema{},
 					}},
 				},
 				Summary: "Remove the plugin runtime with the given name.",
@@ -2181,9 +2180,9 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
 								Required:    true,
 							},
-							"parent_cgroup": {
+							"cgroup_parent": {
 								Type:        framework.TypeString,
-								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_parent-cgroup"][0]),
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
 								Required:    true,
 							},
 							"cpu": {
@@ -2208,10 +2207,78 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 	}
 }
 
-// TODO thy
-//func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
-//	return []*framework.Path{}
-//}
+func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
+	return []*framework.Path{
+		{
+			Pattern: "plugins/runtimes/catalog/(?P<type>container)/?$",
+
+			DisplayAttrs: &framework.DisplayAttributes{
+				OperationPrefix: "plugins-runtimes-catalog",
+				OperationVerb:   "list",
+				OperationSuffix: "plugins-runtimes-with-type",
+			},
+
+			Fields: map[string]*framework.FieldSchema{
+				"type": {
+					Type:        framework.TypeString,
+					Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_type"][0]),
+				},
+			},
+
+			Operations: map[logical.Operation]framework.OperationHandler{
+				logical.ListOperation: &framework.PathOperation{
+					Callback: b.handlePluginRuntimeCatalogTypedList,
+					Responses: map[int][]framework.Response{
+						http.StatusOK: {{
+							Description: "OK",
+							Fields: map[string]*framework.FieldSchema{
+								"keys": {
+									Type:        framework.TypeStringSlice,
+									Description: "List of plugin runtime names in the catalog",
+									Required:    true,
+								},
+							},
+						}},
+					},
+					Summary: "List the plugin runtimes of a given type in the catalog.",
+				},
+			},
+
+			HelpSynopsis:    strings.TrimSpace(sysHelp["plugin-runtime-catalog"][0]),
+			HelpDescription: strings.TrimSpace(sysHelp["plugin-runtime-catalog"][1]),
+		},
+		{
+			Pattern: "plugins/runtimes/catalog/?$",
+
+			DisplayAttrs: &framework.DisplayAttributes{
+				OperationPrefix: "plugins-runtimes-catalog",
+				OperationVerb:   "list",
+				OperationSuffix: "plugins-runtimes",
+			},
+
+			Operations: map[logical.Operation]framework.OperationHandler{
+				logical.ReadOperation: &framework.PathOperation{
+					Callback: b.handlePluginRuntimeCatalogUntypedList,
+					Responses: map[int][]framework.Response{
+						http.StatusOK: {{
+							Description: "OK",
+							Fields: map[string]*framework.FieldSchema{
+								"types": {
+									Type:        framework.TypeMap,
+									Description: "Map of plugin runtime types to their plugin runtime names in the catalog",
+									Required:    true,
+								},
+							},
+						}},
+					},
+				},
+			},
+
+			HelpSynopsis:    strings.TrimSpace(sysHelp["plugin-runtime-catalog-list-all"][0]),
+			HelpDescription: strings.TrimSpace(sysHelp["plugin-runtime-catalog-list-all"][1]),
+		},
+	}
+}
 
 func (b *SystemBackend) toolsPaths() []*framework.Path {
 	return []*framework.Path{
diff --git a/vault/plugin_runtime_catalog.go b/vault/plugin_runtime_catalog.go
index d11ca2afa2e1..008e276cd5c3 100644
--- a/vault/plugin_runtime_catalog.go
+++ b/vault/plugin_runtime_catalog.go
@@ -46,8 +46,8 @@ func (c *Core) setupPluginRuntimeCatalog(ctx context.Context) error {
 }
 
 // Get retrieves a plugin runtime with the specified name from the catalog
-// It returns a PluginRuntimeRunner or an error if no plugin runtime was found.
-func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.PluginRuntimeType) (*pluginruntimeutil.PluginRuntimeRunner, error) {
+// It returns a PluginRuntimeConfig or an error if no plugin runtime was found.
+func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.PluginRuntimeType) (*pluginruntimeutil.PluginRuntimeConfig, error) {
 	storageKey := path.Join(prt.String(), name)
 	c.lock.RLock()
 	defer c.lock.RUnlock()
@@ -58,7 +58,7 @@ func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.
 	if entry == nil {
 		return nil, fmt.Errorf("failed to retrieve plugin %q %q: %w", prt.String(), name, err)
 	}
-	runner := new(pluginruntimeutil.PluginRuntimeRunner)
+	runner := new(pluginruntimeutil.PluginRuntimeConfig)
 	if err := jsonutil.DecodeJSON(entry.Value, runner); err != nil {
 		return nil, fmt.Errorf("failed to decode plugin runtime entry: %w", err)
 	}
@@ -73,11 +73,11 @@ func (c *PluginRuntimeCatalog) Set(ctx context.Context, name string, prt consts.
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
-	entry := &pluginruntimeutil.PluginRuntimeRunner{
+	entry := &pluginruntimeutil.PluginRuntimeConfig{
 		Name:         name,
 		Type:         prt,
 		OCIRuntime:   ociRuntime,
-		ParentCGroup: parentCGroup,
+		CgroupParent: parentCGroup,
 		CPU:          cpu,
 		Memory:       memory,
 	}
@@ -132,7 +132,7 @@ func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntim
 			continue
 		}
 
-		runner := new(pluginruntimeutil.PluginRuntimeRunner)
+		runner := new(pluginruntimeutil.PluginRuntimeConfig)
 		if err := jsonutil.DecodeJSON(entry.Value, runner); err != nil {
 			return nil, fmt.Errorf("failed to decode plugin runtime entry: %w", err)
 		}
diff --git a/vault/plugin_runtime_catalog_test.go b/vault/plugin_runtime_catalog_test.go
index 0fada7832d3c..ce6558a2077b 100644
--- a/vault/plugin_runtime_catalog_test.go
+++ b/vault/plugin_runtime_catalog_test.go
@@ -8,7 +8,6 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/hashicorp/vault/sdk/helper/consts"
 	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
 )
 
@@ -16,17 +15,16 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 	core, _, _ := TestCoreUnsealed(t)
 	ctx := context.Background()
 
-	expected := &pluginruntimeutil.PluginRuntimeRunner{
+	expected := &pluginruntimeutil.PluginRuntimeConfig{
 		Name:         "gvisor",
-		Type:         consts.PluginRuntimeTypeContainer,
 		OCIRuntime:   "runsc",
-		ParentCGroup: "/cpulimit-cgroup/",
+		CgroupParent: "/cpulimit/",
 		CPU:          1,
 		Memory:       10000,
 	}
 
 	// Set new plugin runtime
-	err := core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.ParentCGroup, expected.CPU, expected.Memory)
+	err := core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.CgroupParent, expected.CPU, expected.Memory)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}
@@ -41,10 +39,10 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 	}
 
 	// Set existing plugin runtime
-	expected.ParentCGroup = "memorylimit-cgroup"
+	expected.CgroupParent = "memorylimit-cgroup"
 	expected.CPU = 0.5
 	expected.Memory = 5000
-	err = core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.ParentCGroup, expected.CPU, expected.Memory)
+	err = core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.CgroupParent, expected.CPU, expected.Memory)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}

From bb3225479ca8e07d2b2be706e5a6e97e58b64535 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Tue, 29 Aug 2023 08:15:52 -0700
Subject: [PATCH 04/20] change runner.go to config.go

---
 sdk/helper/pluginruntimeutil/{runner.go => config.go} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename sdk/helper/pluginruntimeutil/{runner.go => config.go} (100%)

diff --git a/sdk/helper/pluginruntimeutil/runner.go b/sdk/helper/pluginruntimeutil/config.go
similarity index 100%
rename from sdk/helper/pluginruntimeutil/runner.go
rename to sdk/helper/pluginruntimeutil/config.go

From 5107640e759aaa772e6a8b6ebee44b1ff069b8ec Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Tue, 29 Aug 2023 08:16:45 -0700
Subject: [PATCH 05/20] wip plugin runtime CRUD test

---
 vault/logical_system_test.go | 136 +++++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)

diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index 7d5ce33a4714..b05b20ee02d4 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5884,3 +5884,139 @@ func TestSystemBackend_ReadExperiments(t *testing.T) {
 		})
 	}
 }
+
+func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
+	b := testSystemBackend(t)
+
+	// Register the plugin runtime
+	req := logical.TestRequest(t, logical.UpdateOperation, "plugins/runtimes/catalog/container/foo")
+	req.Data = map[string]interface{}{
+		"oci_runtime":   "some-oci-runtime",
+		"cgroup_parent": "/cpulimit/",
+		"cpu":           1,
+		"memory":        10000,
+	}
+
+	resp, err := b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v %#v", err, resp)
+	}
+	if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
+		t.Fatalf("bad: %#v", resp)
+	}
+
+	// validate the response structure for plugin container runtime named foo
+	schema.ValidateResponse(
+		t,
+		schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
+		resp,
+		true,
+	)
+
+	// Read the plugin runtime
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container/foo")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// validate the response structure for plugin container runtime named foo
+	schema.ValidateResponse(
+		t,
+		schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
+		resp,
+		true,
+	)
+
+	exp := map[string]interface{}{
+		"name":          "foo",
+		"type":          "container",
+		"oci_runtime":   "some-oci-runtime",
+		"cgroup_parent": "/cpulimit/",
+		"cpu":           1,
+		"memory":        10000,
+	}
+	if !reflect.DeepEqual(resp.Data, exp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	}
+
+	// List the plugin runtimes of container type
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	exp = map[string]interface{}{
+		"keys": []string{"foo"},
+	}
+	if !reflect.DeepEqual(resp.Data, exp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	}
+
+	// List the plugin runtimes (untyped or all)
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	exp = map[string]interface{}{
+		"type": map[string][]string{"container": {"foo"}},
+	}
+	if !reflect.DeepEqual(resp.Data, exp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	}
+
+	// Delete the plugin runtime
+	req = logical.TestRequest(t, logical.DeleteOperation, "plugins/runtimes/catalog/container/foo")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	if resp != nil {
+		t.Fatalf("bad: %#v", resp)
+	}
+
+	// validate the response structure for plugin container runtime named foo
+	schema.ValidateResponse(
+		t,
+		schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
+		resp,
+		true,
+	)
+
+	// Read the plugin runtime (deleted)
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container/foo")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	if resp != nil {
+		t.Fatalf("bad: %#v", resp)
+	}
+
+	// List the plugin runtimes (deleted)
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	exp = map[string]interface{}{}
+	if !reflect.DeepEqual(resp.Data, exp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	}
+
+	// List the plugin runtimes (untyped or all)
+	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog")
+	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	exp = map[string]interface{}{}
+	if !reflect.DeepEqual(resp.Data, exp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	}
+}

From d2b890385f74c0c15e11b97f7aa3fb5f9916ab15 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Tue, 29 Aug 2023 17:41:00 -0700
Subject: [PATCH 06/20] fix plugin runtime CRUD test failure

---
 sdk/helper/pluginruntimeutil/config.go |  2 +-
 vault/logical_system.go                | 16 +++++++++++++---
 vault/logical_system_paths.go          |  6 +++++-
 vault/logical_system_test.go           | 18 +++++++++---------
 vault/plugin_runtime_catalog.go        |  2 +-
 5 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/sdk/helper/pluginruntimeutil/config.go b/sdk/helper/pluginruntimeutil/config.go
index 1deaa6d195ba..16f97c9ead11 100644
--- a/sdk/helper/pluginruntimeutil/config.go
+++ b/sdk/helper/pluginruntimeutil/config.go
@@ -15,5 +15,5 @@ type PluginRuntimeConfig struct {
 	OCIRuntime   string                   `json:"oci_runtime" structs:"oci_runtime"`
 	CgroupParent string                   `json:"cgroup_parent" structs:"cgroup_parent"`
 	CPU          float32                  `json:"cpu" structs:"cpu"`
-	Memory       uint64                   `json:"memory" structs:"memory"`
+	Memory       int64                    `json:"memory" structs:"memory"`
 }
diff --git a/vault/logical_system.go b/vault/logical_system.go
index 27d192c082d9..e4936af185c7 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -752,10 +752,16 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 		}
 		cgroupParent := d.Get("cgroup_parent").(string)
 		cpu := float32(d.Get("cpu").(float64))
+		if cpu < 0 {
+			return logical.ErrorResponse("runtime cpu in bytes must be greater than 0"), nil
+		}
 		if cpu == 0 {
 			cpu = pluginruntimeutil.DefaultCPU
 		}
-		memory := uint64(d.Get("memory").(int64))
+		memory := d.Get("memory").(int64)
+		if memory < 0 {
+			return logical.ErrorResponse("runtime memory in bytes must be greater than 0"), nil
+		}
 		if memory == 0 {
 			memory = pluginruntimeutil.DefaultMemory
 		}
@@ -787,7 +793,7 @@ func (b *SystemBackend) handlePluginRuntimeCatalogDelete(ctx context.Context, _
 
 	err = b.Core.pluginRuntimeCatalog.Delete(ctx, runtimeName, runtimeType)
 	if err != nil {
-		return logical.ErrorResponse(err.Error()), nil
+		return nil, err
 	}
 	return nil, nil
 }
@@ -810,8 +816,12 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 
 	conf, err := b.Core.pluginRuntimeCatalog.Get(ctx, runtimeName, runtimeType)
 	if err != nil {
-		return logical.ErrorResponse(err.Error()), nil
+		return nil, err
+	}
+	if conf == nil {
+		return nil, nil
 	}
+
 	return &logical.Response{Data: map[string]interface{}{
 		"name":          conf.Name,
 		"type":          conf.Type.String(),
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index f88bfbb5810d..df906ef3dfbf 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/hashicorp/vault/sdk/framework"
+	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
 	"github.com/hashicorp/vault/sdk/logical"
 )
 
@@ -2113,6 +2114,7 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 			"oci_runtime": {
 				Type:        framework.TypeString,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
+				Default:     pluginruntimeutil.DefaultOCIRuntime,
 			},
 			"cgroup_parent": {
 				Type:        framework.TypeString,
@@ -2121,10 +2123,12 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 			"cpu": {
 				Type:        framework.TypeFloat,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
+				Default:     pluginruntimeutil.DefaultCPU,
 			},
 			"memory": {
 				Type:        framework.TypeInt64,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory"][0]),
+				Default:     pluginruntimeutil.DefaultMemory,
 			},
 		},
 
@@ -2257,7 +2261,7 @@ func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
 			},
 
 			Operations: map[logical.Operation]framework.OperationHandler{
-				logical.ReadOperation: &framework.PathOperation{
+				logical.ListOperation: &framework.PathOperation{
 					Callback: b.handlePluginRuntimeCatalogUntypedList,
 					Responses: map[int][]framework.Response{
 						http.StatusOK: {{
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index b05b20ee02d4..a0bf1f385646 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5933,15 +5933,15 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		"type":          "container",
 		"oci_runtime":   "some-oci-runtime",
 		"cgroup_parent": "/cpulimit/",
-		"cpu":           1,
-		"memory":        10000,
+		"cpu":           float32(1),
+		"memory":        int64(10000),
 	}
 	if !reflect.DeepEqual(resp.Data, exp) {
 		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
 	}
 
 	// List the plugin runtimes of container type
-	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container")
+	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog/container")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
 	if err != nil {
 		t.Fatalf("err: %v", err)
@@ -5955,14 +5955,14 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	}
 
 	// List the plugin runtimes (untyped or all)
-	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog")
+	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}
 
 	exp = map[string]interface{}{
-		"type": map[string][]string{"container": {"foo"}},
+		"types": map[string]interface{}{"container": []string{"foo"}},
 	}
 	if !reflect.DeepEqual(resp.Data, exp) {
 		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
@@ -5989,15 +5989,15 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	// Read the plugin runtime (deleted)
 	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container/foo")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-	if err != nil {
-		t.Fatalf("err: %v", err)
+	if err == nil {
+		t.Fatal("expected a read error after the runtime was deleted")
 	}
 	if resp != nil {
 		t.Fatalf("bad: %#v", resp)
 	}
 
 	// List the plugin runtimes (deleted)
-	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog/container")
+	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog/container")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
 	if err != nil {
 		t.Fatalf("err: %v", err)
@@ -6009,7 +6009,7 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	}
 
 	// List the plugin runtimes (untyped or all)
-	req = logical.TestRequest(t, logical.ReadOperation, "plugins/runtimes/catalog")
+	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
 	if err != nil {
 		t.Fatalf("err: %v", err)
diff --git a/vault/plugin_runtime_catalog.go b/vault/plugin_runtime_catalog.go
index 008e276cd5c3..28720810f27f 100644
--- a/vault/plugin_runtime_catalog.go
+++ b/vault/plugin_runtime_catalog.go
@@ -69,7 +69,7 @@ func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.
 }
 
 // Set registers a new plugin with the catalog, or updates an existing plugin runtime
-func (c *PluginRuntimeCatalog) Set(ctx context.Context, name string, prt consts.PluginRuntimeType, ociRuntime string, parentCGroup string, cpu float32, memory uint64) error {
+func (c *PluginRuntimeCatalog) Set(ctx context.Context, name string, prt consts.PluginRuntimeType, ociRuntime string, parentCGroup string, cpu float32, memory int64) error {
 	c.lock.Lock()
 	defer c.lock.Unlock()
 

From 8352ecd9d08e469fb51fc690d77984b912e5e089 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 10:20:54 -0700
Subject: [PATCH 07/20] add copyright headers

---
 api/sys_plugins_runtimes_test.go       | 3 +++
 sdk/helper/pluginruntimeutil/config.go | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
index d3d06af0da09..312f5e670c4c 100644
--- a/api/sys_plugins_runtimes_test.go
+++ b/api/sys_plugins_runtimes_test.go
@@ -1,3 +1,6 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
 package api
 
 import (
diff --git a/sdk/helper/pluginruntimeutil/config.go b/sdk/helper/pluginruntimeutil/config.go
index 16f97c9ead11..f492429f999e 100644
--- a/sdk/helper/pluginruntimeutil/config.go
+++ b/sdk/helper/pluginruntimeutil/config.go
@@ -1,3 +1,6 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
 package pluginruntimeutil
 
 import "github.com/hashicorp/vault/sdk/helper/consts"

From 49a26e04b0d429713375439ad4c7292acbe53867 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 10:57:19 -0700
Subject: [PATCH 08/20] change cpu to int64 type and refactor
 PluginRuntimeCatalog.Set()

---
 api/sys_plugins_runtimes.go            | 32 +++++++++++++-------------
 sdk/helper/pluginruntimeutil/config.go |  4 ++--
 vault/logical_system.go                | 15 ++++++++----
 vault/logical_system_paths.go          |  4 ++--
 vault/logical_system_test.go           |  2 +-
 vault/plugin_runtime_catalog.go        | 17 ++++----------
 vault/plugin_runtime_catalog_test.go   |  6 ++---
 7 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index c68facd22e75..5d66cff31683 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -13,12 +13,12 @@ import (
 )
 
 type PluginRuntimeDetails struct {
-	Type         string  `json:"type"`
-	Name         string  `json:"name"`
-	OCIRuntime   string  `json:"oci_runtime"`
-	CgroupParent string  `json:"cgroup_parent"`
-	CPU          float32 `json:"cpu"`
-	Memory       uint64  `json:"memory"`
+	Type         string `json:"type"`
+	Name         string `json:"name"`
+	OCIRuntime   string `json:"oci_runtime"`
+	CgroupParent string `json:"cgroup_parent"`
+	CPU          int64  `json:"cpu"`
+	Memory       int64  `json:"memory"`
 }
 
 // ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
@@ -43,12 +43,12 @@ type GetPluginRuntimeInput struct {
 
 // GetPluginRuntimeResponse is the response from the GetPluginRuntime call.
 type GetPluginRuntimeResponse struct {
-	Type         string  `json:"type"`
-	Name         string  `json:"name"`
-	OCIRuntime   string  `json:"oci_runtime"`
-	CgroupParent string  `json:"cgroup_parent"`
-	CPU          float32 `json:"cpu"`
-	Memory       uint64  `json:"memory"`
+	Type         string `json:"type"`
+	Name         string `json:"name"`
+	OCIRuntime   string `json:"oci_runtime"`
+	CgroupParent string `json:"cgroup_parent"`
+	CPU          int64  `json:"cpu"`
+	Memory       int64  `json:"memory"`
 }
 
 // GetPluginRuntime wraps GetPluginRuntimeWithContext using context.Background.
@@ -88,10 +88,10 @@ type RegisterPluginRuntimeInput struct {
 	// Type of the plugin. Required.
 	Type PluginRuntimeType `json:"type"`
 
-	OCIRuntime   string  `json:"oci_runtime,omitempty"`
-	CgroupParent string  `json:"cgroup_parent,omitempty"`
-	CPU          float32 `json:"cpu,omitempty"`
-	Memory       uint64  `json:"memory,omitempty"`
+	OCIRuntime   string `json:"oci_runtime,omitempty"`
+	CgroupParent string `json:"cgroup_parent,omitempty"`
+	CPU          int64  `json:"cpu,omitempty"`
+	Memory       int64  `json:"memory,omitempty"`
 }
 
 // RegisterPluginRuntime wraps RegisterPluginWithContext using context.Background.
diff --git a/sdk/helper/pluginruntimeutil/config.go b/sdk/helper/pluginruntimeutil/config.go
index f492429f999e..b72fa1ead785 100644
--- a/sdk/helper/pluginruntimeutil/config.go
+++ b/sdk/helper/pluginruntimeutil/config.go
@@ -7,7 +7,7 @@ import "github.com/hashicorp/vault/sdk/helper/consts"
 
 const (
 	DefaultOCIRuntime = "runsc"
-	DefaultCPU        = 0.1
+	DefaultCPU        = 1
 	DefaultMemory     = 100000000
 )
 
@@ -17,6 +17,6 @@ type PluginRuntimeConfig struct {
 	Type         consts.PluginRuntimeType `json:"type" structs:"type"`
 	OCIRuntime   string                   `json:"oci_runtime" structs:"oci_runtime"`
 	CgroupParent string                   `json:"cgroup_parent" structs:"cgroup_parent"`
-	CPU          float32                  `json:"cpu" structs:"cpu"`
+	CPU          int64                    `json:"cpu" structs:"cpu"`
 	Memory       int64                    `json:"memory" structs:"memory"`
 }
diff --git a/vault/logical_system.go b/vault/logical_system.go
index a0f2f4064ccf..1c3b3ef11619 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -752,9 +752,9 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 			ociRuntime = pluginruntimeutil.DefaultOCIRuntime
 		}
 		cgroupParent := d.Get("cgroup_parent").(string)
-		cpu := float32(d.Get("cpu").(float64))
+		cpu := d.Get("cpu").(int64)
 		if cpu < 0 {
-			return logical.ErrorResponse("runtime cpu in bytes must be greater than 0"), nil
+			return logical.ErrorResponse("runtime cpu must be greater than 0"), nil
 		}
 		if cpu == 0 {
 			cpu = pluginruntimeutil.DefaultCPU
@@ -766,8 +766,15 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 		if memory == 0 {
 			memory = pluginruntimeutil.DefaultMemory
 		}
-		err := b.Core.pluginRuntimeCatalog.Set(ctx, runtimeName, runtimeType, ociRuntime, cgroupParent, cpu, memory)
-		if err != nil {
+		if err = b.Core.pluginRuntimeCatalog.Set(ctx,
+			&pluginruntimeutil.PluginRuntimeConfig{
+				Name:         runtimeName,
+				Type:         runtimeType,
+				OCIRuntime:   ociRuntime,
+				CgroupParent: cgroupParent,
+				CPU:          cpu,
+				Memory:       memory,
+			}); err != nil {
 			return logical.ErrorResponse(err.Error()), nil
 		}
 	default:
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index df906ef3dfbf..c03061dde5a9 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -2121,7 +2121,7 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
 			},
 			"cpu": {
-				Type:        framework.TypeFloat,
+				Type:        framework.TypeInt64,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
 				Default:     pluginruntimeutil.DefaultCPU,
 			},
@@ -2190,7 +2190,7 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 								Required:    true,
 							},
 							"cpu": {
-								Type:        framework.TypeFloat,
+								Type:        framework.TypeInt64,
 								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
 								Required:    true,
 							},
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index a0bf1f385646..8271a4c513f0 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5933,7 +5933,7 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		"type":          "container",
 		"oci_runtime":   "some-oci-runtime",
 		"cgroup_parent": "/cpulimit/",
-		"cpu":           float32(1),
+		"cpu":           int64(1),
 		"memory":        int64(10000),
 	}
 	if !reflect.DeepEqual(resp.Data, exp) {
diff --git a/vault/plugin_runtime_catalog.go b/vault/plugin_runtime_catalog.go
index 28720810f27f..551e7c47b366 100644
--- a/vault/plugin_runtime_catalog.go
+++ b/vault/plugin_runtime_catalog.go
@@ -69,26 +69,20 @@ func (c *PluginRuntimeCatalog) Get(ctx context.Context, name string, prt consts.
 }
 
 // Set registers a new plugin with the catalog, or updates an existing plugin runtime
-func (c *PluginRuntimeCatalog) Set(ctx context.Context, name string, prt consts.PluginRuntimeType, ociRuntime string, parentCGroup string, cpu float32, memory int64) error {
+func (c *PluginRuntimeCatalog) Set(ctx context.Context, conf *pluginruntimeutil.PluginRuntimeConfig) error {
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
-	entry := &pluginruntimeutil.PluginRuntimeConfig{
-		Name:         name,
-		Type:         prt,
-		OCIRuntime:   ociRuntime,
-		CgroupParent: parentCGroup,
-		CPU:          cpu,
-		Memory:       memory,
+	if conf == nil {
+		return fmt.Errorf("plugin runtime config reference is nil")
 	}
 
-	// TODO what happens to the running existing container with(out) running plugins. Need to shut it down before setting it?
-	buf, err := json.Marshal(entry)
+	buf, err := json.Marshal(conf)
 	if err != nil {
 		return fmt.Errorf("failed to encode plugin entry: %w", err)
 	}
 
-	storageKey := path.Join(prt.String(), name)
+	storageKey := path.Join(conf.Type.String(), conf.Name)
 	logicalEntry := logical.StorageEntry{
 		Key:   storageKey,
 		Value: buf,
@@ -112,7 +106,6 @@ func (c *PluginRuntimeCatalog) Delete(ctx context.Context, name string, prt cons
 		return ErrPluginRuntimeNotFound
 	}
 
-	// TODO what happens to the running existing container with(out) running plugins. Need to shut it down before setting it?
 	return c.catalogView.Delete(ctx, storageKey)
 }
 
diff --git a/vault/plugin_runtime_catalog_test.go b/vault/plugin_runtime_catalog_test.go
index ce6558a2077b..b576cfd61ef7 100644
--- a/vault/plugin_runtime_catalog_test.go
+++ b/vault/plugin_runtime_catalog_test.go
@@ -24,7 +24,7 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 	}
 
 	// Set new plugin runtime
-	err := core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.CgroupParent, expected.CPU, expected.Memory)
+	err := core.pluginRuntimeCatalog.Set(ctx, expected)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}
@@ -40,9 +40,9 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 
 	// Set existing plugin runtime
 	expected.CgroupParent = "memorylimit-cgroup"
-	expected.CPU = 0.5
+	expected.CPU = 2
 	expected.Memory = 5000
-	err = core.pluginRuntimeCatalog.Set(ctx, expected.Name, expected.Type, expected.OCIRuntime, expected.CgroupParent, expected.CPU, expected.Memory)
+	err = core.pluginRuntimeCatalog.Set(ctx, expected)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}

From 66201a14c73b7016a0cb3e4137367575a72dea4e Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 11:27:06 -0700
Subject: [PATCH 09/20] faithfully represent the data passed to us in storage

---
 vault/logical_system.go       | 23 ---------------------
 vault/logical_system_paths.go | 38 -----------------------------------
 vault/logical_system_test.go  | 26 ------------------------
 3 files changed, 87 deletions(-)

diff --git a/vault/logical_system.go b/vault/logical_system.go
index 1c3b3ef11619..927c940a42bd 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -748,24 +748,15 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 	switch runtimeType {
 	case consts.PluginRuntimeTypeContainer:
 		ociRuntime := d.Get("oci_runtime").(string)
-		if ociRuntime == "" {
-			ociRuntime = pluginruntimeutil.DefaultOCIRuntime
-		}
 		cgroupParent := d.Get("cgroup_parent").(string)
 		cpu := d.Get("cpu").(int64)
 		if cpu < 0 {
 			return logical.ErrorResponse("runtime cpu must be greater than 0"), nil
 		}
-		if cpu == 0 {
-			cpu = pluginruntimeutil.DefaultCPU
-		}
 		memory := d.Get("memory").(int64)
 		if memory < 0 {
 			return logical.ErrorResponse("runtime memory in bytes must be greater than 0"), nil
 		}
-		if memory == 0 {
-			memory = pluginruntimeutil.DefaultMemory
-		}
 		if err = b.Core.pluginRuntimeCatalog.Set(ctx,
 			&pluginruntimeutil.PluginRuntimeConfig{
 				Name:         runtimeName,
@@ -840,20 +831,6 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 	}}, nil
 }
 
-func (b *SystemBackend) handlePluginRuntimeCatalogTypedList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
-	runtimeType, err := consts.ParsePluginRuntimeType(d.Get("type").(string))
-	if err != nil {
-		return nil, err
-	}
-
-	runtimes, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
-	if err != nil {
-		return nil, err
-	}
-	sort.Strings(runtimes)
-	return logical.ListResponse(runtimes), nil
-}
-
 func (b *SystemBackend) handlePluginRuntimeCatalogUntypedList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
 	data := make(map[string]interface{})
 	for _, runtimeType := range consts.PluginRuntimeTypes {
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index c03061dde5a9..1114c2994c29 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -2213,44 +2213,6 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 
 func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
 	return []*framework.Path{
-		{
-			Pattern: "plugins/runtimes/catalog/(?P<type>container)/?$",
-
-			DisplayAttrs: &framework.DisplayAttributes{
-				OperationPrefix: "plugins-runtimes-catalog",
-				OperationVerb:   "list",
-				OperationSuffix: "plugins-runtimes-with-type",
-			},
-
-			Fields: map[string]*framework.FieldSchema{
-				"type": {
-					Type:        framework.TypeString,
-					Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_type"][0]),
-				},
-			},
-
-			Operations: map[logical.Operation]framework.OperationHandler{
-				logical.ListOperation: &framework.PathOperation{
-					Callback: b.handlePluginRuntimeCatalogTypedList,
-					Responses: map[int][]framework.Response{
-						http.StatusOK: {{
-							Description: "OK",
-							Fields: map[string]*framework.FieldSchema{
-								"keys": {
-									Type:        framework.TypeStringSlice,
-									Description: "List of plugin runtime names in the catalog",
-									Required:    true,
-								},
-							},
-						}},
-					},
-					Summary: "List the plugin runtimes of a given type in the catalog.",
-				},
-			},
-
-			HelpSynopsis:    strings.TrimSpace(sysHelp["plugin-runtime-catalog"][0]),
-			HelpDescription: strings.TrimSpace(sysHelp["plugin-runtime-catalog"][1]),
-		},
 		{
 			Pattern: "plugins/runtimes/catalog/?$",
 
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index 8271a4c513f0..608f00e2364d 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5940,20 +5940,6 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
 	}
 
-	// List the plugin runtimes of container type
-	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog/container")
-	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-	if err != nil {
-		t.Fatalf("err: %v", err)
-	}
-
-	exp = map[string]interface{}{
-		"keys": []string{"foo"},
-	}
-	if !reflect.DeepEqual(resp.Data, exp) {
-		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
-	}
-
 	// List the plugin runtimes (untyped or all)
 	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
@@ -5996,18 +5982,6 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		t.Fatalf("bad: %#v", resp)
 	}
 
-	// List the plugin runtimes (deleted)
-	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog/container")
-	resp, err = b.HandleRequest(namespace.RootContext(nil), req)
-	if err != nil {
-		t.Fatalf("err: %v", err)
-	}
-
-	exp = map[string]interface{}{}
-	if !reflect.DeepEqual(resp.Data, exp) {
-		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
-	}
-
 	// List the plugin runtimes (untyped or all)
 	req = logical.TestRequest(t, logical.ListOperation, "plugins/runtimes/catalog")
 	resp, err = b.HandleRequest(namespace.RootContext(nil), req)

From d06ef06f425bf738ba269778869f5c6fc41fab41 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 11:33:26 -0700
Subject: [PATCH 10/20] remove ListPluginRuntimesWithContext and use
 ListPluginRuntimes with context.Context parameter

---
 api/sys_plugins_runtimes.go      | 9 ++-------
 api/sys_plugins_runtimes_test.go | 4 ++--
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 5d66cff31683..7e9b59feba1f 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -147,14 +147,9 @@ func (c *Sys) DeregisterPluginRuntimeWithContext(ctx context.Context, i *Deregis
 	return err
 }
 
-// ListPluginRuntimes wraps ListPluginRuntimesWithContext using context.Background.
-func (c *Sys) ListPluginRuntimes(i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
-	return c.ListPluginRuntimesWithContext(context.Background(), i)
-}
-
-// ListPluginRuntimesWithContext lists all plugin runtimes in the catalog and returns their names as a
+// ListPluginRuntimes lists all plugin runtimes in the catalog and returns their names as a
 // list of strings.
-func (c *Sys) ListPluginRuntimesWithContext(ctx context.Context, i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
+func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
 	defer cancelFunc()
 
diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
index 312f5e670c4c..6a2a2b98090b 100644
--- a/api/sys_plugins_runtimes_test.go
+++ b/api/sys_plugins_runtimes_test.go
@@ -119,7 +119,7 @@ func TestListPluginRuntimeTyped(t *testing.T) {
 				Type: tc.runtimeType,
 			}
 
-			list, err := client.Sys().ListPluginRuntimesWithContext(context.Background(), &input)
+			list, err := client.Sys().ListPluginRuntimes(context.Background(), &input)
 			if tc.expectedErrNil && err != nil {
 				t.Fatal(err)
 			}
@@ -158,7 +158,7 @@ func TestListPluginRuntimeUntyped(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			info, err := client.Sys().ListPluginRuntimesWithContext(context.Background(), nil)
+			info, err := client.Sys().ListPluginRuntimes(context.Background(), nil)
 			if tc.expectedErrNil && err != nil {
 				t.Fatal(err)
 			}

From 8ebbdfba3fd1d30871a01daff89655646bfacc20 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 17:20:15 -0700
Subject: [PATCH 11/20] list API returns a slice of plugin runtimes in response
 data

---
 api/sys_plugins_runtimes.go          | 113 ++++++++++++---------------
 api/sys_plugins_runtimes_test.go     |  80 +++++++++++++++++--
 vault/logical_system.go              |  27 +++++--
 vault/logical_system_paths.go        |   8 +-
 vault/logical_system_test.go         |  52 +++++++-----
 vault/plugin_runtime_catalog.go      |  12 +--
 vault/plugin_runtime_catalog_test.go |  12 +--
 7 files changed, 188 insertions(+), 116 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 7e9b59feba1f..0c605f55a3a9 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -12,27 +12,6 @@ import (
 	"github.com/mitchellh/mapstructure"
 )
 
-type PluginRuntimeDetails struct {
-	Type         string `json:"type"`
-	Name         string `json:"name"`
-	OCIRuntime   string `json:"oci_runtime"`
-	CgroupParent string `json:"cgroup_parent"`
-	CPU          int64  `json:"cpu"`
-	Memory       int64  `json:"memory"`
-}
-
-// ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
-type ListPluginRuntimesInput struct {
-	// Type of the plugin. Required.
-	Type PluginRuntimeType `json:"type"`
-}
-
-// ListPluginRuntimesResponse is the response from the ListPluginRuntimes call.
-type ListPluginRuntimesResponse struct {
-	// RuntimesByType is the list of plugin runtimes by type.
-	RuntimesByType map[PluginRuntimeType][]string `json:"types"`
-}
-
 // GetPluginRuntimeInput is used as input to the GetPluginRuntime function.
 type GetPluginRuntimeInput struct {
 	Name string `json:"-"`
@@ -51,13 +30,8 @@ type GetPluginRuntimeResponse struct {
 	Memory       int64  `json:"memory"`
 }
 
-// GetPluginRuntime wraps GetPluginRuntimeWithContext using context.Background.
-func (c *Sys) GetPluginRuntime(i *GetPluginRuntimeInput) (*GetPluginRuntimeResponse, error) {
-	return c.GetPluginRuntimeWithContext(context.Background(), i)
-}
-
-// GetPluginRuntimeWithContext retrieves information about the plugin.
-func (c *Sys) GetPluginRuntimeWithContext(ctx context.Context, i *GetPluginRuntimeInput) (*GetPluginRuntimeResponse, error) {
+// GetPluginRuntime retrieves information about the plugin.
+func (c *Sys) GetPluginRuntime(ctx context.Context, i *GetPluginRuntimeInput) (*GetPluginRuntimeResponse, error) {
 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
 	defer cancelFunc()
 
@@ -94,13 +68,8 @@ type RegisterPluginRuntimeInput struct {
 	Memory       int64  `json:"memory,omitempty"`
 }
 
-// RegisterPluginRuntime wraps RegisterPluginWithContext using context.Background.
-func (c *Sys) RegisterPluginRuntime(i *RegisterPluginRuntimeInput) error {
-	return c.RegisterPluginRuntimeWithContext(context.Background(), i)
-}
-
-// RegisterPluginRuntimeWithContext registers the plugin with the given information.
-func (c *Sys) RegisterPluginRuntimeWithContext(ctx context.Context, i *RegisterPluginRuntimeInput) error {
+// RegisterPluginRuntime registers the plugin with the given information.
+func (c *Sys) RegisterPluginRuntime(ctx context.Context, i *RegisterPluginRuntimeInput) error {
 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
 	defer cancelFunc()
 
@@ -127,14 +96,9 @@ type DeregisterPluginRuntimeInput struct {
 	Type PluginRuntimeType `json:"type"`
 }
 
-// DeregisterPluginRuntime wraps DeregisterPluginRuntimeWithContext using context.Background.
-func (c *Sys) DeregisterPluginRuntime(i *DeregisterPluginRuntimeInput) error {
-	return c.DeregisterPluginRuntimeWithContext(context.Background(), i)
-}
-
-// DeregisterPluginRuntimeWithContext removes the plugin with the given name from the plugin
+// DeregisterPluginRuntime removes the plugin with the given name from the plugin
 // catalog.
-func (c *Sys) DeregisterPluginRuntimeWithContext(ctx context.Context, i *DeregisterPluginRuntimeInput) error {
+func (c *Sys) DeregisterPluginRuntime(ctx context.Context, i *DeregisterPluginRuntimeInput) error {
 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
 	defer cancelFunc()
 
@@ -147,6 +111,27 @@ func (c *Sys) DeregisterPluginRuntimeWithContext(ctx context.Context, i *Deregis
 	return err
 }
 
+type PluginRuntimeDetails struct {
+	Type         string `json:"type"`
+	Name         string `json:"name"`
+	OCIRuntime   string `json:"oci_runtime"`
+	CgroupParent string `json:"cgroup_parent"`
+	CPU          int64  `json:"cpu"`
+	Memory       int64  `json:"memory"`
+}
+
+// ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
+type ListPluginRuntimesInput struct {
+	// Type of the plugin. Required.
+	Type PluginRuntimeType `json:"type"`
+}
+
+// ListPluginRuntimesResponse is the response from the ListPluginRuntimes call.
+type ListPluginRuntimesResponse struct {
+	// RuntimesByType is the list of plugin runtimes by type.
+	Runtimes []PluginRuntimeDetails `json:"runtimes"`
+}
+
 // ListPluginRuntimes lists all plugin runtimes in the catalog and returns their names as a
 // list of strings.
 func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
@@ -173,40 +158,40 @@ func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput
 	if secret == nil || secret.Data == nil {
 		return nil, errors.New("data from server response is empty")
 	}
+	if _, ok := secret.Data["runtimes"]; !ok {
+		return nil, fmt.Errorf("data from server response does not contain runtimes")
+	}
+
+	runtimesRaw, ok := secret.Data["runtimes"].([]interface{})
+	if !ok {
+		return nil, fmt.Errorf("unable to parse runtimes")
+	}
 
 	result := &ListPluginRuntimesResponse{
-		RuntimesByType: make(map[PluginRuntimeType][]string),
+		Runtimes: []PluginRuntimeDetails{},
+	}
+	var runtimes []PluginRuntimeDetails
+	for _, runtimeRaw := range runtimesRaw {
+		var runtime PluginRuntimeDetails
+		if err = mapstructure.Decode(runtimeRaw, &runtime); err != nil {
+			return nil, err
+		}
+		runtimes = append(runtimes, runtime)
 	}
 
 	// return all runtimes in the catalog
 	if i == nil {
-		for _, runtimeType := range PluginRuntimeTypes {
-			runtimesRaw, ok := secret.Data[runtimeType.String()]
-			if !ok {
-				continue
-			}
-
-			var tmp []string
-			if err = mapstructure.Decode(runtimesRaw, &tmp); err != nil {
-				return nil, err
-			}
-			result.RuntimesByType[runtimeType] = tmp
-		}
+		result.Runtimes = runtimes
 		return result, nil
 	}
 
 	switch i.Type {
 	default:
-		runtimesRaw, ok := secret.Data[i.Type.String()]
-		if !ok {
-			return nil, fmt.Errorf("no %s entry in returned data", i.Type.String())
-		}
-
-		var tmp []string
-		if err := mapstructure.Decode(runtimesRaw, &tmp); err != nil {
-			return nil, err
+		for _, runtime := range runtimes {
+			if runtime.Type == i.Type.String() {
+				result.Runtimes = append(result.Runtimes, runtime)
+			}
 		}
-		result.RuntimesByType[i.Type] = tmp
 	}
 	return result, nil
 }
diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
index 6a2a2b98090b..58a61eb869f3 100644
--- a/api/sys_plugins_runtimes_test.go
+++ b/api/sys_plugins_runtimes_test.go
@@ -22,7 +22,7 @@ func TestRegisterPluginRuntime(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	err = client.Sys().RegisterPluginRuntimeWithContext(context.Background(), &RegisterPluginRuntimeInput{
+	err = client.Sys().RegisterPluginRuntime(context.Background(), &RegisterPluginRuntimeInput{
 		Name:         "gvisor",
 		Type:         PluginRuntimeTypeContainer,
 		OCIRuntime:   "runsc",
@@ -68,7 +68,7 @@ func TestGetPluginRuntime(t *testing.T) {
 				Type: PluginRuntimeTypeContainer,
 			}
 
-			info, err := client.Sys().GetPluginRuntimeWithContext(context.Background(), &input)
+			info, err := client.Sys().GetPluginRuntime(context.Background(), &input)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -91,8 +91,15 @@ func TestListPluginRuntimeTyped(t *testing.T) {
 			runtimeType: PluginRuntimeTypeContainer,
 			body:        listPluginRuntimeTypedResponse,
 			expectedResponse: &ListPluginRuntimesResponse{
-				RuntimesByType: map[PluginRuntimeType][]string{
-					PluginRuntimeTypeContainer: {"gvisor"},
+				Runtimes: []PluginRuntimeDetails{
+					{
+						Type:         "container",
+						Name:         "gvisor",
+						OCIRuntime:   "runsc",
+						CgroupParent: "/cpulimit/",
+						CPU:          1,
+						Memory:       10000,
+					},
 				},
 			},
 			expectedErrNil: true,
@@ -140,8 +147,31 @@ func TestListPluginRuntimeUntyped(t *testing.T) {
 		{
 			body: listPluginRuntimeUntypedResponse,
 			expectedResponse: &ListPluginRuntimesResponse{
-				RuntimesByType: map[PluginRuntimeType][]string{
-					PluginRuntimeTypeContainer: {"gvisor", "foo", "bar"},
+				Runtimes: []PluginRuntimeDetails{
+					{
+						Type:         "container",
+						Name:         "gvisor",
+						OCIRuntime:   "runsc",
+						CgroupParent: "/cpulimit/",
+						CPU:          1,
+						Memory:       10000,
+					},
+					{
+						Type:         "container",
+						Name:         "foo",
+						OCIRuntime:   "otherociruntime",
+						CgroupParent: "/memorylimit/",
+						CPU:          2,
+						Memory:       20000,
+					},
+					{
+						Type:         "container",
+						Name:         "bar",
+						OCIRuntime:   "otherociruntime",
+						CgroupParent: "/cpulimit/",
+						CPU:          3,
+						Memory:       30000,
+					},
 				},
 			},
 			expectedErrNil: true,
@@ -187,7 +217,16 @@ const getPluginRuntimeResponse = `{
 const listPluginRuntimeTypedResponse = `{
     "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
     "data": {
-        "container": ["gvisor"]
+       "runtimes": [
+			{
+				"name": "gvisor",
+				"type": "container",
+				"oci_runtime": "runsc",
+				"cgroup_parent": "/cpulimit/",
+				"cpu": 1,
+				"memory": 10000
+			}
+		]
     },
     "warnings": null,
     "auth": null
@@ -197,7 +236,32 @@ const listPluginRuntimeTypedResponse = `{
 const listPluginRuntimeUntypedResponse = `{
     "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
     "data": {
-        "container": ["gvisor", "foo", "bar"]
+        "runtimes": [
+			{
+				"name": "gvisor",
+				"type": "container",
+				"oci_runtime": "runsc",
+				"cgroup_parent": "/cpulimit/",
+				"cpu": 1,
+				"memory": 10000
+			},
+			{
+				"name": "foo",
+				"type": "container",
+				"oci_runtime": "otherociruntime",
+				"cgroup_parent": "/memorylimit/",
+				"cpu": 2,
+				"memory": 20000
+			},
+			{
+				"name": "bar",
+				"type": "container",
+				"oci_runtime": "otherociruntime",
+				"cgroup_parent": "/cpulimit/",
+				"cpu": 3,
+				"memory": 30000
+			}
+		]
     },
     "warnings": null,
     "auth": null
diff --git a/vault/logical_system.go b/vault/logical_system.go
index 927c940a42bd..8d2c529de77f 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -831,27 +831,40 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 	}}, nil
 }
 
-func (b *SystemBackend) handlePluginRuntimeCatalogUntypedList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
-	data := make(map[string]interface{})
+func (b *SystemBackend) handlePluginRuntimeCatalogList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
+	var data []map[string]interface{}
 	for _, runtimeType := range consts.PluginRuntimeTypes {
 		if runtimeType == consts.PluginRuntimeTypeUnsupported {
 			continue
 		}
-		runtimes, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
+		configs, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
 		if err != nil {
 			return nil, err
 		}
-		if len(runtimes) > 0 {
-			sort.Strings(runtimes)
-			data[runtimeType.String()] = runtimes
+
+		if len(configs) > 0 {
+			sort.Slice(configs, func(i, j int) bool {
+				return strings.Compare(configs[i].Name, configs[j].Name) == -1
+			})
+			for _, conf := range configs {
+				data = append(data, map[string]interface{}{
+					"name":          conf.Name,
+					"type":          conf.Type.String(),
+					"oci_runtime":   conf.OCIRuntime,
+					"cgroup_parent": conf.CgroupParent,
+					"cpu":           conf.CPU,
+					"memory":        conf.Memory,
+				})
+			}
 		}
 	}
 
 	resp := &logical.Response{
 		Data: map[string]interface{}{},
 	}
+
 	if len(data) > 0 {
-		resp.Data["types"] = data
+		resp.Data["runtimes"] = data
 	}
 
 	return resp, nil
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index 1114c2994c29..a8fc092c252d 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -2224,14 +2224,14 @@ func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
 
 			Operations: map[logical.Operation]framework.OperationHandler{
 				logical.ListOperation: &framework.PathOperation{
-					Callback: b.handlePluginRuntimeCatalogUntypedList,
+					Callback: b.handlePluginRuntimeCatalogList,
 					Responses: map[int][]framework.Response{
 						http.StatusOK: {{
 							Description: "OK",
 							Fields: map[string]*framework.FieldSchema{
-								"types": {
-									Type:        framework.TypeMap,
-									Description: "Map of plugin runtime types to their plugin runtime names in the catalog",
+								"runtimes": {
+									Type:        framework.TypeSlice,
+									Description: "List of all plugin runtimes in the catalog",
 									Required:    true,
 								},
 							},
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index 608f00e2364d..1cc23d5f9dab 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -33,6 +33,7 @@ import (
 	"github.com/hashicorp/vault/sdk/helper/compressutil"
 	"github.com/hashicorp/vault/sdk/helper/consts"
 	"github.com/hashicorp/vault/sdk/helper/jsonutil"
+	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
 	"github.com/hashicorp/vault/sdk/helper/pluginutil"
 	"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
 	"github.com/hashicorp/vault/sdk/logical"
@@ -5888,13 +5889,22 @@ func TestSystemBackend_ReadExperiments(t *testing.T) {
 func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	b := testSystemBackend(t)
 
+	conf := pluginruntimeutil.PluginRuntimeConfig{
+		Name:         "foo",
+		Type:         consts.PluginRuntimeTypeContainer,
+		OCIRuntime:   "some-oci-runtime",
+		CgroupParent: "/cpulimit/",
+		CPU:          1,
+		Memory:       10000,
+	}
+
 	// Register the plugin runtime
-	req := logical.TestRequest(t, logical.UpdateOperation, "plugins/runtimes/catalog/container/foo")
+	req := logical.TestRequest(t, logical.UpdateOperation, fmt.Sprintf("plugins/runtimes/catalog/%s/%s", conf.Type.String(), conf.Name))
 	req.Data = map[string]interface{}{
-		"oci_runtime":   "some-oci-runtime",
-		"cgroup_parent": "/cpulimit/",
-		"cpu":           1,
-		"memory":        10000,
+		"oci_runtime":   conf.OCIRuntime,
+		"cgroup_parent": conf.OCIRuntime,
+		"cpu":           conf.CPU,
+		"memory":        conf.Memory,
 	}
 
 	resp, err := b.HandleRequest(namespace.RootContext(nil), req)
@@ -5928,16 +5938,16 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		true,
 	)
 
-	exp := map[string]interface{}{
-		"name":          "foo",
-		"type":          "container",
-		"oci_runtime":   "some-oci-runtime",
-		"cgroup_parent": "/cpulimit/",
-		"cpu":           int64(1),
-		"memory":        int64(10000),
+	readExp := map[string]interface{}{
+		"type":          conf.Type.String(),
+		"name":          conf.Name,
+		"oci_runtime":   conf.OCIRuntime,
+		"cgroup_parent": conf.OCIRuntime,
+		"cpu":           conf.CPU,
+		"memory":        conf.Memory,
 	}
-	if !reflect.DeepEqual(resp.Data, exp) {
-		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	if !reflect.DeepEqual(resp.Data, readExp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, readExp)
 	}
 
 	// List the plugin runtimes (untyped or all)
@@ -5947,11 +5957,11 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		t.Fatalf("err: %v", err)
 	}
 
-	exp = map[string]interface{}{
-		"types": map[string]interface{}{"container": []string{"foo"}},
+	listExp := map[string]interface{}{
+		"runtimes": []map[string]interface{}{readExp},
 	}
-	if !reflect.DeepEqual(resp.Data, exp) {
-		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	if !reflect.DeepEqual(resp.Data, listExp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, listExp)
 	}
 
 	// Delete the plugin runtime
@@ -5989,8 +5999,8 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		t.Fatalf("err: %v", err)
 	}
 
-	exp = map[string]interface{}{}
-	if !reflect.DeepEqual(resp.Data, exp) {
-		t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
+	listExp = map[string]interface{}{}
+	if !reflect.DeepEqual(resp.Data, listExp) {
+		t.Fatalf("got: %#v expect: %#v", resp.Data, listExp)
 	}
 }
diff --git a/vault/plugin_runtime_catalog.go b/vault/plugin_runtime_catalog.go
index 551e7c47b366..9cb0f58869fd 100644
--- a/vault/plugin_runtime_catalog.go
+++ b/vault/plugin_runtime_catalog.go
@@ -109,11 +109,11 @@ func (c *PluginRuntimeCatalog) Delete(ctx context.Context, name string, prt cons
 	return c.catalogView.Delete(ctx, storageKey)
 }
 
-func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntimeType) ([]string, error) {
+func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntimeType) ([]*pluginruntimeutil.PluginRuntimeConfig, error) {
 	c.lock.RLock()
 	defer c.lock.RUnlock()
 
-	var retList []string
+	var retList []*pluginruntimeutil.PluginRuntimeConfig
 	keys, err := logical.CollectKeys(ctx, c.catalogView)
 	if err != nil {
 		return nil, err
@@ -125,16 +125,16 @@ func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntim
 			continue
 		}
 
-		runner := new(pluginruntimeutil.PluginRuntimeConfig)
-		if err := jsonutil.DecodeJSON(entry.Value, runner); err != nil {
+		conf := new(pluginruntimeutil.PluginRuntimeConfig)
+		if err := jsonutil.DecodeJSON(entry.Value, conf); err != nil {
 			return nil, fmt.Errorf("failed to decode plugin runtime entry: %w", err)
 		}
 
-		if runner.Type != prt {
+		if conf.Type != prt {
 			continue
 		}
 
-		retList = append(retList, runner.Name)
+		retList = append(retList, conf)
 	}
 	return retList, nil
 }
diff --git a/vault/plugin_runtime_catalog_test.go b/vault/plugin_runtime_catalog_test.go
index b576cfd61ef7..50bded62fca4 100644
--- a/vault/plugin_runtime_catalog_test.go
+++ b/vault/plugin_runtime_catalog_test.go
@@ -57,12 +57,12 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 		t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", runner, expected)
 	}
 
-	names, err := core.pluginRuntimeCatalog.List(ctx, expected.Type)
+	configs, err := core.pluginRuntimeCatalog.List(ctx, expected.Type)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}
-	if len(names) != 1 {
-		t.Fatalf("expected plugin runtime catalog to have 1 container runtime but got %d", len(names))
+	if len(configs) != 1 {
+		t.Fatalf("expected plugin runtime catalog to have 1 container runtime but got %d", len(configs))
 	}
 
 	// Delete plugin runtime
@@ -72,11 +72,11 @@ func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
 	}
 
 	// Assert the plugin runtime catalog is empty
-	names, err = core.pluginRuntimeCatalog.List(ctx, expected.Type)
+	configs, err = core.pluginRuntimeCatalog.List(ctx, expected.Type)
 	if err != nil {
 		t.Fatalf("err: %v", err)
 	}
-	if len(names) != 0 {
-		t.Fatalf("expected plugin runtime catalog to have 0 container runtimes but got %d", len(names))
+	if len(configs) != 0 {
+		t.Fatalf("expected plugin runtime catalog to have 0 container runtimes but got %d", len(configs))
 	}
 }

From 75d6d48fb0627113260fe57dba70807096b07279 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 18:07:23 -0700
Subject: [PATCH 12/20] using encoding/json to marshal and unmarshal runtimes
 in Data instead of mapstructure.Decode

---
 api/sys_plugins_runtimes.go | 33 +++++++++++----------------------
 1 file changed, 11 insertions(+), 22 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 0c605f55a3a9..83b8ab5debf2 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -5,11 +5,10 @@ package api
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
-
-	"github.com/mitchellh/mapstructure"
 )
 
 // GetPluginRuntimeInput is used as input to the GetPluginRuntime function.
@@ -162,35 +161,25 @@ func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput
 		return nil, fmt.Errorf("data from server response does not contain runtimes")
 	}
 
-	runtimesRaw, ok := secret.Data["runtimes"].([]interface{})
-	if !ok {
-		return nil, fmt.Errorf("unable to parse runtimes")
+	runtimesJSON, err := json.Marshal(secret.Data["runtimes"])
+	if err != nil {
+		return nil, fmt.Errorf("unable to marshal runtimes")
 	}
 
-	result := &ListPluginRuntimesResponse{
-		Runtimes: []PluginRuntimeDetails{},
-	}
 	var runtimes []PluginRuntimeDetails
-	for _, runtimeRaw := range runtimesRaw {
-		var runtime PluginRuntimeDetails
-		if err = mapstructure.Decode(runtimeRaw, &runtime); err != nil {
-			return nil, err
-		}
-		runtimes = append(runtimes, runtime)
+	if err = json.Unmarshal(runtimesJSON, &runtimes); err != nil {
+		return nil, fmt.Errorf("unable to parse runtimes")
 	}
 
 	// return all runtimes in the catalog
 	if i == nil {
-		result.Runtimes = runtimes
-		return result, nil
+		return &ListPluginRuntimesResponse{Runtimes: runtimes}, nil
 	}
 
-	switch i.Type {
-	default:
-		for _, runtime := range runtimes {
-			if runtime.Type == i.Type.String() {
-				result.Runtimes = append(result.Runtimes, runtime)
-			}
+	result := &ListPluginRuntimesResponse{Runtimes: []PluginRuntimeDetails{}}
+	for _, runtime := range runtimes {
+		if runtime.Type == i.Type.String() {
+			result.Runtimes = append(result.Runtimes, runtime)
 		}
 	}
 	return result, nil

From 9e59d843b4a0ce841e74eff47348fbcc8e6f4744 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Wed, 30 Aug 2023 18:34:55 -0700
Subject: [PATCH 13/20] change data from type []map[string]interface{} to
 []map[string]any

---
 vault/logical_system.go      | 4 ++--
 vault/logical_system_test.go | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/vault/logical_system.go b/vault/logical_system.go
index 8d2c529de77f..cfef50b7c53d 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -832,7 +832,7 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 }
 
 func (b *SystemBackend) handlePluginRuntimeCatalogList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
-	var data []map[string]interface{}
+	var data []map[string]any
 	for _, runtimeType := range consts.PluginRuntimeTypes {
 		if runtimeType == consts.PluginRuntimeTypeUnsupported {
 			continue
@@ -847,7 +847,7 @@ func (b *SystemBackend) handlePluginRuntimeCatalogList(ctx context.Context, _ *l
 				return strings.Compare(configs[i].Name, configs[j].Name) == -1
 			})
 			for _, conf := range configs {
-				data = append(data, map[string]interface{}{
+				data = append(data, map[string]any{
 					"name":          conf.Name,
 					"type":          conf.Type.String(),
 					"oci_runtime":   conf.OCIRuntime,
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index 1cc23d5f9dab..a235c2e5830e 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5938,7 +5938,7 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		true,
 	)
 
-	readExp := map[string]interface{}{
+	readExp := map[string]any{
 		"type":          conf.Type.String(),
 		"name":          conf.Name,
 		"oci_runtime":   conf.OCIRuntime,
@@ -5958,7 +5958,7 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	}
 
 	listExp := map[string]interface{}{
-		"runtimes": []map[string]interface{}{readExp},
+		"runtimes": []map[string]any{readExp},
 	}
 	if !reflect.DeepEqual(resp.Data, listExp) {
 		t.Fatalf("got: %#v expect: %#v", resp.Data, listExp)

From 48bfbc05e4696e460996d328451f0f4ff5e4f9ca Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 08:31:59 -0700
Subject: [PATCH 14/20] Update api/sys_plugins_runtimes.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
---
 api/sys_plugins_runtimes.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 83b8ab5debf2..49f080794ca4 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -187,5 +187,5 @@ func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput
 
 // pluginRuntimeCatalogPathByType is a helper to construct the proper API path by plugin type
 func pluginRuntimeCatalogPathByType(runtimeType PluginRuntimeType, name string) string {
-	return fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", runtimeType, name)
+	return fmt.Sprintf("/v1/sys/plugins/runtimes/catalog/%s/%s", runtimeType, name)
 }

From b0308039cfbf5b448f3bb175db086ab6a69166c0 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 08:33:36 -0700
Subject: [PATCH 15/20] Update vault/logical_system.go

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
---
 vault/logical_system.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vault/logical_system.go b/vault/logical_system.go
index cfef50b7c53d..898b63f4787a 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -751,11 +751,11 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 		cgroupParent := d.Get("cgroup_parent").(string)
 		cpu := d.Get("cpu").(int64)
 		if cpu < 0 {
-			return logical.ErrorResponse("runtime cpu must be greater than 0"), nil
+			return logical.ErrorResponse("runtime cpu cannot be negative"), nil
 		}
 		memory := d.Get("memory").(int64)
 		if memory < 0 {
-			return logical.ErrorResponse("runtime memory in bytes must be greater than 0"), nil
+			return logical.ErrorResponse("runtime memory in bytes cannot be negative"), nil
 		}
 		if err = b.Core.pluginRuntimeCatalog.Set(ctx,
 			&pluginruntimeutil.PluginRuntimeConfig{

From 5a1898815ed0c9eade10f495fb3a898eaab81a7e Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 08:52:36 -0700
Subject: [PATCH 16/20] add mapstructure tags to PluginRuntimeDetails

---
 api/sys_plugins_runtimes.go | 38 ++++++++++++++++++-------------------
 1 file changed, 18 insertions(+), 20 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index 49f080794ca4..c7037b2e760e 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -5,10 +5,11 @@ package api
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
+
+	"github.com/mitchellh/mapstructure"
 )
 
 // GetPluginRuntimeInput is used as input to the GetPluginRuntime function.
@@ -111,12 +112,12 @@ func (c *Sys) DeregisterPluginRuntime(ctx context.Context, i *DeregisterPluginRu
 }
 
 type PluginRuntimeDetails struct {
-	Type         string `json:"type"`
-	Name         string `json:"name"`
-	OCIRuntime   string `json:"oci_runtime"`
-	CgroupParent string `json:"cgroup_parent"`
-	CPU          int64  `json:"cpu"`
-	Memory       int64  `json:"memory"`
+	Type         string `json:"type" mapstructure:"type"`
+	Name         string `json:"name" mapstructure:"name"`
+	OCIRuntime   string `json:"oci_runtime" mapstructure:"oci_runtime"`
+	CgroupParent string `json:"cgroup_parent" mapstructure:"cgroup_parent"`
+	CPU          int64  `json:"cpu" mapstructure:"cpu"`
+	Memory       int64  `json:"memory" mapstructure:"memory"`
 }
 
 // ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
@@ -133,12 +134,12 @@ type ListPluginRuntimesResponse struct {
 
 // ListPluginRuntimes lists all plugin runtimes in the catalog and returns their names as a
 // list of strings.
-func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
+func (c *Sys) ListPluginRuntimes(ctx context.Context, input *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
 	defer cancelFunc()
 
-	if i != nil && i.Type == PluginRuntimeTypeUnsupported {
-		return nil, fmt.Errorf("%q is not a supported runtime type", i.Type.String())
+	if input != nil && input.Type == PluginRuntimeTypeUnsupported {
+		return nil, fmt.Errorf("%q is not a supported runtime type", input.Type.String())
 	}
 
 	resp, err := c.c.rawRequestWithContext(ctx, c.c.NewRequest(http.MethodGet, "/v1/sys/plugins/runtimes/catalog"))
@@ -161,24 +162,21 @@ func (c *Sys) ListPluginRuntimes(ctx context.Context, i *ListPluginRuntimesInput
 		return nil, fmt.Errorf("data from server response does not contain runtimes")
 	}
 
-	runtimesJSON, err := json.Marshal(secret.Data["runtimes"])
-	if err != nil {
-		return nil, fmt.Errorf("unable to marshal runtimes")
-	}
-
 	var runtimes []PluginRuntimeDetails
-	if err = json.Unmarshal(runtimesJSON, &runtimes); err != nil {
-		return nil, fmt.Errorf("unable to parse runtimes")
+	if err = mapstructure.Decode(secret.Data["runtimes"], &runtimes); err != nil {
+		return nil, err
 	}
 
 	// return all runtimes in the catalog
-	if i == nil {
+	if input == nil {
 		return &ListPluginRuntimesResponse{Runtimes: runtimes}, nil
 	}
 
-	result := &ListPluginRuntimesResponse{Runtimes: []PluginRuntimeDetails{}}
+	result := &ListPluginRuntimesResponse{
+		Runtimes: []PluginRuntimeDetails{},
+	}
 	for _, runtime := range runtimes {
-		if runtime.Type == i.Type.String() {
+		if runtime.Type == input.Type.String() {
 			result.Runtimes = append(result.Runtimes, runtime)
 		}
 	}

From 257d6fe18bc3b5d8e94d0b3092375a119e0ecf25 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 10:03:53 -0700
Subject: [PATCH 17/20] rename fields cpu and memory to cpu_nanos and
 memory_bytes

---
 api/sys_plugins_runtimes.go            |  8 ++++----
 api/sys_plugins_runtimes_test.go       | 20 ++++++++++----------
 sdk/helper/pluginruntimeutil/config.go |  6 ------
 vault/logical_system.go                | 22 +++++++++++-----------
 vault/logical_system_paths.go          | 20 ++++++++------------
 vault/logical_system_test.go           |  8 ++++----
 6 files changed, 37 insertions(+), 47 deletions(-)

diff --git a/api/sys_plugins_runtimes.go b/api/sys_plugins_runtimes.go
index c7037b2e760e..c3380a85d1bf 100644
--- a/api/sys_plugins_runtimes.go
+++ b/api/sys_plugins_runtimes.go
@@ -26,8 +26,8 @@ type GetPluginRuntimeResponse struct {
 	Name         string `json:"name"`
 	OCIRuntime   string `json:"oci_runtime"`
 	CgroupParent string `json:"cgroup_parent"`
-	CPU          int64  `json:"cpu"`
-	Memory       int64  `json:"memory"`
+	CPU          int64  `json:"cpu_nanos"`
+	Memory       int64  `json:"memory_bytes"`
 }
 
 // GetPluginRuntime retrieves information about the plugin.
@@ -116,8 +116,8 @@ type PluginRuntimeDetails struct {
 	Name         string `json:"name" mapstructure:"name"`
 	OCIRuntime   string `json:"oci_runtime" mapstructure:"oci_runtime"`
 	CgroupParent string `json:"cgroup_parent" mapstructure:"cgroup_parent"`
-	CPU          int64  `json:"cpu" mapstructure:"cpu"`
-	Memory       int64  `json:"memory" mapstructure:"memory"`
+	CPU          int64  `json:"cpu_nanos" mapstructure:"cpu_nanos"`
+	Memory       int64  `json:"memory_bytes" mapstructure:"memory_bytes"`
 }
 
 // ListPluginRuntimesInput is used as input to the ListPluginRuntimes function.
diff --git a/api/sys_plugins_runtimes_test.go b/api/sys_plugins_runtimes_test.go
index 58a61eb869f3..6c3486a31a00 100644
--- a/api/sys_plugins_runtimes_test.go
+++ b/api/sys_plugins_runtimes_test.go
@@ -207,8 +207,8 @@ const getPluginRuntimeResponse = `{
         "type": "container",
         "oci_runtime": "runsc",
         "cgroup_parent": "/cpulimit/",
-        "cpu": 1,
-        "memory": 10000
+        "cpu_nanos": 1,
+        "memory_bytes": 10000
     },
     "warnings": null,
     "auth": null
@@ -223,8 +223,8 @@ const listPluginRuntimeTypedResponse = `{
 				"type": "container",
 				"oci_runtime": "runsc",
 				"cgroup_parent": "/cpulimit/",
-				"cpu": 1,
-				"memory": 10000
+				"cpu_nanos": 1,
+				"memory_bytes": 10000
 			}
 		]
     },
@@ -242,24 +242,24 @@ const listPluginRuntimeUntypedResponse = `{
 				"type": "container",
 				"oci_runtime": "runsc",
 				"cgroup_parent": "/cpulimit/",
-				"cpu": 1,
-				"memory": 10000
+				"cpu_nanos": 1,
+				"memory_bytes": 10000
 			},
 			{
 				"name": "foo",
 				"type": "container",
 				"oci_runtime": "otherociruntime",
 				"cgroup_parent": "/memorylimit/",
-				"cpu": 2,
-				"memory": 20000
+				"cpu_nanos": 2,
+				"memory_bytes": 20000
 			},
 			{
 				"name": "bar",
 				"type": "container",
 				"oci_runtime": "otherociruntime",
 				"cgroup_parent": "/cpulimit/",
-				"cpu": 3,
-				"memory": 30000
+				"cpu_nanos": 3,
+				"memory_bytes": 30000
 			}
 		]
     },
diff --git a/sdk/helper/pluginruntimeutil/config.go b/sdk/helper/pluginruntimeutil/config.go
index b72fa1ead785..4a361ddfc7b5 100644
--- a/sdk/helper/pluginruntimeutil/config.go
+++ b/sdk/helper/pluginruntimeutil/config.go
@@ -5,12 +5,6 @@ package pluginruntimeutil
 
 import "github.com/hashicorp/vault/sdk/helper/consts"
 
-const (
-	DefaultOCIRuntime = "runsc"
-	DefaultCPU        = 1
-	DefaultMemory     = 100000000
-)
-
 // PluginRuntimeConfig defines the metadata needed to run a plugin runtime
 type PluginRuntimeConfig struct {
 	Name         string                   `json:"name" structs:"name"`
diff --git a/vault/logical_system.go b/vault/logical_system.go
index 898b63f4787a..e7d56ea6f6a5 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -749,11 +749,11 @@ func (b *SystemBackend) handlePluginRuntimeCatalogUpdate(ctx context.Context, _
 	case consts.PluginRuntimeTypeContainer:
 		ociRuntime := d.Get("oci_runtime").(string)
 		cgroupParent := d.Get("cgroup_parent").(string)
-		cpu := d.Get("cpu").(int64)
+		cpu := d.Get("cpu_nanos").(int64)
 		if cpu < 0 {
-			return logical.ErrorResponse("runtime cpu cannot be negative"), nil
+			return logical.ErrorResponse("runtime cpu in nanos cannot be negative"), nil
 		}
-		memory := d.Get("memory").(int64)
+		memory := d.Get("memory_bytes").(int64)
 		if memory < 0 {
 			return logical.ErrorResponse("runtime memory in bytes cannot be negative"), nil
 		}
@@ -826,8 +826,8 @@ func (b *SystemBackend) handlePluginRuntimeCatalogRead(ctx context.Context, _ *l
 		"type":          conf.Type.String(),
 		"oci_runtime":   conf.OCIRuntime,
 		"cgroup_parent": conf.CgroupParent,
-		"cpu":           conf.CPU,
-		"memory":        conf.Memory,
+		"cpu_nanos":     conf.CPU,
+		"memory_bytes":  conf.Memory,
 	}}, nil
 }
 
@@ -852,8 +852,8 @@ func (b *SystemBackend) handlePluginRuntimeCatalogList(ctx context.Context, _ *l
 					"type":          conf.Type.String(),
 					"oci_runtime":   conf.OCIRuntime,
 					"cgroup_parent": conf.CgroupParent,
-					"cpu":           conf.CPU,
-					"memory":        conf.Memory,
+					"cpu_nanos":     conf.CPU,
+					"memory_bytes":  conf.Memory,
 				})
 			}
 		}
@@ -6129,12 +6129,12 @@ This path responds to the following HTTP methods.
 		"Optional parent cgroup for the container",
 		"",
 	},
-	"plugin-runtime-catalog_cpu": {
-		"The limit of runtime CPU (default 0.1)",
+	"plugin-runtime-catalog_cpu-nanos": {
+		"The limit of runtime CPU in nanos",
 		"",
 	},
-	"plugin-runtime-catalog_memory": {
-		"The limit of runtime memory in bytes (default 100000000)",
+	"plugin-runtime-catalog_memory-bytes": {
+		"The limit of runtime memory in bytes",
 		"",
 	},
 	"leases": {
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index a8fc092c252d..1d143493b944 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -8,7 +8,6 @@ import (
 	"strings"
 
 	"github.com/hashicorp/vault/sdk/framework"
-	"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
 	"github.com/hashicorp/vault/sdk/logical"
 )
 
@@ -2114,21 +2113,18 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 			"oci_runtime": {
 				Type:        framework.TypeString,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_oci-runtime"][0]),
-				Default:     pluginruntimeutil.DefaultOCIRuntime,
 			},
 			"cgroup_parent": {
 				Type:        framework.TypeString,
 				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
 			},
-			"cpu": {
+			"cpu_nanos": {
 				Type:        framework.TypeInt64,
-				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
-				Default:     pluginruntimeutil.DefaultCPU,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu-nanos"][0]),
 			},
-			"memory": {
+			"memory_bytes": {
 				Type:        framework.TypeInt64,
-				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory"][0]),
-				Default:     pluginruntimeutil.DefaultMemory,
+				Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory-bytes"][0]),
 			},
 		},
 
@@ -2189,14 +2185,14 @@ func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
 								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
 								Required:    true,
 							},
-							"cpu": {
+							"cpu_nanos": {
 								Type:        framework.TypeInt64,
-								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu"][0]),
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu-nanos"][0]),
 								Required:    true,
 							},
-							"memory": {
+							"memory_bytes": {
 								Type:        framework.TypeInt64,
-								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory"][0]),
+								Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory-bytes"][0]),
 								Required:    true,
 							},
 						},
diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go
index a235c2e5830e..3cbabf642d13 100644
--- a/vault/logical_system_test.go
+++ b/vault/logical_system_test.go
@@ -5903,8 +5903,8 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 	req.Data = map[string]interface{}{
 		"oci_runtime":   conf.OCIRuntime,
 		"cgroup_parent": conf.OCIRuntime,
-		"cpu":           conf.CPU,
-		"memory":        conf.Memory,
+		"cpu_nanos":     conf.CPU,
+		"memory_bytes":  conf.Memory,
 	}
 
 	resp, err := b.HandleRequest(namespace.RootContext(nil), req)
@@ -5943,8 +5943,8 @@ func TestSystemBackend_pluginRuntimeCRUD(t *testing.T) {
 		"name":          conf.Name,
 		"oci_runtime":   conf.OCIRuntime,
 		"cgroup_parent": conf.OCIRuntime,
-		"cpu":           conf.CPU,
-		"memory":        conf.Memory,
+		"cpu_nanos":     conf.CPU,
+		"memory_bytes":  conf.Memory,
 	}
 	if !reflect.DeepEqual(resp.Data, readExp) {
 		t.Fatalf("got: %#v expect: %#v", resp.Data, readExp)

From 47353fce1b22e67b98fb065285f04f74b2188ddd Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 10:47:37 -0700
Subject: [PATCH 18/20] add /sys/plugins/runtimes/catalog/{type}{name} to sudo
 paths

---
 api/sudo_paths.go      | 27 ++++++++++++++-------------
 api/sudo_paths_test.go |  5 +++++
 2 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/api/sudo_paths.go b/api/sudo_paths.go
index fb7113a0f3b3..020cc09ac0df 100644
--- a/api/sudo_paths.go
+++ b/api/sudo_paths.go
@@ -32,19 +32,20 @@ var sudoPaths = map[string]*regexp.Regexp{
 	// This entry is a bit wrong... sys/leases/lookup does NOT require sudo. But sys/leases/lookup/ with a trailing
 	// slash DOES require sudo. But the part of the Vault CLI that uses this logic doesn't pass operation-appropriate
 	// trailing slashes, it always strips them off, so we end up giving the wrong answer for one of these.
-	"/sys/leases/lookup/{prefix}":        regexp.MustCompile(`^/sys/leases/lookup(?:/.+)?$`),
-	"/sys/leases/revoke-force/{prefix}":  regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
-	"/sys/leases/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
-	"/sys/plugins/catalog/{name}":        regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
-	"/sys/plugins/catalog/{type}":        regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
-	"/sys/plugins/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
-	"/sys/raw/{path}":                    regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
-	"/sys/remount":                       regexp.MustCompile(`^/sys/remount$`),
-	"/sys/revoke-force/{prefix}":         regexp.MustCompile(`^/sys/revoke-force/.+$`),
-	"/sys/revoke-prefix/{prefix}":        regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
-	"/sys/rotate":                        regexp.MustCompile(`^/sys/rotate$`),
-	"/sys/seal":                          regexp.MustCompile(`^/sys/seal$`),
-	"/sys/step-down":                     regexp.MustCompile(`^/sys/step-down$`),
+	"/sys/leases/lookup/{prefix}":                regexp.MustCompile(`^/sys/leases/lookup(?:/.+)?$`),
+	"/sys/leases/revoke-force/{prefix}":          regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
+	"/sys/leases/revoke-prefix/{prefix}":         regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
+	"/sys/plugins/catalog/{name}":                regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
+	"/sys/plugins/catalog/{type}":                regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
+	"/sys/plugins/catalog/{type}/{name}":         regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
+	"/sys/plugins/runtimes/catalog/{type}{name}": regexp.MustCompile(`^/sys/plugins/runtimes/catalog/[\w-]+/[^/]+$`),
+	"/sys/raw/{path}":                            regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
+	"/sys/remount":                               regexp.MustCompile(`^/sys/remount$`),
+	"/sys/revoke-force/{prefix}":                 regexp.MustCompile(`^/sys/revoke-force/.+$`),
+	"/sys/revoke-prefix/{prefix}":                regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
+	"/sys/rotate":                                regexp.MustCompile(`^/sys/rotate$`),
+	"/sys/seal":                                  regexp.MustCompile(`^/sys/seal$`),
+	"/sys/step-down":                             regexp.MustCompile(`^/sys/step-down$`),
 
 	// enterprise-only paths
 	"/sys/replication/dr/primary/secondary-token":          regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),
diff --git a/api/sudo_paths_test.go b/api/sudo_paths_test.go
index 2e97d44cb5b9..b23af7067fc9 100644
--- a/api/sudo_paths_test.go
+++ b/api/sudo_paths_test.go
@@ -55,6 +55,11 @@ func TestIsSudoPath(t *testing.T) {
 			"/sys/plugins/catalog/some-type/some/name/with/slashes",
 			false,
 		},
+		// Testing: sys/plugins/runtimes/catalog/{type}/{name}
+		{
+			"/sys/plugins/runtimes/catalog/some-type/some-name",
+			true,
+		},
 		// Testing: auth/token/accessors (an example of a sudo path that only accepts list operations)
 		// It is matched as sudo without the trailing slash...
 		{

From bbb7c88b65abc40402f04e6c3b7f18bb0b851e32 Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 12:43:14 -0700
Subject: [PATCH 19/20] add /sys/plugins/runtimes/catalog to sudo paths

---
 api/sudo_paths.go | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/api/sudo_paths.go b/api/sudo_paths.go
index 020cc09ac0df..faffdfb0e853 100644
--- a/api/sudo_paths.go
+++ b/api/sudo_paths.go
@@ -32,20 +32,21 @@ var sudoPaths = map[string]*regexp.Regexp{
 	// This entry is a bit wrong... sys/leases/lookup does NOT require sudo. But sys/leases/lookup/ with a trailing
 	// slash DOES require sudo. But the part of the Vault CLI that uses this logic doesn't pass operation-appropriate
 	// trailing slashes, it always strips them off, so we end up giving the wrong answer for one of these.
-	"/sys/leases/lookup/{prefix}":                regexp.MustCompile(`^/sys/leases/lookup(?:/.+)?$`),
-	"/sys/leases/revoke-force/{prefix}":          regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
-	"/sys/leases/revoke-prefix/{prefix}":         regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
-	"/sys/plugins/catalog/{name}":                regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
-	"/sys/plugins/catalog/{type}":                regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
-	"/sys/plugins/catalog/{type}/{name}":         regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
-	"/sys/plugins/runtimes/catalog/{type}{name}": regexp.MustCompile(`^/sys/plugins/runtimes/catalog/[\w-]+/[^/]+$`),
-	"/sys/raw/{path}":                            regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
-	"/sys/remount":                               regexp.MustCompile(`^/sys/remount$`),
-	"/sys/revoke-force/{prefix}":                 regexp.MustCompile(`^/sys/revoke-force/.+$`),
-	"/sys/revoke-prefix/{prefix}":                regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
-	"/sys/rotate":                                regexp.MustCompile(`^/sys/rotate$`),
-	"/sys/seal":                                  regexp.MustCompile(`^/sys/seal$`),
-	"/sys/step-down":                             regexp.MustCompile(`^/sys/step-down$`),
+	"/sys/leases/lookup/{prefix}":                 regexp.MustCompile(`^/sys/leases/lookup(?:/.+)?$`),
+	"/sys/leases/revoke-force/{prefix}":           regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
+	"/sys/leases/revoke-prefix/{prefix}":          regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
+	"/sys/plugins/catalog/{name}":                 regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
+	"/sys/plugins/catalog/{type}":                 regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
+	"/sys/plugins/catalog/{type}/{name}":          regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
+	"/sys/plugins/runtimes/catalog":               regexp.MustCompile(`^/sys/plugins/runtimes/catalog/?$`),
+	"/sys/plugins/runtimes/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/runtimes/catalog/[\w-]+/[\w-]+$`),
+	"/sys/raw/{path}":                             regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
+	"/sys/remount":                                regexp.MustCompile(`^/sys/remount$`),
+	"/sys/revoke-force/{prefix}":                  regexp.MustCompile(`^/sys/revoke-force/.+$`),
+	"/sys/revoke-prefix/{prefix}":                 regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
+	"/sys/rotate":                                 regexp.MustCompile(`^/sys/rotate$`),
+	"/sys/seal":                                   regexp.MustCompile(`^/sys/seal$`),
+	"/sys/step-down":                              regexp.MustCompile(`^/sys/step-down$`),
 
 	// enterprise-only paths
 	"/sys/replication/dr/primary/secondary-token":          regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),

From c5185d385167b78ccd93ba5e4915db3566620bad Mon Sep 17 00:00:00 2001
From: Thy Ton <maithytonn@gmail.com>
Date: Thu, 31 Aug 2023 12:49:10 -0700
Subject: [PATCH 20/20] revert the regex change in
 /sys/plugins/runtimes/catalog/{type}/{name}

---
 api/sudo_paths.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/sudo_paths.go b/api/sudo_paths.go
index faffdfb0e853..24beb4bb1f2a 100644
--- a/api/sudo_paths.go
+++ b/api/sudo_paths.go
@@ -39,7 +39,7 @@ var sudoPaths = map[string]*regexp.Regexp{
 	"/sys/plugins/catalog/{type}":                 regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
 	"/sys/plugins/catalog/{type}/{name}":          regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
 	"/sys/plugins/runtimes/catalog":               regexp.MustCompile(`^/sys/plugins/runtimes/catalog/?$`),
-	"/sys/plugins/runtimes/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/runtimes/catalog/[\w-]+/[\w-]+$`),
+	"/sys/plugins/runtimes/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/runtimes/catalog/[\w-]+/[^/]+$`),
 	"/sys/raw/{path}":                             regexp.MustCompile(`^/sys/raw(?:/.+)?$`),
 	"/sys/remount":                                regexp.MustCompile(`^/sys/remount$`),
 	"/sys/revoke-force/{prefix}":                  regexp.MustCompile(`^/sys/revoke-force/.+$`),