Skip to content

Commit a92c695

Browse files
fix(operator): move authorization RBAC to dedicated cluster_scoped
reconciler
1 parent 21cfa33 commit a92c695

File tree

8 files changed

+77
-53
lines changed

8 files changed

+77
-53
lines changed

operator/internal/controller/loki/dashboards_controller.go renamed to operator/internal/controller/loki/lokistack_cluster_scoped_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ func (r *DashboardsReconciler) Reconcile(ctx context.Context, req ctrl.Request)
4747
if len(stacks.Items) == 0 {
4848
// Removes all LokiStack dashboard resources on OpenShift clusters when
4949
// the last LokiStack custom resource is deleted.
50-
if err := handlers.DeleteDashboards(ctx, r.Client, r.OperatorNs); err != nil {
50+
if err := handlers.DeleteClusterScopedResources(ctx, r.Client, r.OperatorNs); err != nil {
5151
return ctrl.Result{}, kverrors.Wrap(err, "failed to delete dashboard resources")
5252
}
5353
return ctrl.Result{}, nil
5454
}
5555

5656
// Creates all LokiStack dashboard resources on OpenShift clusters when
5757
// the first LokiStack custom resource is created.
58-
if err := handlers.CreateDashboards(ctx, r.Log, r.OperatorNs, r.Client, r.Scheme); err != nil {
58+
if err := handlers.CreateClusterScopedResources(ctx, r.Log, r.OperatorNs, r.Client, r.Scheme, stacks); err != nil {
5959
return ctrl.Result{}, kverrors.Wrap(err, "failed to create dashboard resources", "req", req)
6060
}
6161
return ctrl.Result{}, nil

operator/internal/handlers/dashboards_create.go renamed to operator/internal/handlers/lokistack_cluster_scope_resources_create.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,34 @@ import (
1111
"sigs.k8s.io/controller-runtime/pkg/client" //nolint:typecheck
1212
ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1313

14+
lokiv1 "github.com/grafana/loki/operator/api/loki/v1"
1415
"github.com/grafana/loki/operator/internal/external/k8s"
1516
"github.com/grafana/loki/operator/internal/manifests"
1617
"github.com/grafana/loki/operator/internal/manifests/openshift"
18+
rbacv1 "k8s.io/api/rbac/v1"
1719
)
1820

19-
// CreateDashboards handles the LokiStack dashboards create events.
20-
func CreateDashboards(ctx context.Context, log logr.Logger, operatorNs string, k k8s.Client, s *runtime.Scheme) error {
21+
// CreateClusterScopedResources handles the LokiStack cluster scoped create events.
22+
func CreateClusterScopedResources(ctx context.Context, log logr.Logger, operatorNs string, k k8s.Client, s *runtime.Scheme, stacks lokiv1.LokiStackList) error {
2123
objs, err := openshift.BuildDashboards(operatorNs)
2224
if err != nil {
2325
return kverrors.Wrap(err, "failed to build dashboard manifests")
2426
}
2527

28+
subjects := make([]rbacv1.Subject, 0, len(stacks.Items))
29+
for _, stack := range stacks.Items {
30+
subjects = append(subjects, rbacv1.Subject{
31+
Kind: "ServiceAccount",
32+
Name: manifests.GatewayName(stack.Name),
33+
Namespace: stack.Namespace,
34+
})
35+
}
36+
rbacOBjs, err := openshift.BuildRBAC(subjects)
37+
if err != nil {
38+
return kverrors.Wrap(err, "failed to build RBAC manifests")
39+
}
40+
objs = append(objs, rbacOBjs...)
41+
2642
var errCount int32
2743
for _, obj := range objs {
2844
desired := obj.DeepCopyObject().(client.Object)

operator/internal/handlers/dashboards_create_test.go renamed to operator/internal/handlers/lokistack_cluster_scope_resources_create_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestCreateDashboards_ReturnsResourcesInManagedNamespaces(t *testing.T) {
5353

5454
k.StatusStub = func() client.StatusWriter { return sw }
5555

56-
err := CreateDashboards(context.TODO(), logger, "test", k, scheme)
56+
err := CreateClusterScopedResources(context.TODO(), logger, "test", k, scheme)
5757
require.NoError(t, err)
5858

5959
// make sure create was called

operator/internal/handlers/dashboards_delete.go renamed to operator/internal/handlers/lokistack_cluster_scope_resources_delete.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"github.com/grafana/loki/operator/internal/manifests/openshift"
1212
)
1313

14-
// DeleteDashboards removes all cluster-scoped dashboard resources.
15-
func DeleteDashboards(ctx context.Context, k k8s.Client, operatorNs string) error {
14+
// DeleteClusterScopedResources removes all cluster-scoped resources.
15+
func DeleteClusterScopedResources(ctx context.Context, k k8s.Client, operatorNs string) error {
1616
objs, err := openshift.BuildDashboards(operatorNs)
1717
if err != nil {
1818
return kverrors.Wrap(err, "failed to build dashboards manifests")

operator/internal/handlers/dashboards_delete_test.go renamed to operator/internal/handlers/lokistack_cluster_scope_resources_delete_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestDeleteDashboards(t *testing.T) {
1919

2020
k := &k8sfakes.FakeClient{}
2121

22-
err = DeleteDashboards(context.TODO(), k, "operator-ns")
22+
err = DeleteClusterScopedResources(context.TODO(), k, "operator-ns")
2323
require.NoError(t, err)
2424
require.Equal(t, k.DeleteCallCount(), len(objs))
2525
}
@@ -30,6 +30,6 @@ func TestDeleteDashboards_ReturnsNoError_WhenNotFound(t *testing.T) {
3030
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
3131
}
3232

33-
err := DeleteDashboards(context.TODO(), k, "operator-ns")
33+
err := DeleteClusterScopedResources(context.TODO(), k, "operator-ns")
3434
require.NoError(t, err)
3535
}

operator/internal/manifests/openshift/build.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,11 @@ func BuildGatewayObjects(opts Options) []client.Object {
1515
}
1616
}
1717

18-
// BuildGatewayTenantModeObjects returns a list of auxiliary openshift/k8s objects
19-
// for lokistack gateway deployments on OpenShift for tenant modes:
20-
// - openshift-logging
21-
// - openshift-network
22-
func BuildGatewayTenantModeObjects(opts Options) []client.Object {
23-
return []client.Object{
24-
BuildGatewayClusterRole(opts),
25-
BuildGatewayClusterRoleBinding(opts),
26-
}
27-
}
28-
2918
// BuildRulerObjects returns a list of auxiliary openshift/k8s objects
3019
// for lokistack ruler deployments on OpenShift.
3120
func BuildRulerObjects(opts Options) []client.Object {
3221
return []client.Object{
3322
BuildAlertManagerCAConfigMap(opts),
3423
BuildRulerServiceAccount(opts),
35-
BuildRulerClusterRole(opts),
36-
BuildRulerClusterRoleBinding(opts),
3724
}
3825
}

operator/internal/manifests/openshift/rbac.go

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,36 @@ package openshift
33
import (
44
rbacv1 "k8s.io/api/rbac/v1"
55
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"sigs.k8s.io/controller-runtime/pkg/client"
67
)
78

9+
const (
10+
gatewayName = "lokistack-gateway"
11+
rulerName = "lokistack-ruler"
12+
)
13+
14+
func BuildRBAC(subjects []rbacv1.Subject) ([]client.Object, error) {
15+
objs := make([]client.Object, 0, 4)
16+
objs = append(objs, BuildGatewayClusterRole())
17+
objs = append(objs, BuildGatewayClusterRoleBinding(subjects))
18+
objs = append(objs, BuildRulerClusterRole())
19+
objs = append(objs, BuildRulerClusterRoleBinding(subjects))
20+
return objs, nil
21+
}
22+
823
// BuildGatewayClusterRole returns a k8s ClusterRole object for the
924
// lokistack gateway serviceaccount to allow creating:
1025
// - TokenReviews to authenticate the user by bearer token.
1126
// - SubjectAccessReview to authorize the user by bearer token.
1227
// if having access to read/create logs.
13-
func BuildGatewayClusterRole(opts Options) *rbacv1.ClusterRole {
28+
func BuildGatewayClusterRole() *rbacv1.ClusterRole {
1429
return &rbacv1.ClusterRole{
1530
TypeMeta: metav1.TypeMeta{
1631
Kind: "ClusterRole",
1732
APIVersion: rbacv1.SchemeGroupVersion.String(),
1833
},
1934
ObjectMeta: metav1.ObjectMeta{
20-
Name: authorizerRbacName(opts.BuildOpts.GatewayName),
21-
Labels: opts.BuildOpts.Labels,
35+
Name: authorizerRbacName(gatewayName),
2236
},
2337
Rules: []rbacv1.PolicyRule{
2438
{
@@ -51,42 +65,34 @@ func BuildGatewayClusterRole(opts Options) *rbacv1.ClusterRole {
5165
// the lokistack gateway serviceaccount to grant access to:
5266
// - rbac.authentication.k8s.io/TokenReviews
5367
// - rbac.authorization.k8s.io/SubjectAccessReviews
54-
func BuildGatewayClusterRoleBinding(opts Options) *rbacv1.ClusterRoleBinding {
68+
func BuildGatewayClusterRoleBinding(subjects []rbacv1.Subject) *rbacv1.ClusterRoleBinding {
5569
return &rbacv1.ClusterRoleBinding{
5670
TypeMeta: metav1.TypeMeta{
5771
Kind: "ClusterRoleBinding",
5872
APIVersion: rbacv1.SchemeGroupVersion.String(),
5973
},
6074
ObjectMeta: metav1.ObjectMeta{
61-
Name: authorizerRbacName(opts.BuildOpts.GatewayName),
62-
Labels: opts.BuildOpts.Labels,
75+
Name: authorizerRbacName(gatewayName),
6376
},
6477
RoleRef: rbacv1.RoleRef{
6578
APIGroup: "rbac.authorization.k8s.io",
6679
Kind: "ClusterRole",
67-
Name: authorizerRbacName(opts.BuildOpts.GatewayName),
68-
},
69-
Subjects: []rbacv1.Subject{
70-
{
71-
Kind: rbacv1.ServiceAccountKind,
72-
Name: gatewayServiceAccountName(opts),
73-
Namespace: opts.BuildOpts.LokiStackNamespace,
74-
},
80+
Name: authorizerRbacName(gatewayName),
7581
},
82+
Subjects: subjects,
7683
}
7784
}
7885

7986
// BuildRulerClusterRole returns a k8s ClusterRole object for the
8087
// lokistack ruler serviceaccount to allow patching sending alerts to alertmanagers.
81-
func BuildRulerClusterRole(opts Options) *rbacv1.ClusterRole {
88+
func BuildRulerClusterRole() *rbacv1.ClusterRole {
8289
return &rbacv1.ClusterRole{
8390
TypeMeta: metav1.TypeMeta{
8491
Kind: "ClusterRole",
8592
APIVersion: rbacv1.SchemeGroupVersion.String(),
8693
},
8794
ObjectMeta: metav1.ObjectMeta{
88-
Name: authorizerRbacName(opts.BuildOpts.RulerName),
89-
Labels: opts.BuildOpts.Labels,
95+
Name: authorizerRbacName(rulerName),
9096
},
9197
Rules: []rbacv1.PolicyRule{
9298
{
@@ -125,27 +131,20 @@ func BuildRulerClusterRole(opts Options) *rbacv1.ClusterRole {
125131

126132
// BuildRulerClusterRoleBinding returns a k8s ClusterRoleBinding object for
127133
// the lokistack ruler serviceaccount to grant access to alertmanagers.
128-
func BuildRulerClusterRoleBinding(opts Options) *rbacv1.ClusterRoleBinding {
134+
func BuildRulerClusterRoleBinding(subjects []rbacv1.Subject) *rbacv1.ClusterRoleBinding {
129135
return &rbacv1.ClusterRoleBinding{
130136
TypeMeta: metav1.TypeMeta{
131137
Kind: "ClusterRoleBinding",
132138
APIVersion: rbacv1.SchemeGroupVersion.String(),
133139
},
134140
ObjectMeta: metav1.ObjectMeta{
135-
Name: authorizerRbacName(opts.BuildOpts.RulerName),
136-
Labels: opts.BuildOpts.Labels,
141+
Name: authorizerRbacName(rulerName),
137142
},
138143
RoleRef: rbacv1.RoleRef{
139144
APIGroup: "rbac.authorization.k8s.io",
140145
Kind: "ClusterRole",
141-
Name: authorizerRbacName(opts.BuildOpts.RulerName),
142-
},
143-
Subjects: []rbacv1.Subject{
144-
{
145-
Kind: rbacv1.ServiceAccountKind,
146-
Name: rulerServiceAccountName(opts),
147-
Namespace: opts.BuildOpts.LokiStackNamespace,
148-
},
146+
Name: authorizerRbacName(rulerName),
149147
},
148+
Subjects: subjects,
150149
}
151150
}

operator/internal/validation/openshift/common.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
descriptionAnnotationName = "description"
2020

2121
namespaceLabelName = "kubernetes_namespace_name"
22+
namespaceOTLPLabelName = "k8s_namespace_name"
2223
namespaceOpenshiftLogging = "openshift-logging"
2324

2425
tenantAudit = "audit"
@@ -64,23 +65,44 @@ func validateRuleExpression(namespace, tenantID, rawExpr string) error {
6465
}
6566

6667
matchers := selector.Matchers()
67-
if tenantID != tenantAudit && !validateIncludesNamespace(namespace, matchers) {
68-
return lokiv1.ErrRuleMustMatchNamespace
68+
if tenantID != tenantAudit {
69+
if !validateIncludesNamespace(namespace, matchers) {
70+
return lokiv1.ErrRuleMustMatchNamespace
71+
}
72+
if !validateExclusiveNamespaceLabels(matchers) {
73+
return lokiv1.ErrRuleExclusiveNamespaceLabel
74+
}
6975
}
7076

7177
return nil
7278
}
7379

7480
func validateIncludesNamespace(namespace string, matchers []*labels.Matcher) bool {
7581
for _, m := range matchers {
76-
if m.Name == namespaceLabelName && m.Type == labels.MatchEqual && m.Value == namespace {
82+
if (m.Name == namespaceLabelName || m.Name == namespaceOTLPLabelName) && m.Type == labels.MatchEqual && m.Value == namespace {
7783
return true
7884
}
7985
}
8086

8187
return false
8288
}
8389

90+
func validateExclusiveNamespaceLabels(matchers []*labels.Matcher) bool {
91+
var namespaceLabelSet, otlpLabelSet bool
92+
93+
for _, m := range matchers {
94+
if m.Name == namespaceLabelName && m.Type == labels.MatchEqual {
95+
namespaceLabelSet = true
96+
}
97+
if m.Name == namespaceOTLPLabelName && m.Type == labels.MatchEqual {
98+
otlpLabelSet = true
99+
}
100+
}
101+
102+
// Only one of the labels should be set, not both
103+
return (namespaceLabelSet || otlpLabelSet) && !(namespaceLabelSet && otlpLabelSet)
104+
}
105+
84106
func tenantForNamespace(namespace string) []string {
85107
if strings.HasPrefix(namespace, "openshift") ||
86108
strings.HasPrefix(namespace, "kube-") ||

0 commit comments

Comments
 (0)