Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add default role and permission #24

Merged
merged 6 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 47 additions & 10 deletions cmd/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package app

import (
"fmt"
"os"
"strconv"

Expand All @@ -14,11 +15,10 @@ import (
"github.com/Improwised/kube-oidc-proxy/cmd/app/options"
"github.com/Improwised/kube-oidc-proxy/pkg/probe"
"github.com/Improwised/kube-oidc-proxy/pkg/proxy"
"github.com/Improwised/kube-oidc-proxy/pkg/proxy/rbac"
"github.com/Improwised/kube-oidc-proxy/pkg/proxy/subjectaccessreview"
"github.com/Improwised/kube-oidc-proxy/pkg/proxy/tokenreview"
"github.com/Improwised/kube-oidc-proxy/pkg/util"

rbacvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)

func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
Expand Down Expand Up @@ -55,17 +55,27 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
return err
}

// check if the cluster role config file exists
if _, err := os.Stat(opts.App.Cluster.RoleConfig); err != nil {
// Validate cluster config
if err := clusterConfigValidation(clustersConfig); err != nil {
return err
}

clustersRoleConfigMap, err := util.LoadRBACConfig(opts.App.Cluster.RoleConfig)
if err != nil {
return err
var clustersRoleConfigMap map[string]util.RBAC
if opts.App.Cluster.RoleConfig != "" {
// check if the cluster role config file exists
if _, err := os.Stat(opts.App.Cluster.RoleConfig); err != nil {
return err
}

clustersRoleConfigMap, err = util.LoadRBACConfig(opts.App.Cluster.RoleConfig)
if err != nil {
return err
}
}

isRBACLoded := make(map[string]bool)
for _, cluster := range clustersConfig {
isRBACLoded[cluster.Name] = false
ConfigFlags := &genericclioptions.ConfigFlags{
KubeConfig: &cluster.Path,
}
Expand Down Expand Up @@ -118,20 +128,32 @@ func buildRunCommand(stopCh <-chan struct{}, opts *options.Options) *cobra.Comma
}

subectAccessReviewer, err := subjectaccessreview.New(kubeclient.AuthorizationV1().SubjectAccessReviews())
kubeclient.AuthorizationV1().RESTClient()
if err != nil {
return err
}

cluster.Kubeclient = kubeclient
cluster.SubjectAccessReviewer = subectAccessReviewer

}

for clusterName, RBACConfig := range clustersRoleConfigMap {
for _, cluster := range clustersConfig {
if cluster.Name == clusterName {
_, StaticRoles := rbacvalidation.NewTestRuleResolver(RBACConfig.Roles, RBACConfig.RoleBindings, RBACConfig.ClusterRoles, RBACConfig.ClusterRoleBindings)
cluster.Authorizer = util.NewAuthorizer(StaticRoles)
isRBACLoded[cluster.Name] = true
err := rbac.LoadRBAC(RBACConfig, cluster)
if err != nil {
return err
}
}
}
}

for _, cluster := range clustersConfig {
if !isRBACLoded[cluster.Name] {
err := rbac.LoadRBAC(util.RBAC{}, cluster)
if err != nil {
return err
}
}
}
Expand Down Expand Up @@ -200,3 +222,18 @@ func LoadClusterConfig(path string) ([]*proxy.ClusterConfig, error) {
}
return clusterList, nil
}

