-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #46076 from liggitt/node-authorizer
Automatic merge from submit-queue Node authorizer This PR implements the authorization portion of https://github.com/kubernetes/community/blob/master/contributors/design-proposals/kubelet-authorizer.md and kubernetes/enhancements#279: * Adds a new authorization mode (`Node`) that authorizes requests from nodes based on a graph of related pods,secrets,configmaps,pvcs, and pvs: * Watches pods, adds edges (secret -> pod, configmap -> pod, pvc -> pod, pod -> node) * Watches pvs, adds edges (secret -> pv, pv -> pvc) * When both Node and RBAC authorization modes are enabled, the default RBAC binding that grants the `system:node` role to the `system:nodes` group is not automatically created. * Tightens the `NodeRestriction` admission plugin to require identifiable nodes for requests from users in the `system:nodes` group. This authorization mode is intended to be used in combination with the `NodeRestriction` admission plugin, which limits the pods and nodes a node may modify. To enable in combination with RBAC authorization and the NodeRestriction admission plugin: * start the API server with `--authorization-mode=Node,RBAC --admission-control=...,NodeRestriction,...` * start kubelets with TLS boostrapping or with client credentials that place them in the `system:nodes` group with a username of `system:node:<nodeName>` ```release-note kube-apiserver: a new authorization mode (`--authorization-mode=Node`) authorizes nodes to access secrets, configmaps, persistent volume claims and persistent volumes related to their pods. * Nodes must use client credentials that place them in the `system:nodes` group with a username of `system:node:<nodeName>` in order to be authorized by the node authorizer (the credentials obtained by the kubelet via TLS bootstrapping satisfy these requirements) * When used in combination with the `RBAC` authorization mode (`--authorization-mode=Node,RBAC`), the `system:node` role is no longer automatically granted to the `system:nodes` group. ``` ```release-note RBAC: the automatic binding of the `system:node` role to the `system:nodes` group is deprecated and will not be created in future releases. It is recommended that nodes be authorized using the new `Node` authorization mode instead. Installations that wish to continue giving all members of the `system:nodes` group the `system:node` role (which grants broad read access, including all secrets and configmaps) must create an installation-specific ClusterRoleBinding. ``` Follow-up: - [ ] enable e2e CI environment with admission and authorizer enabled (blocked by kubelet TLS bootstrapping enablement in #40760) - [ ] optionally enable this authorizer and admission plugin in kubeadm - [ ] optionally enable this authorizer and admission plugin in kube-up
- Loading branch information
Showing
55 changed files
with
4,409 additions
and
59 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
|
||
licenses(["notice"]) | ||
|
||
load( | ||
"@io_bazel_rules_go//go:def.bzl", | ||
"go_library", | ||
"go_test", | ||
) | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = ["util.go"], | ||
tags = ["automanaged"], | ||
deps = ["//pkg/api:go_default_library"], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [":package-srcs"], | ||
tags = ["automanaged"], | ||
) | ||
|
||
go_test( | ||
name = "go_default_test", | ||
srcs = ["util_test.go"], | ||
library = ":go_default_library", | ||
tags = ["automanaged"], | ||
deps = [ | ||
"//pkg/api:go_default_library", | ||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", | ||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
reviewers: | ||
- smarterclayton | ||
- kargakis | ||
- david-mcmahon |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
Copyright 2017 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 persistentvolume | ||
|
||
import ( | ||
"k8s.io/kubernetes/pkg/api" | ||
) | ||
|
||
// VisitPVSecretNames invokes the visitor function with the name of every secret | ||
// referenced by the PV spec. If visitor returns false, visiting is short-circuited. | ||
// Returns true if visiting completed, false if visiting was short-circuited. | ||
func VisitPVSecretNames(pv *api.PersistentVolume, visitor func(string) bool) bool { | ||
source := &pv.Spec.PersistentVolumeSource | ||
switch { | ||
case source.AzureFile != nil: | ||
if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) { | ||
return false | ||
} | ||
case source.CephFS != nil: | ||
if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) { | ||
return false | ||
} | ||
case source.FlexVolume != nil: | ||
if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) { | ||
return false | ||
} | ||
case source.RBD != nil: | ||
if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) { | ||
return false | ||
} | ||
case source.ScaleIO != nil: | ||
if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) { | ||
return false | ||
} | ||
case source.ISCSI != nil: | ||
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
Copyright 2017 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 persistentvolume | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"strings" | ||
|
||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
"k8s.io/kubernetes/pkg/api" | ||
) | ||
|
||
func TestPVSecrets(t *testing.T) { | ||
// Stub containing all possible secret references in a PV. | ||
// The names of the referenced secrets match struct paths detected by reflection. | ||
pvs := []*api.PersistentVolume{ | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
AzureFile: &api.AzureFileVolumeSource{ | ||
SecretName: "Spec.PersistentVolumeSource.AzureFile.SecretName"}}}}, | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
CephFS: &api.CephFSVolumeSource{ | ||
SecretRef: &api.LocalObjectReference{ | ||
Name: "Spec.PersistentVolumeSource.CephFS.SecretRef"}}}}}, | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
FlexVolume: &api.FlexVolumeSource{ | ||
SecretRef: &api.LocalObjectReference{ | ||
Name: "Spec.PersistentVolumeSource.FlexVolume.SecretRef"}}}}}, | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
RBD: &api.RBDVolumeSource{ | ||
SecretRef: &api.LocalObjectReference{ | ||
Name: "Spec.PersistentVolumeSource.RBD.SecretRef"}}}}}, | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
ScaleIO: &api.ScaleIOVolumeSource{ | ||
SecretRef: &api.LocalObjectReference{ | ||
Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef"}}}}}, | ||
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{ | ||
ISCSI: &api.ISCSIVolumeSource{ | ||
SecretRef: &api.LocalObjectReference{ | ||
Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef"}}}}}, | ||
} | ||
extractedNames := sets.NewString() | ||
for _, pv := range pvs { | ||
VisitPVSecretNames(pv, func(name string) bool { | ||
extractedNames.Insert(name) | ||
return true | ||
}) | ||
} | ||
|
||
// excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects | ||
excludedSecretPaths := sets.NewString( | ||
"Spec.PersistentVolumeSource.CephFS.SecretFile", | ||
) | ||
// expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects. | ||
// every path here should be represented as an example in the PV stub above, with the secret name set to the path. | ||
expectedSecretPaths := sets.NewString( | ||
"Spec.PersistentVolumeSource.AzureFile.SecretName", | ||
"Spec.PersistentVolumeSource.CephFS.SecretRef", | ||
"Spec.PersistentVolumeSource.FlexVolume.SecretRef", | ||
"Spec.PersistentVolumeSource.RBD.SecretRef", | ||
"Spec.PersistentVolumeSource.ScaleIO.SecretRef", | ||
"Spec.PersistentVolumeSource.ISCSI.SecretRef", | ||
) | ||
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.PersistentVolume{})) | ||
secretPaths = secretPaths.Difference(excludedSecretPaths) | ||
if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 { | ||
t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n")) | ||
t.Error("Missing expected secret paths. Verify VisitPVSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths") | ||
} | ||
if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 { | ||
t.Logf("Extra secret paths:\n%s", strings.Join(extraPaths.List(), "\n")) | ||
t.Error("Extra fields with 'secret' in the name found. Verify VisitPVSecretNames() is including these fields if appropriate, then correct expectedSecretPaths") | ||
} | ||
|
||
if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 { | ||
t.Logf("Missing expected secret names:\n%s", strings.Join(missingNames.List(), "\n")) | ||
t.Error("Missing expected secret names. Verify the PV stub above includes these references, then verify VisitPVSecretNames() is correctly finding the missing names") | ||
} | ||
if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 { | ||
t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n")) | ||
t.Error("Extra secret names extracted. Verify VisitPVSecretNames() is correctly extracting secret names") | ||
} | ||
} | ||
|
||
// collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name. | ||
func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.Type) sets.String { | ||
secretPaths := sets.NewString() | ||
|
||
if tp.Kind() == reflect.Ptr { | ||
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) | ||
return secretPaths | ||
} | ||
|
||
if strings.Contains(strings.ToLower(name), "secret") { | ||
secretPaths.Insert(path.String()) | ||
} | ||
|
||
switch tp.Kind() { | ||
case reflect.Ptr: | ||
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...) | ||
case reflect.Struct: | ||
for i := 0; i < tp.NumField(); i++ { | ||
field := tp.Field(i) | ||
secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...) | ||
} | ||
case reflect.Interface: | ||
t.Errorf("cannot find secret fields in interface{} field %s", path.String()) | ||
case reflect.Map: | ||
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...) | ||
case reflect.Slice: | ||
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...) | ||
default: | ||
// all primitive types | ||
} | ||
|
||
return secretPaths | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.