Fix issue #211 + ns access msg

mine
derailed 2019-06-04 09:47:17 -06:00
parent 574c58e329
commit b66a25e5f8
11 changed files with 83 additions and 44 deletions

View File

@ -24,19 +24,23 @@ func NewMockConnection() *MockConnection {
return &MockConnection{fail: pegomock.GlobalFailHandler}
}
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{_param0, _param1, _param2, _param3}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0
return ret0, ret1
}
func (mock *MockConnection) Config() *k8s.Config {

View File

@ -64,7 +64,7 @@ type (
ServerVersion() (*version.Info, error)
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
CanIAccess(ns, name, resURL string, verbs []string) bool
CanIAccess(ns, name, resURL string, verbs []string) (bool, error)
}
// APIClient represents a Kubernetes api client.
@ -90,7 +90,7 @@ func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient {
}
// CanIAccess checks if user has access to a certain resource.
func (a *APIClient) CanIAccess(ns, name, resURL string, verbs []string) bool {
func (a *APIClient) CanIAccess(ns, name, resURL string, verbs []string) (bool, error) {
_, gr := schema.ParseResourceArg(strings.ToLower(resURL))
sar := &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
@ -104,24 +104,31 @@ func (a *APIClient) CanIAccess(ns, name, resURL string, verbs []string) bool {
},
}
var resp *authorizationv1.SelfSubjectAccessReview
var err error
var allow bool
user, _ := a.Config().CurrentUserName()
groups, _ := a.Config().CurrentGroupNames()
log.Debug().Msgf("AuthInfo user/groups: %s:%v", user, groups)
var (
resp *authorizationv1.SelfSubjectAccessReview
err error
allow bool
)
for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v
resp, err = a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews().Create(sar)
if err != nil {
log.Warn().Err(err).Msgf("CanIAccess")
return false
return false, err
}
log.Debug().Msgf("CHECKING ACCESS for %s/%s/ in NS %q verb: %s -> %t, %s", resURL, name, ns, v, resp.Status.Allowed, resp.Status.Reason)
log.Debug().Msgf("CHECKING ACCESS res:%s-%q for NS: %q Verb: %s -> %t, %s", resURL, name, ns, v, resp.Status.Allowed, resp.Status.Reason)
if !resp.Status.Allowed {
return false
return false, err
}
allow = true
}
log.Debug().Msgf("GRANT ACCESS:%t", allow)
return allow
log.Debug().Msgf("GRANT ACCESS? >>> %t", allow)
return allow, nil
}
// CurrentNamespaceName return namespace name set via either cli arg or cluster config.

View File

