From 04bec87cb28df2d29cefd550967804f050db12c0 Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 24 Feb 2021 10:37:43 -0500 Subject: [PATCH 1/2] add hardcoded authorizer to approve /metrics for metrics scraper --- main.go | 13 +++-- pkg/hardcodedauthorizer/metrics.go | 58 +++++++++++++++++++++ pkg/hardcodedauthorizer/metrics_test.go | 67 +++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 pkg/hardcodedauthorizer/metrics.go create mode 100644 pkg/hardcodedauthorizer/metrics_test.go diff --git a/main.go b/main.go index 7f86288f7..393a85835 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authorization/union" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -47,6 +48,7 @@ import ( "github.com/brancz/kube-rbac-proxy/pkg/authn" "github.com/brancz/kube-rbac-proxy/pkg/authz" + "github.com/brancz/kube-rbac-proxy/pkg/hardcodedauthorizer" "github.com/brancz/kube-rbac-proxy/pkg/proxy" rbac_proxy_tls "github.com/brancz/kube-rbac-proxy/pkg/tls" ) @@ -199,10 +201,15 @@ func main() { sarClient := kubeClient.AuthorizationV1().SubjectAccessReviews() authorizer, err := authz.NewAuthorizer(sarClient) - if err != nil { klog.Fatalf("Failed to create authorizer: %v", err) } + authorizer = union.New( + // prefix the authorizer with the permissions for metrics scraping which are well known. + // openshift RBAC policy will always allow this user to read metrics. + hardcodedauthorizer.NewHardCodedMetricsAuthorizer(), + authorizer, + ) auth, err := proxy.New(kubeClient, cfg.auth, authorizer, authenticator) @@ -388,14 +395,14 @@ func initKubeConfig(kcLocation string) *rest.Config { if kcLocation != "" { kubeConfig, err := clientcmd.BuildConfigFromFlags("", kcLocation) if err != nil { - klog.Fatalf("unable to build rest config based on provided path to kubeconfig file: %v",err) + klog.Fatalf("unable to build rest config based on provided path to kubeconfig file: %v", err) } return kubeConfig } kubeConfig, err := rest.InClusterConfig() if err != nil { - klog.Fatalf("cannot find Service Account in pod to build in-cluster rest config: %v",err) + klog.Fatalf("cannot find Service Account in pod to build in-cluster rest config: %v", err) } return kubeConfig diff --git a/pkg/hardcodedauthorizer/metrics.go b/pkg/hardcodedauthorizer/metrics.go new file mode 100644 index 000000000..67891c85f --- /dev/null +++ b/pkg/hardcodedauthorizer/metrics.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 Frederic Branczyk All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// this is copied from library-go to avoid a hard dependency +package hardcodedauthorizer + +import ( + "context" + + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +type metricsAuthorizer struct{} + +// GetUser() user.Info - checked +// GetVerb() string - checked +// IsReadOnly() bool - na +// GetNamespace() string - na +// GetResource() string - na +// GetSubresource() string - na +// GetName() string - na +// GetAPIGroup() string - na +// GetAPIVersion() string - na +// IsResourceRequest() bool - checked +// GetPath() string - checked +func (metricsAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { + if a.GetUser() == nil { + return authorizer.DecisionNoOpinion, "", nil + } + if a.GetUser().GetName() != "system:serviceaccount:openshift-monitoring:prometheus-k8s" { + return authorizer.DecisionNoOpinion, "", nil + } + if !a.IsResourceRequest() && + a.GetVerb() == "get" && + a.GetPath() == "/metrics" { + return authorizer.DecisionAllow, "requesting metrics is allowed", nil + } + + return authorizer.DecisionNoOpinion, "", nil +} + +// NewHardCodedMetricsAuthorizer returns a hardcoded authorizer for checking metrics. +func NewHardCodedMetricsAuthorizer() *metricsAuthorizer { + return new(metricsAuthorizer) +} diff --git a/pkg/hardcodedauthorizer/metrics_test.go b/pkg/hardcodedauthorizer/metrics_test.go new file mode 100644 index 000000000..a57eaf79f --- /dev/null +++ b/pkg/hardcodedauthorizer/metrics_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2021 Frederic Branczyk All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package hardcodedauthorizer + +import ( + "context" + "testing" + + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func TestAuthorizer(t *testing.T) { + tests := []struct { + name string + authorizer authorizer.Authorizer + + shouldPass []authorizer.Attributes + shouldNoOpinion []authorizer.Attributes + }{ + { + name: "metrics", + authorizer: NewHardCodedMetricsAuthorizer(), + shouldPass: []authorizer.Attributes{ + authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "system:serviceaccount:openshift-monitoring:prometheus-k8s"}, Verb: "get", Path: "/metrics"}, + }, + shouldNoOpinion: []authorizer.Attributes{ + // wrong user + authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "other"}, Verb: "get", Path: "/metrics"}, + // wrong verb + authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "system:serviceaccount:openshift-monitoring:prometheus-k8s"}, Verb: "update", Path: "/metrics"}, + + // wrong path + authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "system:serviceaccount:openshift-monitoring:prometheus-k8s"}, Verb: "get", Path: "/api"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, attr := range tt.shouldPass { + if decision, _, _ := tt.authorizer.Authorize(context.Background(), attr); decision != authorizer.DecisionAllow { + t.Errorf("incorrectly restricted %v", attr) + } + } + + for _, attr := range tt.shouldNoOpinion { + if decision, _, _ := tt.authorizer.Authorize(context.Background(), attr); decision != authorizer.DecisionNoOpinion { + t.Errorf("incorrectly opinionated %v", attr) + } + } + }) + } +} From 3fea9a79761000eb94f54a9d9fac348ac17d885a Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 24 Feb 2021 10:38:04 -0500 Subject: [PATCH 2/2] bump - k8s union authorizer --- .../pkg/authorization/union/union.go | 106 ++++++++++++++++++ vendor/modules.txt | 1 + 2 files changed, 107 insertions(+) create mode 100644 vendor/k8s.io/apiserver/pkg/authorization/union/union.go diff --git a/vendor/k8s.io/apiserver/pkg/authorization/union/union.go b/vendor/k8s.io/apiserver/pkg/authorization/union/union.go new file mode 100644 index 000000000..89d68ffed --- /dev/null +++ b/vendor/k8s.io/apiserver/pkg/authorization/union/union.go @@ -0,0 +1,106 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package union implements an authorizer that combines multiple subauthorizer. +// The union authorizer iterates over each subauthorizer and returns the first +// decision that is either an Allow decision or a Deny decision. If a +// subauthorizer returns a NoOpinion, then the union authorizer moves onto the +// next authorizer or, if the subauthorizer was the last authorizer, returns +// NoOpinion as the aggregate decision. I.e. union authorizer creates an +// aggregate decision and supports short-circuit allows and denies from +// subauthorizers. +package union + +import ( + "context" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +// unionAuthzHandler authorizer against a chain of authorizer.Authorizer +type unionAuthzHandler []authorizer.Authorizer + +// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects +func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { + return unionAuthzHandler(authorizationHandlers) +} + +// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful +func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + var ( + errlist []error + reasonlist []string + ) + + for _, currAuthzHandler := range authzHandler { + decision, reason, err := currAuthzHandler.Authorize(ctx, a) + + if err != nil { + errlist = append(errlist, err) + } + if len(reason) != 0 { + reasonlist = append(reasonlist, reason) + } + switch decision { + case authorizer.DecisionAllow, authorizer.DecisionDeny: + return decision, reason, err + case authorizer.DecisionNoOpinion: + // continue to the next authorizer + } + } + + return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) +} + +// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver +type unionAuthzRulesHandler []authorizer.RuleResolver + +// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects +func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver { + return unionAuthzRulesHandler(authorizationHandlers) +} + +// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful +func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { + var ( + errList []error + resourceRulesList []authorizer.ResourceRuleInfo + nonResourceRulesList []authorizer.NonResourceRuleInfo + ) + incompleteStatus := false + + for _, currAuthzHandler := range authzHandler { + resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace) + + if incomplete == true { + incompleteStatus = true + } + if err != nil { + errList = append(errList, err) + } + if len(resourceRules) > 0 { + resourceRulesList = append(resourceRulesList, resourceRules...) + } + if len(nonResourceRules) > 0 { + nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...) + } + } + + return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 86e46ff5e..e6ef48f2a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -324,6 +324,7 @@ k8s.io/apiserver/pkg/authentication/token/tokenfile k8s.io/apiserver/pkg/authentication/user k8s.io/apiserver/pkg/authorization/authorizer k8s.io/apiserver/pkg/authorization/authorizerfactory +k8s.io/apiserver/pkg/authorization/union k8s.io/apiserver/pkg/endpoints/request k8s.io/apiserver/pkg/server/dynamiccertificates k8s.io/apiserver/pkg/server/egressselector