mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
add plugin runtime API (#22469)
--------- Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
41
api/plugin_runtime_types.go
Normal file
41
api/plugin_runtime_types.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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{
|
||||
PluginRuntimeTypeUnsupported,
|
||||
PluginRuntimeTypeContainer,
|
||||
}
|
||||
|
||||
type PluginRuntimeType uint32
|
||||
|
||||
// This is a list of PluginRuntimeTypes used by Vault.
|
||||
const (
|
||||
PluginRuntimeTypeUnsupported PluginRuntimeType = iota
|
||||
PluginRuntimeTypeContainer
|
||||
)
|
||||
|
||||
func (r PluginRuntimeType) String() string {
|
||||
switch r {
|
||||
case PluginRuntimeTypeContainer:
|
||||
return "container"
|
||||
default:
|
||||
return "unsupported"
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error) {
|
||||
switch PluginRuntimeType {
|
||||
case "container":
|
||||
return PluginRuntimeTypeContainer, nil
|
||||
default:
|
||||
return PluginRuntimeTypeUnsupported, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
|
||||
}
|
||||
}
|
||||
@@ -32,19 +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/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-]+/[^/]+$`),
|
||||
"/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$`),
|
||||
|
||||
@@ -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...
|
||||
{
|
||||
|
||||
189
api/sys_plugins_runtimes.go
Normal file
189
api/sys_plugins_runtimes.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
CgroupParent string `json:"cgroup_parent"`
|
||||
CPU int64 `json:"cpu_nanos"`
|
||||
Memory int64 `json:"memory_bytes"`
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
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"`
|
||||
CgroupParent string `json:"cgroup_parent,omitempty"`
|
||||
CPU int64 `json:"cpu,omitempty"`
|
||||
Memory int64 `json:"memory,omitempty"`
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
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 removes the plugin with the given name from the plugin
|
||||
// catalog.
|
||||
func (c *Sys) DeregisterPluginRuntime(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
|
||||
}
|
||||
|
||||
type PluginRuntimeDetails struct {
|
||||
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_nanos" mapstructure:"cpu_nanos"`
|
||||
Memory int64 `json:"memory_bytes" mapstructure:"memory_bytes"`
|
||||
}
|
||||
|
||||
// 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, input *ListPluginRuntimesInput) (*ListPluginRuntimesResponse, error) {
|
||||
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
|
||||
defer cancelFunc()
|
||||
|
||||
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"))
|
||||
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")
|
||||
}
|
||||
if _, ok := secret.Data["runtimes"]; !ok {
|
||||
return nil, fmt.Errorf("data from server response does not contain runtimes")
|
||||
}
|
||||
|
||||
var runtimes []PluginRuntimeDetails
|
||||
if err = mapstructure.Decode(secret.Data["runtimes"], &runtimes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return all runtimes in the catalog
|
||||
if input == nil {
|
||||
return &ListPluginRuntimesResponse{Runtimes: runtimes}, nil
|
||||
}
|
||||
|
||||
result := &ListPluginRuntimesResponse{
|
||||
Runtimes: []PluginRuntimeDetails{},
|
||||
}
|
||||
for _, runtime := range runtimes {
|
||||
if runtime.Type == input.Type.String() {
|
||||
result.Runtimes = append(result.Runtimes, runtime)
|
||||
}
|
||||
}
|
||||
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/runtimes/catalog/%s/%s", runtimeType, name)
|
||||
}
|
||||
268
api/sys_plugins_runtimes_test.go
Normal file
268
api/sys_plugins_runtimes_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
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().RegisterPluginRuntime(context.Background(), &RegisterPluginRuntimeInput{
|
||||
Name: "gvisor",
|
||||
Type: PluginRuntimeTypeContainer,
|
||||
OCIRuntime: "runsc",
|
||||
CgroupParent: "/cpulimit/",
|
||||
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",
|
||||
CgroupParent: "/cpulimit/",
|
||||
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().GetPluginRuntime(context.Background(), &input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.expected, *info) {
|
||||
t.Errorf("expected: %#v\ngot: %#v", tc.expected, info)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPluginRuntimeTyped(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
runtimeType PluginRuntimeType
|
||||
body string
|
||||
expectedResponse *ListPluginRuntimesResponse
|
||||
expectedErrNil bool
|
||||
}{
|
||||
{
|
||||
runtimeType: PluginRuntimeTypeContainer,
|
||||
body: listPluginRuntimeTypedResponse,
|
||||
expectedResponse: &ListPluginRuntimesResponse{
|
||||
Runtimes: []PluginRuntimeDetails{
|
||||
{
|
||||
Type: "container",
|
||||
Name: "gvisor",
|
||||
OCIRuntime: "runsc",
|
||||
CgroupParent: "/cpulimit/",
|
||||
CPU: 1,
|
||||
Memory: 10000,
|
||||
},
|
||||
},
|
||||
},
|
||||
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().ListPluginRuntimes(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{
|
||||
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,
|
||||
},
|
||||
} {
|
||||
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().ListPluginRuntimes(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",
|
||||
"cgroup_parent": "/cpulimit/",
|
||||
"cpu_nanos": 1,
|
||||
"memory_bytes": 10000
|
||||
},
|
||||
"warnings": null,
|
||||
"auth": null
|
||||
}`
|
||||
|
||||
const listPluginRuntimeTypedResponse = `{
|
||||
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
|
||||
"data": {
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "gvisor",
|
||||
"type": "container",
|
||||
"oci_runtime": "runsc",
|
||||
"cgroup_parent": "/cpulimit/",
|
||||
"cpu_nanos": 1,
|
||||
"memory_bytes": 10000
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": null,
|
||||
"auth": null
|
||||
}
|
||||
`
|
||||
|
||||
const listPluginRuntimeUntypedResponse = `{
|
||||
"request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
|
||||
"data": {
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "gvisor",
|
||||
"type": "container",
|
||||
"oci_runtime": "runsc",
|
||||
"cgroup_parent": "/cpulimit/",
|
||||
"cpu_nanos": 1,
|
||||
"memory_bytes": 10000
|
||||
},
|
||||
{
|
||||
"name": "foo",
|
||||
"type": "container",
|
||||
"oci_runtime": "otherociruntime",
|
||||
"cgroup_parent": "/memorylimit/",
|
||||
"cpu_nanos": 2,
|
||||
"memory_bytes": 20000
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"type": "container",
|
||||
"oci_runtime": "otherociruntime",
|
||||
"cgroup_parent": "/cpulimit/",
|
||||
"cpu_nanos": 3,
|
||||
"memory_bytes": 30000
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": null,
|
||||
"auth": null
|
||||
}`
|
||||
41
sdk/helper/consts/plugin_runtime_types.go
Normal file
41
sdk/helper/consts/plugin_runtime_types.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package consts
|
||||
|
||||
// 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{
|
||||
PluginRuntimeTypeUnsupported,
|
||||
PluginRuntimeTypeContainer,
|
||||
}
|
||||
|
||||
type PluginRuntimeType uint32
|
||||
|
||||
// This is a list of PluginRuntimeTypes used by Vault.
|
||||
const (
|
||||
PluginRuntimeTypeUnsupported PluginRuntimeType = iota
|
||||
PluginRuntimeTypeContainer
|
||||
)
|
||||
|
||||
func (r PluginRuntimeType) String() string {
|
||||
switch r {
|
||||
case PluginRuntimeTypeContainer:
|
||||
return "container"
|
||||
default:
|
||||
return "unsupported"
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePluginRuntimeType(PluginRuntimeType string) (PluginRuntimeType, error) {
|
||||
switch PluginRuntimeType {
|
||||
case "container":
|
||||
return PluginRuntimeTypeContainer, nil
|
||||
default:
|
||||
return PluginRuntimeTypeUnsupported, fmt.Errorf("%q is not a supported plugin runtime type", PluginRuntimeType)
|
||||
}
|
||||
}
|
||||
16
sdk/helper/pluginruntimeutil/config.go
Normal file
16
sdk/helper/pluginruntimeutil/config.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package pluginruntimeutil
|
||||
|
||||
import "github.com/hashicorp/vault/sdk/helper/consts"
|
||||
|
||||
// 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"`
|
||||
CgroupParent string `json:"cgroup_parent" structs:"cgroup_parent"`
|
||||
CPU int64 `json:"cpu" structs:"cpu"`
|
||||
Memory int64 `json:"memory" structs:"memory"`
|
||||
}
|
||||
@@ -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
|
||||
@@ -2293,6 +2296,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
|
||||
}
|
||||
|
||||
@@ -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,8 @@ 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())
|
||||
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()...)
|
||||
@@ -733,6 +737,147 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
cgroupParent := d.Get("cgroup_parent").(string)
|
||||
cpu := d.Get("cpu_nanos").(int64)
|
||||
if cpu < 0 {
|
||||
return logical.ErrorResponse("runtime cpu in nanos cannot be negative"), nil
|
||||
}
|
||||
memory := d.Get("memory_bytes").(int64)
|
||||
if memory < 0 {
|
||||
return logical.ErrorResponse("runtime memory in bytes cannot be negative"), 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:
|
||||
logical.ErrorResponse(fmt.Sprintf("%s is not a supported plugin runtime type", runtimeTypeStr))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
conf, err := b.Core.pluginRuntimeCatalog.Get(ctx, runtimeName, runtimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logical.Response{Data: map[string]interface{}{
|
||||
"name": conf.Name,
|
||||
"type": conf.Type.String(),
|
||||
"oci_runtime": conf.OCIRuntime,
|
||||
"cgroup_parent": conf.CgroupParent,
|
||||
"cpu_nanos": conf.CPU,
|
||||
"memory_bytes": conf.Memory,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handlePluginRuntimeCatalogList(ctx context.Context, _ *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
var data []map[string]any
|
||||
for _, runtimeType := range consts.PluginRuntimeTypes {
|
||||
if runtimeType == consts.PluginRuntimeTypeUnsupported {
|
||||
continue
|
||||
}
|
||||
configs, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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]any{
|
||||
"name": conf.Name,
|
||||
"type": conf.Type.String(),
|
||||
"oci_runtime": conf.OCIRuntime,
|
||||
"cgroup_parent": conf.CgroupParent,
|
||||
"cpu_nanos": conf.CPU,
|
||||
"memory_bytes": conf.Memory,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
resp.Data["runtimes"] = 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)
|
||||
@@ -5955,6 +6100,51 @@ Each entry is of the form "key=value".`,
|
||||
"The semantic version of the plugin to use.",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog": {
|
||||
"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": {
|
||||
"The name of the plugin runtime",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_type": {
|
||||
"The type of the plugin runtime",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_oci-runtime": {
|
||||
"The OCI-compatible runtime (default \"runsc\")",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_cgroup-parent": {
|
||||
"Optional parent cgroup for the container",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_cpu-nanos": {
|
||||
"The limit of runtime CPU in nanos",
|
||||
"",
|
||||
},
|
||||
"plugin-runtime-catalog_memory-bytes": {
|
||||
"The limit of runtime memory in bytes",
|
||||
"",
|
||||
},
|
||||
"leases": {
|
||||
`View or list lease metadata.`,
|
||||
`
|
||||
|
||||
@@ -2093,6 +2093,155 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pluginsRuntimesCatalogCRUDPath() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "plugins/runtimes/catalog/(?P<type>container)/" + framework.GenericNameRegex("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]),
|
||||
},
|
||||
"cgroup_parent": {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
|
||||
},
|
||||
"cpu_nanos": {
|
||||
Type: framework.TypeInt64,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu-nanos"][0]),
|
||||
},
|
||||
"memory_bytes": {
|
||||
Type: framework.TypeInt64,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory-bytes"][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",
|
||||
}},
|
||||
},
|
||||
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,
|
||||
},
|
||||
"cgroup_parent": {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cgroup-parent"][0]),
|
||||
Required: true,
|
||||
},
|
||||
"cpu_nanos": {
|
||||
Type: framework.TypeInt64,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_cpu-nanos"][0]),
|
||||
Required: true,
|
||||
},
|
||||
"memory_bytes": {
|
||||
Type: framework.TypeInt64,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-runtime-catalog_memory-bytes"][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]),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pluginsRuntimesCatalogListPaths() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: "plugins/runtimes/catalog/?$",
|
||||
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "plugins-runtimes-catalog",
|
||||
OperationVerb: "list",
|
||||
OperationSuffix: "plugins-runtimes",
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ListOperation: &framework.PathOperation{
|
||||
Callback: b.handlePluginRuntimeCatalogList,
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"runtimes": {
|
||||
Type: framework.TypeSlice,
|
||||
Description: "List of all plugin runtimes 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{
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
@@ -5900,3 +5901,122 @@ 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, fmt.Sprintf("plugins/runtimes/catalog/%s/%s", conf.Type.String(), conf.Name))
|
||||
req.Data = map[string]interface{}{
|
||||
"oci_runtime": conf.OCIRuntime,
|
||||
"cgroup_parent": conf.OCIRuntime,
|
||||
"cpu_nanos": conf.CPU,
|
||||
"memory_bytes": conf.Memory,
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
readExp := map[string]any{
|
||||
"type": conf.Type.String(),
|
||||
"name": conf.Name,
|
||||
"oci_runtime": conf.OCIRuntime,
|
||||
"cgroup_parent": conf.OCIRuntime,
|
||||
"cpu_nanos": conf.CPU,
|
||||
"memory_bytes": conf.Memory,
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, readExp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, readExp)
|
||||
}
|
||||
|
||||
// List the plugin runtimes (untyped or all)
|
||||
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)
|
||||
}
|
||||
|
||||
listExp := map[string]interface{}{
|
||||
"runtimes": []map[string]any{readExp},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, listExp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, listExp)
|
||||
}
|
||||
|
||||
// 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.Fatal("expected a read error after the runtime was deleted")
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
// List the plugin runtimes (untyped or all)
|
||||
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)
|
||||
}
|
||||
|
||||
listExp = map[string]interface{}{}
|
||||
if !reflect.DeepEqual(resp.Data, listExp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, listExp)
|
||||
}
|
||||
}
|
||||
|
||||
140
vault/plugin_runtime_catalog.go
Normal file
140
vault/plugin_runtime_catalog.go
Normal file
@@ -0,0 +1,140 @@
|
||||
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 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()
|
||||
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.PluginRuntimeConfig)
|
||||
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, conf *pluginruntimeutil.PluginRuntimeConfig) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if conf == nil {
|
||||
return fmt.Errorf("plugin runtime config reference is nil")
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode plugin entry: %w", err)
|
||||
}
|
||||
|
||||
storageKey := path.Join(conf.Type.String(), conf.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
|
||||
}
|
||||
|
||||
return c.catalogView.Delete(ctx, storageKey)
|
||||
}
|
||||
|
||||
func (c *PluginRuntimeCatalog) List(ctx context.Context, prt consts.PluginRuntimeType) ([]*pluginruntimeutil.PluginRuntimeConfig, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
var retList []*pluginruntimeutil.PluginRuntimeConfig
|
||||
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
|
||||
}
|
||||
|
||||
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 conf.Type != prt {
|
||||
continue
|
||||
}
|
||||
|
||||
retList = append(retList, conf)
|
||||
}
|
||||
return retList, nil
|
||||
}
|
||||
82
vault/plugin_runtime_catalog_test.go
Normal file
82
vault/plugin_runtime_catalog_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/pluginruntimeutil"
|
||||
)
|
||||
|
||||
func TestPluginRuntimeCatalog_CRUD(t *testing.T) {
|
||||
core, _, _ := TestCoreUnsealed(t)
|
||||
ctx := context.Background()
|
||||
|
||||
expected := &pluginruntimeutil.PluginRuntimeConfig{
|
||||
Name: "gvisor",
|
||||
OCIRuntime: "runsc",
|
||||
CgroupParent: "/cpulimit/",
|
||||
CPU: 1,
|
||||
Memory: 10000,
|
||||
}
|
||||
|
||||
// Set new plugin runtime
|
||||
err := core.pluginRuntimeCatalog.Set(ctx, expected)
|
||||
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.CgroupParent = "memorylimit-cgroup"
|
||||
expected.CPU = 2
|
||||
expected.Memory = 5000
|
||||
err = core.pluginRuntimeCatalog.Set(ctx, expected)
|
||||
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)
|
||||
}
|
||||
|
||||
configs, err := core.pluginRuntimeCatalog.List(ctx, expected.Type)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(configs) != 1 {
|
||||
t.Fatalf("expected plugin runtime catalog to have 1 container runtime but got %d", len(configs))
|
||||
}
|
||||
|
||||
// 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
|
||||
configs, err = core.pluginRuntimeCatalog.List(ctx, expected.Type)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(configs) != 0 {
|
||||
t.Fatalf("expected plugin runtime catalog to have 0 container runtimes but got %d", len(configs))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user