diff --git a/deploy/clusterrole-aggregate-edit.yaml b/deploy/clusterrole-aggregate-edit.yaml new file mode 100644 index 0000000000..aa6998fe74 --- /dev/null +++ b/deploy/clusterrole-aggregate-edit.yaml @@ -0,0 +1,20 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: shipwright-build-aggregate-edit +rules: +- apiGroups: ['shipwright.io'] + resources: ['clusterbuildstrategies'] + verbs: ['get', 'list', 'watch'] +- apiGroups: ['shipwright.io'] + resources: ['buildstrategies'] + verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'] +- apiGroups: ['shipwright.io'] + resources: ['builds'] + verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'] +- apiGroups: ['shipwright.io'] + resources: ['buildruns'] + verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'] diff --git a/deploy/clusterrole-aggregate-view.yaml b/deploy/clusterrole-aggregate-view.yaml new file mode 100644 index 0000000000..48448a73c8 --- /dev/null +++ b/deploy/clusterrole-aggregate-view.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: shipwright-build-aggregate-view +rules: +- apiGroups: ['shipwright.io'] + resources: ['clusterbuildstrategies'] + verbs: ['get', 'list', 'watch'] +- apiGroups: ['shipwright.io'] + resources: ['buildstrategies'] + verbs: ['get', 'list', 'watch'] +- apiGroups: ['shipwright.io'] + resources: ['builds'] + verbs: ['get', 'list', 'watch'] +- apiGroups: ['shipwright.io'] + resources: ['buildruns'] + verbs: ['get', 'list', 'watch'] diff --git a/docs/configuration.md b/docs/configuration.md index 6c2b98ac98..a21b1c7f70 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,6 +6,8 @@ SPDX-License-Identifier: Apache-2.0 # Configuration +## Controller Settings + The controller is installed into Kubernetes with reasonable defaults. However, there are some settings that can be overridden using environment variables in [`controller.yaml`](../deploy/500-controller.yaml). The following environment variables are available: @@ -30,3 +32,19 @@ The following environment variables are available: | `KUBE_API_QPS` | QPS to use for the Kubernetes API client. See [Config.QPS](https://pkg.go.dev/k8s.io/client-go/rest#Config.QPS). A value of 0 or lower will use the default from client-go, which currently is 5. Default is 0. | | `TERMINATION_LOG_PATH` | Path of the termination log. This is where controller application will write the reason of its termination. Default value is `/dev/termination-log`. | | `GIT_ENABLE_REWRITE_RULE` | Enable Git wrapper to setup a URL `insteadOf` Git config rewrite rule for the respective source URL hostname. Default is `false`. | + +## Role-based Access Control + +The release deployment YAML file includes two cluster-wide roles for using Shipwright Build objects. +The following roles are installed: + +- `shpwright-build-aggregate-view`: this role grants read access (get, list, watch) to most Shipwright Build objects. + This includes `BuildStrategy`, `ClusterBuildStrategy`, `Build`, and `BuildRun` objects. + This role is aggregated to the Kubernetes "view" role. +- `shipwright-build-aggregate-edit`: this role grants write access (create, update, patch, delete) to Shipwright objects that are namespace-scoped. + This includes `BuildStrategy`, `Builds`, and `BuildRuns`. + Read access is granted to all `ClusterBuildStrategy` objects. + This role is aggregated to the Kubernetes "edit" and "admin" roles. + +Only cluster administrators are granted write access to `ClusterBuildStrategy` objects. +This can be changed by creating a separate Kubernetes `ClusterRole` with these permissions and binding the role to appropriate users. diff --git a/test/e2e/e2e_rbac_test.go b/test/e2e/e2e_rbac_test.go new file mode 100644 index 0000000000..6fbd6a89db --- /dev/null +++ b/test/e2e/e2e_rbac_test.go @@ -0,0 +1,75 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package e2e_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("User RBAC for Shipwright", func() { + + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + }) + + It("should install an aggregated edit role for developers", func() { + editRole, err := testBuild.Clientset.RbacV1().ClusterRoles().Get(ctx, "shipwright-build-aggregate-edit", metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectedAggregates := []string{ + "rbac.authorization.k8s.io/aggregate-to-edit", + "rbac.authorization.k8s.io/aggregate-to-admin", + } + for _, aggregate := range expectedAggregates { + aggregateValue, exists := editRole.Labels[aggregate] + Expect(exists).To(BeTrue()) + Expect(aggregateValue).To(Equal("true")) + } + // We should have at least two rules - one for ClusterBuildStrategy, another for all else + // More than two rules is acceptable. + Expect(len(editRole.Rules)).To(BeNumerically(">=", 2)) + for _, rule := range editRole.Rules { + Expect(rule.APIGroups).To(ContainElement("shipwright.io")) + for _, resource := range rule.Resources { + if resource == "clusterbuildstrategies" { + Expect(rule.Verbs).To(ContainElements("get", "list", "watch")) + Expect(rule.Verbs).NotTo(ContainElement("create")) + Expect(rule.Verbs).NotTo(ContainElement("update")) + Expect(rule.Verbs).NotTo(ContainElement("patch")) + Expect(rule.Verbs).NotTo(ContainElement("delete")) + } else { + Expect(rule.Verbs).To(ContainElements("get", "list", "watch", "create", "update", "patch", "delete")) + } + } + } + }) + + It("should install an aggregated view role for all users", func() { + viewRole, err := testBuild.Clientset.RbacV1().ClusterRoles().Get(ctx, "shipwright-build-aggregate-view", metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + aggregateValue, exists := viewRole.Labels["rbac.authorization.k8s.io/aggregate-to-view"] + Expect(exists).To(BeTrue()) + Expect(aggregateValue).To(Equal("true")) + // We should have at least one rule, as this applies "view" permissions to all Shipwright Build objects + // More rules are acceptable for future fine-grained controls. + Expect(len(viewRole.Rules)).To(BeNumerically(">=", 1)) + for _, rule := range viewRole.Rules { + Expect(rule.APIGroups).To(ContainElement("shipwright.io")) + Expect(rule.Verbs).To(ContainElements("get", "list", "watch")) + Expect(rule.Verbs).NotTo(ContainElement("create")) + Expect(rule.Verbs).NotTo(ContainElement("update")) + Expect(rule.Verbs).NotTo(ContainElement("patch")) + Expect(rule.Verbs).NotTo(ContainElement("delete")) + } + }) + +})