mirror of
https://github.com/niusmallnan/steve.git
synced 2025-06-25 14:11:36 +00:00
Increase security around cluster shell
This commit is contained in:
parent
3eba71d06b
commit
445acdc240
@ -107,13 +107,8 @@ func (p *Factory) K8sInterface(ctx *types.APIRequest) (kubernetes.Interface, err
|
|||||||
return kubernetes.NewForConfig(cfg)
|
return kubernetes.NewForConfig(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Factory) AdminK8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error) {
|
func (p *Factory) AdminK8sInterface() (kubernetes.Interface, error) {
|
||||||
cfg, err := setupConfig(ctx, p.clientCfg, false)
|
return kubernetes.NewForConfig(p.clientCfg)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubernetes.NewForConfig(cfg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Factory) Client(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) {
|
func (p *Factory) Client(ctx *types.APIRequest, s *types.APISchema, namespace string) (dynamic.ResourceInterface, error) {
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package clusters
|
package clusters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
"github.com/rancher/steve/pkg/clustercache"
|
||||||
|
|
||||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||||
|
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -31,16 +34,21 @@ var (
|
|||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(schemas *types.APISchemas, cg proxy.ClientGetter) {
|
func Register(ctx context.Context, schemas *types.APISchemas, cg proxy.ClientGetter, cluster clustercache.ClusterCache) {
|
||||||
|
shell := &shell{
|
||||||
|
cg: cg,
|
||||||
|
namespace: "dashboard-shells",
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.OnAdd(ctx, shell.PurgeOldShell)
|
||||||
|
cluster.OnChange(ctx, func(gvr schema.GroupVersionResource, key string, obj, oldObj runtime.Object) error {
|
||||||
|
return shell.PurgeOldShell(gvr, key, obj)
|
||||||
|
})
|
||||||
schemas.MustImportAndCustomize(Cluster{}, func(schema *types.APISchema) {
|
schemas.MustImportAndCustomize(Cluster{}, func(schema *types.APISchema) {
|
||||||
schema.CollectionMethods = []string{http.MethodGet}
|
schema.CollectionMethods = []string{http.MethodGet}
|
||||||
schema.ResourceMethods = []string{http.MethodGet}
|
schema.ResourceMethods = []string{http.MethodGet}
|
||||||
schema.Store = &Store{}
|
schema.Store = &Store{}
|
||||||
|
|
||||||
shell := &shell{
|
|
||||||
cg: cg,
|
|
||||||
namespace: "dashboard-shells",
|
|
||||||
}
|
|
||||||
schema.LinkHandlers = map[string]http.Handler{
|
schema.LinkHandlers = map[string]http.Handler{
|
||||||
"shell": shell,
|
"shell": shell,
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
|
||||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||||
"github.com/rancher/wrangler/pkg/condition"
|
"github.com/rancher/wrangler/pkg/condition"
|
||||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||||
@ -15,7 +14,10 @@ import (
|
|||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
@ -25,11 +27,50 @@ import (
|
|||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
roleLabel = "shell.cattle.io/cluster-role"
|
||||||
|
)
|
||||||
|
|
||||||
type shell struct {
|
type shell struct {
|
||||||
namespace string
|
namespace string
|
||||||
cg proxy.ClientGetter
|
cg proxy.ClientGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *shell) PurgeOldShell(gvr schema.GroupVersionResource, key string, obj runtime.Object) error {
|
||||||
|
if obj == nil ||
|
||||||
|
gvr.Version != "v1" ||
|
||||||
|
gvr.Group != rbacv1.GroupName ||
|
||||||
|
gvr.Resource != "clusterroles" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
meta, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
// ignore error
|
||||||
|
logrus.Warnf("failed to find metadata for %v, %s", gvr, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.GetLabels()[roleLabel] != "true" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.GetCreationTimestamp().Add(time.Hour).Before(time.Now()) {
|
||||||
|
client, err := s.cg.AdminK8sInterface()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := meta.GetName()
|
||||||
|
go func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
client.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
ctx, user, client, err := s.contextAndClient(req)
|
ctx, user, client, err := s.contextAndClient(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -42,7 +83,12 @@ func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer client.RbacV1().ClusterRoles().Delete(ctx, role.Name, metav1.DeleteOptions{})
|
defer func() {
|
||||||
|
// Don't use request context as it already be canceled at this point
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
client.RbacV1().ClusterRoles().Delete(ctx, role.Name, metav1.DeleteOptions{})
|
||||||
|
}()
|
||||||
|
|
||||||
pod, err := s.createPod(ctx, user, role, client)
|
pod, err := s.createPod(ctx, user, role, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,7 +112,7 @@ func (s *shell) proxyRequest(rw http.ResponseWriter, req *http.Request, pod *v1.
|
|||||||
Stdout: false,
|
Stdout: false,
|
||||||
Stderr: false,
|
Stderr: false,
|
||||||
TTY: false,
|
TTY: false,
|
||||||
Container: "",
|
Container: "shell",
|
||||||
}, scheme.ParameterCodec).URL()
|
}, scheme.ParameterCodec).URL()
|
||||||
|
|
||||||
httpClient := client.CoreV1().RESTClient().(*rest.RESTClient).Client
|
httpClient := client.CoreV1().RESTClient().(*rest.RESTClient).Client
|
||||||
@ -86,9 +132,7 @@ func (s *shell) proxyRequest(rw http.ResponseWriter, req *http.Request, pod *v1.
|
|||||||
|
|
||||||
func (s *shell) contextAndClient(req *http.Request) (context.Context, user.Info, kubernetes.Interface, error) {
|
func (s *shell) contextAndClient(req *http.Request) (context.Context, user.Info, kubernetes.Interface, error) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
apiContext := types.GetAPIContext(req.Context())
|
client, err := s.cg.AdminK8sInterface()
|
||||||
|
|
||||||
client, err := s.cg.AdminK8sInterface(apiContext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, nil, nil, err
|
return ctx, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -122,6 +166,9 @@ func (s *shell) createRole(ctx context.Context, user user.Info, client kubernete
|
|||||||
return client.RbacV1().ClusterRoles().Create(ctx, &rbacv1.ClusterRole{
|
return client.RbacV1().ClusterRoles().Create(ctx, &rbacv1.ClusterRole{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: "dashboard-shell-",
|
GenerateName: "dashboard-shell-",
|
||||||
|
Labels: map[string]string{
|
||||||
|
roleLabel: "true",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Rules: []rbacv1.PolicyRule{
|
Rules: []rbacv1.PolicyRule{
|
||||||
{
|
{
|
||||||
@ -260,19 +307,16 @@ func (s *shell) createPod(ctx context.Context, user user.Info, role *rbacv1.Clus
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hour := int64(15)
|
zero := int64(0)
|
||||||
|
t := true
|
||||||
pod, err := client.CoreV1().Pods(s.namespace).Create(ctx, &v1.Pod{
|
pod, err := client.CoreV1().Pods(s.namespace).Create(ctx, &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: "dashboard-shell-",
|
GenerateName: "dashboard-shell-",
|
||||||
Namespace: s.namespace,
|
Namespace: s.namespace,
|
||||||
Labels: map[string]string{
|
|
||||||
"clusterrolename": role.Name,
|
|
||||||
"clusterroleuid": string(role.UID),
|
|
||||||
},
|
|
||||||
OwnerReferences: ref(role),
|
OwnerReferences: ref(role),
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
ActiveDeadlineSeconds: &hour,
|
TerminationGracePeriodSeconds: &zero,
|
||||||
Volumes: []v1.Volume{
|
Volumes: []v1.Volume{
|
||||||
{
|
{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
@ -289,13 +333,21 @@ func (s *shell) createPod(ctx context.Context, user user.Info, role *rbacv1.Clus
|
|||||||
ServiceAccountName: sa.Name,
|
ServiceAccountName: sa.Name,
|
||||||
Containers: []v1.Container{
|
Containers: []v1.Container{
|
||||||
{
|
{
|
||||||
Name: "shell",
|
Name: "proxy",
|
||||||
TTY: true,
|
Image: "ibuildthecloud/shell:v0.0.1",
|
||||||
Stdin: true,
|
|
||||||
StdinOnce: true,
|
|
||||||
Image: "rancher/rancher-agent:v2.4.3",
|
|
||||||
ImagePullPolicy: v1.PullIfNotPresent,
|
ImagePullPolicy: v1.PullIfNotPresent,
|
||||||
Command: []string{"bash"},
|
Env: []v1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: "KUBECONFIG",
|
||||||
|
Value: "/root/.kube/config",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Command: []string{"kubectl", "proxy"},
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: &zero,
|
||||||
|
RunAsGroup: &zero,
|
||||||
|
ReadOnlyRootFilesystem: &t,
|
||||||
|
},
|
||||||
VolumeMounts: []v1.VolumeMount{
|
VolumeMounts: []v1.VolumeMount{
|
||||||
{
|
{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
@ -305,6 +357,15 @@ func (s *shell) createPod(ctx context.Context, user user.Info, role *rbacv1.Clus
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "shell",
|
||||||
|
TTY: true,
|
||||||
|
Stdin: true,
|
||||||
|
StdinOnce: true,
|
||||||
|
Image: "ibuildthecloud/shell:v0.0.1",
|
||||||
|
ImagePullPolicy: v1.PullIfNotPresent,
|
||||||
|
Command: []string{"bash"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, metav1.CreateOptions{})
|
}, metav1.CreateOptions{})
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package resources
|
package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/rancher/steve/pkg/accesscontrol"
|
"github.com/rancher/steve/pkg/accesscontrol"
|
||||||
"github.com/rancher/steve/pkg/client"
|
"github.com/rancher/steve/pkg/client"
|
||||||
"github.com/rancher/steve/pkg/clustercache"
|
"github.com/rancher/steve/pkg/clustercache"
|
||||||
@ -17,12 +19,12 @@ import (
|
|||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultSchemas(baseSchema *types.APISchemas, ccache clustercache.ClusterCache, cg proxy.ClientGetter) *types.APISchemas {
|
func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache clustercache.ClusterCache, cg proxy.ClientGetter) *types.APISchemas {
|
||||||
counts.Register(baseSchema, ccache)
|
counts.Register(baseSchema, ccache)
|
||||||
subscribe.Register(baseSchema)
|
subscribe.Register(baseSchema)
|
||||||
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
||||||
userpreferences.Register(baseSchema, cg)
|
userpreferences.Register(baseSchema, cg)
|
||||||
clusters.Register(baseSchema, cg)
|
clusters.Register(ctx, baseSchema, cg, ccache)
|
||||||
return baseSchema
|
return baseSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func setup(ctx context.Context, server *Server) (http.Handler, *schema.Collectio
|
|||||||
|
|
||||||
ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient())
|
ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient())
|
||||||
|
|
||||||
server.BaseSchemas = resources.DefaultSchemas(server.BaseSchemas, ccache, cf)
|
server.BaseSchemas = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, cf)
|
||||||
server.SchemaTemplates = append(server.SchemaTemplates, resources.DefaultSchemaTemplates(cf, asl, server.K8s.Discovery())...)
|
server.SchemaTemplates = append(server.SchemaTemplates, resources.DefaultSchemaTemplates(cf, asl, server.K8s.Discovery())...)
|
||||||
|
|
||||||
cols, err := common.NewDynamicColumns(server.RestConfig)
|
cols, err := common.NewDynamicColumns(server.RestConfig)
|
||||||
|
@ -34,7 +34,7 @@ var (
|
|||||||
type ClientGetter interface {
|
type ClientGetter interface {
|
||||||
IsImpersonating() bool
|
IsImpersonating() bool
|
||||||
K8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error)
|
K8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error)
|
||||||
AdminK8sInterface(ctx *types.APIRequest) (kubernetes.Interface, error)
|
AdminK8sInterface() (kubernetes.Interface, error)
|
||||||
Client(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
Client(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
||||||
AdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
AdminClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
||||||
TableClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
TableClient(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
||||||
|
Loading…
Reference in New Issue
Block a user