func clusterConfigValidation(clusterConfig []*proxy.ClusterConfig) error {
// check if the cluster name is not empty and unique
clusterNames := make(map[string]bool)
for _, cluster := range clusterConfig {
if cluster.Name == "" {
return fmt.Errorf("cluster name is empty")
}
if _, ok := clusterNames[cluster.Name]; ok {
return fmt.Errorf("cluster name %s is repeated", cluster.Name)
}
clusterNames[cluster.Name] = true
}
return nil
}
2 changes: 2 additions & 0 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -59,6 +60,7 @@ type ClusterConfig struct {
Name string
Path string
RestConfig *rest.Config
Kubeclient *kubernetes.Clientset
proxyHandler *httputil.ReverseProxy
TokenReviewer *tokenreview.TokenReview
SubjectAccessReviewer *subjectaccessreview.SubjectAccessReview
Expand Down
149 changes: 149 additions & 0 deletions pkg/proxy/rbac/rbac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package rbac

import (
"context"
"fmt"

"github.com/Improwised/kube-oidc-proxy/pkg/proxy"
"github.com/Improwised/kube-oidc-proxy/pkg/util"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/rbac/v1"
apisv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
rbacvalidation "k8s.io/kubernetes/pkg/registry/rbac/validation"
)

var defalutRole = map[string]v1.PolicyRule{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chintansakhiya We should mention those role in documentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will create a new PR for Doc.

"devops": {
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
"developer": {
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods", "pods/log", "pods/exec"},
},
"developer-portforward": {
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods", "pods/log", "pods/exec", "pods/portforward"},
},
"watcher": {
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
}

func LoadRBAC(RBACConfig util.RBAC, cluster *proxy.ClusterConfig) error {
// Watch for namespace
// if namespace is created then create role and rolebinding
watchNamespace, err := cluster.Kubeclient.CoreV1().Namespaces().Watch(context.Background(), apisv1.ListOptions{Watch: true})
if err != nil {
return err
}

for roleName, role := range defalutRole {
// Create ClusterRole
RBACConfig.ClusterRoles = append(RBACConfig.ClusterRoles, &v1.ClusterRole{
TypeMeta: apisv1.TypeMeta{
Kind: "ClusterRole",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: apisv1.ObjectMeta{
Name: fmt.Sprintf("%s:%s", cluster.Name, roleName),
},
Rules: []v1.PolicyRule{role},
})
// Create ClusterRoleBinding
RBACConfig.ClusterRoleBindings = append(RBACConfig.ClusterRoleBindings, &v1.ClusterRoleBinding{
TypeMeta: apisv1.TypeMeta{
Kind: "ClusterRoleBinding",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: apisv1.ObjectMeta{
Name: fmt.Sprintf("%s:%s", cluster.Name, roleName),
},
Subjects: []v1.Subject{
{
Kind: "Group",
Name: fmt.Sprintf("%s:%s", cluster.Name, roleName),
APIGroup: "rbac.authorization.k8s.io",
},
},
RoleRef: v1.RoleRef{
Kind: "ClusterRole",
Name: fmt.Sprintf("%s:%s", cluster.Name, roleName),
},
})
}

go func() {
for e := range watchNamespace.ResultChan() {
switch e.Type {
// If namespace is created then create role and rolebinding
case watch.Added:
for role, policy := range defalutRole {
// Create Role
RBACConfig.Roles = append(RBACConfig.Roles, &v1.Role{
TypeMeta: apisv1.TypeMeta{
Kind: "Role",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: apisv1.ObjectMeta{
Namespace: e.Object.(*corev1.Namespace).Name,
Name: fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name),
},
Rules: []v1.PolicyRule{policy},
})
// Create RoleBinding
RBACConfig.RoleBindings = append(RBACConfig.RoleBindings, &v1.RoleBinding{
TypeMeta: apisv1.TypeMeta{
Kind: "RoleBinding",
APIVersion: "rbac.authorization.k8s.io/v1",
},
ObjectMeta: apisv1.ObjectMeta{
Namespace: e.Object.(*corev1.Namespace).Name,
Name: fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name),
},
Subjects: []v1.Subject{
{
Kind: "Group",
Name: fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name),
APIGroup: "rbac.authorization.k8s.io",
},
},
RoleRef: v1.RoleRef{
Kind: "Role",
Name: fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name),
},
})
}
// If namespace is deleted then delete role and rolebinding
case watch.Deleted:
for role := range defalutRole {
// Delete Role
for i, r := range RBACConfig.Roles {
if r.Name == fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name) {
RBACConfig.Roles = append(RBACConfig.Roles[:i], RBACConfig.Roles[i+1:]...)
}
}
// Delete RoleBinding
for i, r := range RBACConfig.RoleBindings {
if r.Name == fmt.Sprintf("%s:%s:%s", cluster.Name, role, e.Object.(*corev1.Namespace).Name) {
RBACConfig.RoleBindings = append(RBACConfig.RoleBindings[:i], RBACConfig.RoleBindings[i+1:]...)
}
}
}
case watch.Modified:
continue

}
_, StaticRoles := rbacvalidation.NewTestRuleResolver(RBACConfig.Roles, RBACConfig.RoleBindings, RBACConfig.ClusterRoles, RBACConfig.ClusterRoleBindings)
cluster.Authorizer = util.NewAuthorizer(StaticRoles)

}
}()
return nil
}