@ -163,6 +163,15 @@ func (c *Config) ClusterNames() ([]string, error) {
return cc, nil
}
// CurrentGroupNames retrieves the active group names.
func (c *Config) CurrentGroupNames() ([]string, error) {
if c.flags.ImpersonateGroup != nil && len(*c.flags.ImpersonateGroup) != 0 {
return *c.flags.ImpersonateGroup, nil
}
return []string{}, errors.New("unable to locate current group")
}
// CurrentUserName retrieves the active user name.
func (c *Config) CurrentUserName() (string, error) {
if isSet(c.flags.Impersonate) {
@ -186,7 +195,7 @@ func (c *Config) CurrentUserName() (string, error) {
return ctx.AuthInfo, nil
}
return "", errors.New("unable to locate current cluster")
return "", errors.New("unable to locate current user")
}
// CurrentNamespaceName retrieves the active namespace.
@ -199,12 +208,10 @@ func (c *Config) CurrentNamespaceName() (string, error) {
if err != nil {
return "", err
}
ctx, err := c.CurrentContextName()
if err != nil {
return "", err
}
if ctx, ok := cfg.Contexts[ctx]; ok {
if isSet(&ctx.Namespace) {
return ctx.Namespace, nil

View File

@ -24,19 +24,23 @@ func NewMockClusterMeta() *MockClusterMeta {
return &MockClusterMeta{fail: pegomock.GlobalFailHandler}
}
func (mock *MockClusterMeta) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
func (mock *MockClusterMeta) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
}
params := []pegomock.Param{_param0, _param1, _param2, _param3}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0
return ret0, ret1
}
func (mock *MockClusterMeta) ClusterName() string {

View File

@ -24,19 +24,23 @@ func NewMockConnection() *MockConnection {
return &MockConnection{fail: pegomock.GlobalFailHandler}
}
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{_param0, _param1, _param2, _param3}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0
return ret0, ret1
}
func (mock *MockConnection) Config() *k8s.Config {

View File

@ -24,19 +24,23 @@ func NewMockClusterMeta() *MockClusterMeta {
return &MockClusterMeta{fail: pegomock.GlobalFailHandler}
}
func (mock *MockClusterMeta) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
func (mock *MockClusterMeta) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
}
params := []pegomock.Param{_param0, _param1, _param2, _param3}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0
return ret0, ret1
}
func (mock *MockClusterMeta) ClusterName() string {

View File

@ -6,6 +6,7 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
const (
@ -36,7 +37,7 @@ func (v *namespaceView) extraActions(aa keyActions) {
}
func (v *namespaceView) switchNs(app *appView, _, res, sel string) {
v.useNamespace(sel)
v.useNamespace(v.cleanser(sel))
app.gotoResource("po", true)
}
@ -63,12 +64,13 @@ func (v *namespaceView) getSelectedItem() string {
}
func (*namespaceView) cleanser(s string) string {
log.Debug().Msgf("SWITCHING: %s-%s", s, nsCleanser.ReplaceAllString(s, `$1`))
return nsCleanser.ReplaceAllString(s, `$1`)
}
func (v *namespaceView) decorate(data resource.TableData) resource.TableData {
if _, ok := data.Rows[resource.AllNamespaces]; !ok {
if v.app.conn().CanIAccess("", "namespaces", "namespace.v1", []string{"list"}) {
if acc, err := v.app.conn().CanIAccess("", "namespaces", "namespace.v1", []string{"list"}); acc && err != nil {
data.Rows[resource.AllNamespace] = &resource.RowEvent{
Action: resource.Unchanged,
Fields: resource.Row{resource.AllNamespace, "Active", "0"},

View File

@ -97,7 +97,7 @@ func (v *resourceView) init(ctx context.Context, ns string) {
}
v.getTV().setColorer(colorer)
v.nsListAccess = v.app.conn().CanIAccess("", "namespaces", "namespace.v1", []string{"list"})
v.nsListAccess, _ = v.app.conn().CanIAccess("", "namespaces", "namespace.v1", []string{"list"})
if v.nsListAccess {
nn, err := k8s.NewNamespace(v.app.conn()).List(resource.AllNamespaces)
if err != nil {

View File

@ -62,17 +62,20 @@ func NewInformer(client k8s.Connection, ns string) *Informer {
log.Debug().Msgf(">> Starting Informer")
i := Informer{client: client, informers: map[string]StoreInformer{}}
nsAccess := i.client.CanIAccess("", "", "namespaces", []string{"list", "watch"})
ns, err := client.Config().CurrentNamespaceName()
// User did not lock NS. Check all ns access if not bail
if err != nil && !nsAccess {
log.Panic().Msg("Unauthorized: Unable to list ALL namespaces. Missing verbs ['list', 'watch']. Please specify a namespace or correct RBAC")
_, err := client.CanIAccess("", "", "namespaces", []string{"list", "watch"})
if err != nil && ns == AllNamespaces {
log.Panic().Msg("Unauthorized: All namespaces. Missing verbs ['list', 'watch']. Please specify a namespace or correct RBAC")
}
// Namespace is locked in. check if user has auth for this ns access.
if ns != AllNamespaces && !nsAccess {
if !i.client.CanIAccess("", ns, "namespaces", []string{"get", "watch"}) {
log.Panic().Msgf("Unauthorized: Access to namespace %q is missing verbs ['get', 'watch']", ns)
if ns != AllNamespaces {
acc, err := client.CanIAccess("", ns, "namespaces", []string{"get", "watch"})
if err != nil {
log.Panic().Err(err).Msgf("Failed access %s", ns)
}
if !acc {
user, _ := client.Config().CurrentUserName()
log.Panic().Msgf("Unauthorized: %s access to namespace %q is missing verbs ['get', 'watch']", user, ns)
}
i.init(ns)
} else {
@ -90,7 +93,7 @@ func (i *Informer) init(ns string) {
ContainerIndex: NewContainer(po),
}
if i.client.CanIAccess("", "", "nodes", []string{"list", "watch"}) {
if acc, err := i.client.CanIAccess("", "", "nodes", []string{"list", "watch"}); acc && err != nil {
i.informers[NodeIndex] = NewNode(i.client)
}
@ -98,7 +101,7 @@ func (i *Informer) init(ns string) {
return
}
if i.client.CanIAccess("", ns, "metrics.k8s.io", []string{"list", "watch"}) {
if acc, err := i.client.CanIAccess("", ns, "metrics.k8s.io", []string{"list", "watch"}); acc && err != nil {
i.informers[NodeMXIndex] = NewNodeMetrics(i.client)
i.informers[PodMXIndex] = NewPodMetrics(i.client, ns)
}

View File

@ -19,9 +19,9 @@ func TestInformerInitWithNS(t *testing.T) {
cmo := NewMockConnection()
m.When(cmo.Config()).ThenReturn(k8s.NewConfig(f))
m.When(cmo.HasMetrics()).ThenReturn(true)
m.When(cmo.CanIAccess("", "", "namespaces", []string{"list", "watch"})).ThenReturn(false)
m.When(cmo.CanIAccess("", ns, "namespaces", []string{"get", "watch"})).ThenReturn(true)
m.When(cmo.CanIAccess("", ns, "metrics.k8s.io", []string{"list", "watch"})).ThenReturn(true)
m.When(cmo.CanIAccess("", "", "namespaces", []string{"list", "watch"})).ThenReturn(false, nil)
m.When(cmo.CanIAccess("", ns, "namespaces", []string{"get", "watch"})).ThenReturn(true, nil)
m.When(cmo.CanIAccess("", ns, "metrics.k8s.io", []string{"list", "watch"})).ThenReturn(true, nil)
i := NewInformer(cmo, ns)
o, err := i.List(PodIndex, "fred", metav1.ListOptions{})

View File

@ -24,19 +24,23 @@ func NewMockConnection() *MockConnection {
return &MockConnection{fail: pegomock.GlobalFailHandler}
}
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{_param0, _param1, _param2, _param3}
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 bool
var ret1 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(bool)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0
return ret0, ret1
}
func (mock *MockConnection) Config() *k8s.Config {