#200 Allow for additional RBAC role bindings for Jenkins master
This commit is contained in:
parent
9b5672ebcd
commit
f9335df74c
|
|
@ -2,6 +2,7 @@ package v1alpha2
|
|||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
|
@ -52,6 +53,10 @@ type JenkinsSpec struct {
|
|||
// ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin
|
||||
// +optional
|
||||
ConfigurationAsCode ConfigurationAsCode `json:"configurationAsCode,omitempty"`
|
||||
|
||||
// Roles defines list of extra RBAC roles for the Jenkins Master pod service account
|
||||
// +optional
|
||||
Roles []rbacv1.RoleRef `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationLevel defines the level of a Notification
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package v1alpha2
|
|||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
|
@ -369,6 +370,11 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) {
|
|||
in.Restore.DeepCopyInto(&out.Restore)
|
||||
in.GroovyScripts.DeepCopyInto(&out.GroovyScripts)
|
||||
in.ConfigurationAsCode.DeepCopyInto(&out.ConfigurationAsCode)
|
||||
if in.Roles != nil {
|
||||
in, out := &in.Roles, &out.Roles
|
||||
*out = make([]rbacv1.RoleRef, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/go-logr/logr"
|
||||
stackerr "github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
|
@ -180,6 +181,11 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod
|
|||
}
|
||||
r.logger.V(log.VDebug).Info("Service account, role and role binding are present")
|
||||
|
||||
if err := r.ensureExtraRBAC(metaObject); err != nil {
|
||||
return err
|
||||
}
|
||||
r.logger.V(log.VDebug).Info("Extra role bindings are present")
|
||||
|
||||
if err := r.createService(metaObject, resources.GetJenkinsHTTPServiceName(r.Configuration.Jenkins), r.Configuration.Jenkins.Spec.Service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -347,7 +353,11 @@ func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) e
|
|||
return stackerr.WithStack(err)
|
||||
}
|
||||
|
||||
roleBinding := resources.NewRoleBinding(meta)
|
||||
roleBinding := resources.NewRoleBinding(meta.Name, meta.Namespace, meta.Name, rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: meta.Name,
|
||||
})
|
||||
err = r.CreateOrUpdateResource(roleBinding)
|
||||
if err != nil {
|
||||
return stackerr.WithStack(err)
|
||||
|
|
@ -356,6 +366,58 @@ func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) ensureExtraRBAC(meta metav1.ObjectMeta) error {
|
||||
var err error
|
||||
var name string
|
||||
for _, roleRef := range r.Configuration.Jenkins.Spec.Roles {
|
||||
name = getExtraRoleBindingName(meta.Name, roleRef)
|
||||
roleBinding := resources.NewRoleBinding(name, meta.Namespace, meta.Name, roleRef)
|
||||
err = r.CreateOrUpdateResource(roleBinding)
|
||||
if err != nil {
|
||||
return stackerr.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
roleBindings := &rbacv1.RoleBindingList{}
|
||||
err = r.Client.List(context.TODO(), &client.ListOptions{Namespace: r.Configuration.Jenkins.Namespace}, roleBindings)
|
||||
if err != nil {
|
||||
return stackerr.WithStack(err)
|
||||
}
|
||||
for _, roleBinding := range roleBindings.Items {
|
||||
if !strings.HasPrefix(roleBinding.Name, getExtraRoleBindingName(meta.Name, rbacv1.RoleRef{Kind: "Role"})) &&
|
||||
!strings.HasPrefix(roleBinding.Name, getExtraRoleBindingName(meta.Name, rbacv1.RoleRef{Kind: "ClusterRole"})) {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, roleRef := range r.Configuration.Jenkins.Spec.Roles {
|
||||
name = getExtraRoleBindingName(meta.Name, roleRef)
|
||||
if roleBinding.Name == name {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
r.logger.Info(fmt.Sprintf("Deleting RoleBinding '%s'", roleBinding.Name))
|
||||
if err = r.Client.Delete(context.TODO(), &roleBinding); err != nil {
|
||||
return stackerr.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExtraRoleBindingName(serviceAccountName string, roleRef rbacv1.RoleRef) string {
|
||||
var typeName string
|
||||
if roleRef.Kind == "ClusterRole" {
|
||||
typeName = "cr"
|
||||
} else {
|
||||
typeName = "r"
|
||||
}
|
||||
return fmt.Sprintf("%s-%s-%s", serviceAccountName, typeName, roleRef.Name)
|
||||
}
|
||||
|
||||
func (r *ReconcileJenkinsBaseConfiguration) createService(meta metav1.ObjectMeta, name string, config v1alpha2.Service) error {
|
||||
service := corev1.Service{}
|
||||
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: meta.Namespace}, &service)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||
|
|
@ -13,6 +14,11 @@ import (
|
|||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
func TestGetJenkinsOpts(t *testing.T) {
|
||||
|
|
@ -733,3 +739,184 @@ func TestCompareImagePullSecrets(t *testing.T) {
|
|||
assert.False(t, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnsureExtraRBAC(t *testing.T) {
|
||||
namespace := "default"
|
||||
jenkinsName := "example"
|
||||
log.SetupLogger(true)
|
||||
|
||||
fetchAllRoleBindings := func(client k8sclient.Client) (roleBindings *rbacv1.RoleBindingList, err error) {
|
||||
roleBindings = &rbacv1.RoleBindingList{}
|
||||
err = client.List(context.TODO(), &k8sclient.ListOptions{Namespace: namespace}, roleBindings)
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
// given
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkins := &v1alpha2.Jenkins{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jenkinsName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.JenkinsSpec{
|
||||
Roles: []rbacv1.RoleRef{},
|
||||
},
|
||||
}
|
||||
reconciler := New(configuration.Configuration{Client: fakeClient, Jenkins: jenkins, Scheme: scheme.Scheme}, nil, client.JenkinsAPIConnectionSettings{}, nil)
|
||||
metaObject := resources.NewResourceObjectMeta(jenkins)
|
||||
|
||||
// when
|
||||
err = reconciler.createRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
err = reconciler.ensureExtraRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then
|
||||
roleBindings, err := fetchAllRoleBindings(fakeClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(roleBindings.Items))
|
||||
assert.Equal(t, metaObject.Name, roleBindings.Items[0].Name)
|
||||
})
|
||||
clusterRoleKind := "ClusterRole"
|
||||
t.Run("one extra", func(t *testing.T) {
|
||||
// given
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkins := &v1alpha2.Jenkins{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jenkinsName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.JenkinsSpec{
|
||||
Roles: []rbacv1.RoleRef{
|
||||
{APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "edit",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
reconciler := New(configuration.Configuration{Client: fakeClient, Jenkins: jenkins, Scheme: scheme.Scheme}, nil, client.JenkinsAPIConnectionSettings{}, nil)
|
||||
metaObject := resources.NewResourceObjectMeta(jenkins)
|
||||
|
||||
// when
|
||||
err = reconciler.createRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
err = reconciler.ensureExtraRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then
|
||||
roleBindings, err := fetchAllRoleBindings(fakeClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(roleBindings.Items))
|
||||
assert.Equal(t, metaObject.Name, roleBindings.Items[0].Name)
|
||||
assert.Equal(t, jenkins.Spec.Roles[0], roleBindings.Items[1].RoleRef)
|
||||
})
|
||||
t.Run("two extra", func(t *testing.T) {
|
||||
// given
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkins := &v1alpha2.Jenkins{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jenkinsName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.JenkinsSpec{
|
||||
Roles: []rbacv1.RoleRef{
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "admin",
|
||||
},
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "edit",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
reconciler := New(configuration.Configuration{Client: fakeClient, Jenkins: jenkins, Scheme: scheme.Scheme}, nil, client.JenkinsAPIConnectionSettings{}, nil)
|
||||
metaObject := resources.NewResourceObjectMeta(jenkins)
|
||||
|
||||
// when
|
||||
err = reconciler.createRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
err = reconciler.ensureExtraRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then
|
||||
roleBindings, err := fetchAllRoleBindings(fakeClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(roleBindings.Items))
|
||||
assert.Equal(t, metaObject.Name, roleBindings.Items[0].Name)
|
||||
assert.Equal(t, jenkins.Spec.Roles[0], roleBindings.Items[1].RoleRef)
|
||||
assert.Equal(t, jenkins.Spec.Roles[1], roleBindings.Items[2].RoleRef)
|
||||
})
|
||||
t.Run("delete one extra", func(t *testing.T) {
|
||||
// given
|
||||
fakeClient := fake.NewFakeClient()
|
||||
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
assert.NoError(t, err)
|
||||
|
||||
jenkins := &v1alpha2.Jenkins{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jenkinsName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1alpha2.JenkinsSpec{
|
||||
Roles: []rbacv1.RoleRef{
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "admin",
|
||||
},
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "edit",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
reconciler := New(configuration.Configuration{Client: fakeClient, Jenkins: jenkins, Scheme: scheme.Scheme}, log.Log, client.JenkinsAPIConnectionSettings{}, nil)
|
||||
metaObject := resources.NewResourceObjectMeta(jenkins)
|
||||
|
||||
// when
|
||||
roleBindingSkipMe := resources.NewRoleBinding("skip-me", namespace, metaObject.Name, rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "edit",
|
||||
})
|
||||
err = reconciler.CreateOrUpdateResource(roleBindingSkipMe)
|
||||
assert.NoError(t, err)
|
||||
err = reconciler.createRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
err = reconciler.ensureExtraRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
jenkins.Spec.Roles = []rbacv1.RoleRef{
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: clusterRoleKind,
|
||||
Name: "admin",
|
||||
},
|
||||
}
|
||||
err = reconciler.ensureExtraRBAC(metaObject)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then
|
||||
roleBindings, err := fetchAllRoleBindings(fakeClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(roleBindings.Items))
|
||||
assert.Equal(t, metaObject.Name, roleBindings.Items[1].Name)
|
||||
assert.Equal(t, jenkins.Spec.Roles[0], roleBindings.Items[2].RoleRef)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,23 +54,22 @@ func NewRole(meta metav1.ObjectMeta) *v1.Role {
|
|||
}
|
||||
|
||||
// NewRoleBinding returns rbac role binding for jenkins master
|
||||
func NewRoleBinding(meta metav1.ObjectMeta) *v1.RoleBinding {
|
||||
func NewRoleBinding(name, namespace, serviceAccountName string, roleRef v1.RoleRef) *v1.RoleBinding {
|
||||
return &v1.RoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "RoleBinding",
|
||||
APIVersion: "rbac.authorization.k8s.io/v1",
|
||||
},
|
||||
ObjectMeta: meta,
|
||||
RoleRef: v1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: meta.Name,
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
RoleRef: roleRef,
|
||||
Subjects: []v1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: meta.Name,
|
||||
Namespace: meta.Namespace,
|
||||
Name: serviceAccountName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
||||
framework "github.com/operator-framework/operator-sdk/pkg/test"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
|
@ -144,6 +145,13 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S
|
|||
Type: corev1.ServiceTypeNodePort,
|
||||
Port: constants.DefaultHTTPPortInt32,
|
||||
},
|
||||
Roles: []rbacv1.RoleRef{
|
||||
{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: "view",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue