299 lines
6.5 KiB
Go
299 lines
6.5 KiB
Go
package views
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/derailed/k9s/internal/resource"
|
|
"github.com/derailed/k9s/internal/ui"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rs/zerolog/log"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
const policyTitle = "Policy"
|
|
|
|
var policyHeader = append(resource.Row{"NAMESPACE", "NAME", "API GROUP", "BINDING"}, rbacHeaderVerbs...)
|
|
|
|
type (
|
|
namespacedRole struct {
|
|
ns, role string
|
|
}
|
|
|
|
policyView struct {
|
|
*tableView
|
|
|
|
current ui.Igniter
|
|
cancel context.CancelFunc
|
|
subjectKind string
|
|
subjectName string
|
|
cache resource.RowEvents
|
|
}
|
|
)
|
|
|
|
func newPolicyView(app *appView, subject, name string) *policyView {
|
|
v := policyView{}
|
|
{
|
|
v.subjectKind, v.subjectName = mapSubject(subject), name
|
|
v.tableView = newTableView(app, v.getTitle())
|
|
v.SetColorerFn(rbacColorer)
|
|
v.current = app.Frame().GetPrimitive("main").(ui.Igniter)
|
|
v.bindKeys()
|
|
}
|
|
|
|
return &v
|
|
}
|
|
|
|
// Init the view.
|
|
func (v *policyView) Init(c context.Context, ns string) {
|
|
v.SetSortCol(1, len(rbacHeader), false)
|
|
|
|
ctx, cancel := context.WithCancel(c)
|
|
v.cancel = cancel
|
|
go func(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(time.Duration(v.app.Config.K9s.GetRefreshRate()) * time.Second):
|
|
v.refresh()
|
|
v.app.Draw()
|
|
}
|
|
}
|
|
}(ctx)
|
|
|
|
v.refresh()
|
|
v.SelectRow(1, true)
|
|
v.app.SetFocus(v)
|
|
}
|
|
|
|
func (v *policyView) bindKeys() {
|
|
v.RmAction(ui.KeyShiftA)
|
|
|
|
v.SetActions(ui.KeyActions{
|
|
tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false),
|
|
ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false),
|
|
ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false),
|
|
ui.KeyShiftS: ui.NewKeyAction("Sort Namespace", v.SortColCmd(0), false),
|
|
ui.KeyShiftN: ui.NewKeyAction("Sort Name", v.SortColCmd(1), false),
|
|
ui.KeyShiftO: ui.NewKeyAction("Sort Group", v.SortColCmd(2), false),
|
|
ui.KeyShiftB: ui.NewKeyAction("Sort Binding", v.SortColCmd(3), false),
|
|
})
|
|
}
|
|
|
|
func (v *policyView) getTitle() string {
|
|
return fmt.Sprintf(rbacTitleFmt, policyTitle, v.subjectKind+":"+v.subjectName)
|
|
}
|
|
|
|
func (v *policyView) refresh() {
|
|
data, err := v.reconcile()
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("Refresh for %s:%s", v.subjectKind, v.subjectName)
|
|
v.app.Flash().Err(err)
|
|
}
|
|
v.Update(data)
|
|
}
|
|
|
|
func (v *policyView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !v.SearchBuff().Empty() {
|
|
v.SearchBuff().Reset()
|
|
return nil
|
|
}
|
|
|
|
return v.backCmd(evt)
|
|
}
|
|
|
|
func (v *policyView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if v.cancel != nil {
|
|
v.cancel()
|
|
}
|
|
|
|
if v.SearchBuff().IsActive() {
|
|
v.SearchBuff().Reset()
|
|
return nil
|
|
}
|
|
|
|
v.app.inject(v.current)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *policyView) Hints() ui.Hints {
|
|
return v.Hints()
|
|
}
|
|
|
|
func (v *policyView) reconcile() (resource.TableData, error) {
|
|
var table resource.TableData
|
|
|
|
evts, errs := v.clusterPolicies()
|
|
if len(errs) > 0 {
|
|
for _, err := range errs {
|
|
log.Error().Err(err).Msg("Unable to find cluster policies")
|
|
}
|
|
return table, errs[0]
|
|
}
|
|
|
|
nevts, errs := v.namespacedPolicies()
|
|
if len(errs) > 0 {
|
|
for _, err := range errs {
|
|
log.Error().Err(err).Msg("Unable to find cluster policies")
|
|
}
|
|
return table, errs[0]
|
|
}
|
|
|
|
for k, v := range nevts {
|
|
evts[k] = v
|
|
}
|
|
|
|
return buildTable(v, evts), nil
|
|
}
|
|
|
|
// Protocol...
|
|
|
|
func (v *policyView) header() resource.Row {
|
|
return policyHeader
|
|
}
|
|
|
|
func (v *policyView) getCache() resource.RowEvents {
|
|
return v.cache
|
|
}
|
|
|
|
func (v *policyView) setCache(evts resource.RowEvents) {
|
|
v.cache = evts
|
|
}
|
|
|
|
func (v *policyView) clusterPolicies() (resource.RowEvents, []error) {
|
|
var errs []error
|
|
evts := make(resource.RowEvents)
|
|
|
|
crbs, err := v.app.Conn().DialOrDie().RbacV1().ClusterRoleBindings().List(metav1.ListOptions{})
|
|
if err != nil {
|
|
return evts, errs
|
|
}
|
|
|
|
var rr []string
|
|
for _, crb := range crbs.Items {
|
|
for _, s := range crb.Subjects {
|
|
if s.Kind == v.subjectKind && s.Name == v.subjectName {
|
|
rr = append(rr, crb.RoleRef.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, r := range rr {
|
|
role, err := v.app.Conn().DialOrDie().RbacV1().ClusterRoles().Get(r, metav1.GetOptions{})
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
for k, v := range v.parseRules("*", "CR:"+r, role.Rules) {
|
|
evts[k] = v
|
|
}
|
|
}
|
|
|
|
return evts, errs
|
|
}
|
|
|
|
func (v policyView) loadRoleBindings() ([]namespacedRole, error) {
|
|
var rr []namespacedRole
|
|
|
|
dial := v.app.Conn().DialOrDie().RbacV1()
|
|
rbs, err := dial.RoleBindings("").List(metav1.ListOptions{})
|
|
if err != nil {
|
|
return rr, err
|
|
}
|
|
|
|
for _, rb := range rbs.Items {
|
|
for _, s := range rb.Subjects {
|
|
if s.Kind == v.subjectKind && s.Name == v.subjectName {
|
|
rr = append(rr, namespacedRole{rb.Namespace, rb.RoleRef.Name})
|
|
}
|
|
}
|
|
}
|
|
|
|
return rr, nil
|
|
}
|
|
|
|
func (v *policyView) loadRoles(errs []error, rr []namespacedRole) (resource.RowEvents, []error) {
|
|
var (
|
|
dial = v.app.Conn().DialOrDie().RbacV1()
|
|
evts = make(resource.RowEvents)
|
|
)
|
|
for _, r := range rr {
|
|
if cr, err := dial.Roles(r.ns).Get(r.role, metav1.GetOptions{}); err != nil {
|
|
errs = append(errs, err)
|
|
} else {
|
|
for k, v := range v.parseRules(r.ns, "RO:"+r.role, cr.Rules) {
|
|
evts[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
return evts, errs
|
|
}
|
|
|
|
func (v *policyView) namespacedPolicies() (resource.RowEvents, []error) {
|
|
var errs []error
|
|
rr, err := v.loadRoleBindings()
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
evts, errs := v.loadRoles(errs, rr)
|
|
return evts, errs
|
|
}
|
|
|
|
func (v *policyView) parseRules(ns, binding string, rules []rbacv1.PolicyRule) resource.RowEvents {
|
|
m := make(resource.RowEvents, len(rules))
|
|
for _, r := range rules {
|
|
for _, grp := range r.APIGroups {
|
|
for _, res := range r.Resources {
|
|
k := res
|
|
if grp != "" {
|
|
k = res + "." + grp
|
|
}
|
|
for _, na := range r.ResourceNames {
|
|
n := fqn(k, na)
|
|
m[fqn(ns, n)] = &resource.RowEvent{
|
|
Fields: append(policyRow(ns, n, grp, binding), asVerbs(r.Verbs...)...),
|
|
}
|
|
}
|
|
m[fqn(ns, k)] = &resource.RowEvent{
|
|
Fields: append(policyRow(ns, k, grp, binding), asVerbs(r.Verbs...)...),
|
|
}
|
|
}
|
|
}
|
|
for _, nres := range r.NonResourceURLs {
|
|
if nres[0] != '/' {
|
|
nres = "/" + nres
|
|
}
|
|
m[fqn(ns, nres)] = &resource.RowEvent{
|
|
Fields: append(policyRow(ns, nres, resource.NAValue, binding), asVerbs(r.Verbs...)...),
|
|
}
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func policyRow(ns, res, grp, binding string) resource.Row {
|
|
if grp != resource.NAValue {
|
|
grp = toGroup(grp)
|
|
}
|
|
|
|
r := make(resource.Row, 0, len(policyHeader))
|
|
return append(r, ns, res, grp, binding)
|
|
}
|
|
|
|
func mapSubject(subject string) string {
|
|
switch subject {
|
|
case "g":
|
|
return "Group"
|
|
case "s":
|
|
return "ServiceAccount"
|
|
default:
|
|
return "User"
|
|
}
|
|
}
|