mirror of https://github.com/k3s-io/k3s
536 lines
14 KiB
Go
536 lines
14 KiB
Go
/*
|
|
Copyright 2016 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 validation
|
|
|
|
import (
|
|
"testing"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/kubernetes/pkg/apis/rbac"
|
|
)
|
|
|
|
func TestValidateClusterRoleBinding(t *testing.T) {
|
|
errs := ValidateClusterRoleBinding(
|
|
&rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
Subjects: []rbac.Subject{
|
|
{Name: "validsaname", APIGroup: "", Namespace: "foo", Kind: rbac.ServiceAccountKind},
|
|
{Name: "valid@username", APIGroup: rbac.GroupName, Kind: rbac.UserKind},
|
|
{Name: "valid@groupname", APIGroup: rbac.GroupName, Kind: rbac.GroupKind},
|
|
},
|
|
},
|
|
)
|
|
if len(errs) != 0 {
|
|
t.Errorf("expected success: %v", errs)
|
|
}
|
|
|
|
errorCases := map[string]struct {
|
|
A rbac.ClusterRoleBinding
|
|
T field.ErrorType
|
|
F string
|
|
}{
|
|
"bad group": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: "rbac.GroupName", Kind: "ClusterRole", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "roleRef.apiGroup",
|
|
},
|
|
"bad kind": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Type", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "roleRef.kind",
|
|
},
|
|
"reference role": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "roleRef.kind",
|
|
},
|
|
"zero-length name": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "metadata.name",
|
|
},
|
|
"bad role": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole"},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "roleRef.name",
|
|
},
|
|
"bad subject kind": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Name: "subject"}},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "subjects[0].kind",
|
|
},
|
|
"bad subject name": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Namespace: "foo", Name: "subject:bad", Kind: rbac.ServiceAccountKind}},
|
|
},
|
|
T: field.ErrorTypeInvalid,
|
|
F: "subjects[0].name",
|
|
},
|
|
"missing SA namespace": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Name: "good", Kind: rbac.ServiceAccountKind}},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "subjects[0].namespace",
|
|
},
|
|
"missing subject name": {
|
|
A: rbac.ClusterRoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Namespace: "foo", Kind: rbac.ServiceAccountKind}},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "subjects[0].name",
|
|
},
|
|
}
|
|
for k, v := range errorCases {
|
|
errs := ValidateClusterRoleBinding(&v.A)
|
|
if len(errs) == 0 {
|
|
t.Errorf("expected failure %s for %v", k, v.A)
|
|
continue
|
|
}
|
|
for i := range errs {
|
|
if errs[i].Type != v.T {
|
|
t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
|
|
}
|
|
if errs[i].Field != v.F {
|
|
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRoleBinding(t *testing.T) {
|
|
errs := ValidateRoleBinding(
|
|
&rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
Subjects: []rbac.Subject{
|
|
{Name: "validsaname", APIGroup: "", Kind: rbac.ServiceAccountKind},
|
|
{Name: "valid@username", APIGroup: rbac.GroupName, Kind: rbac.UserKind},
|
|
{Name: "valid@groupname", APIGroup: rbac.GroupName, Kind: rbac.GroupKind},
|
|
},
|
|
},
|
|
)
|
|
if len(errs) != 0 {
|
|
t.Errorf("expected success: %v", errs)
|
|
}
|
|
|
|
errorCases := map[string]struct {
|
|
A rbac.RoleBinding
|
|
T field.ErrorType
|
|
F string
|
|
}{
|
|
"bad group": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: "rbac.GroupName", Kind: "ClusterRole", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "roleRef.apiGroup",
|
|
},
|
|
"bad kind": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Type", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "roleRef.kind",
|
|
},
|
|
"zero-length namespace": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "metadata.namespace",
|
|
},
|
|
"zero-length name": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "metadata.name",
|
|
},
|
|
"bad role": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "default"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role"},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "roleRef.name",
|
|
},
|
|
"bad subject kind": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Name: "subject"}},
|
|
},
|
|
T: field.ErrorTypeNotSupported,
|
|
F: "subjects[0].kind",
|
|
},
|
|
"bad subject name": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Name: "subject:bad", Kind: rbac.ServiceAccountKind}},
|
|
},
|
|
T: field.ErrorTypeInvalid,
|
|
F: "subjects[0].name",
|
|
},
|
|
"missing subject name": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
Subjects: []rbac.Subject{{Kind: rbac.ServiceAccountKind}},
|
|
},
|
|
T: field.ErrorTypeRequired,
|
|
F: "subjects[0].name",
|
|
},
|
|
}
|
|
for k, v := range errorCases {
|
|
errs := ValidateRoleBinding(&v.A)
|
|
if len(errs) == 0 {
|
|
t.Errorf("expected failure %s for %v", k, v.A)
|
|
continue
|
|
}
|
|
for i := range errs {
|
|
if errs[i].Type != v.T {
|
|
t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
|
|
}
|
|
if errs[i].Field != v.F {
|
|
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRoleBindingUpdate(t *testing.T) {
|
|
old := &rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master", ResourceVersion: "1"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
}
|
|
|
|
errs := ValidateRoleBindingUpdate(
|
|
&rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master", ResourceVersion: "1"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "valid"},
|
|
},
|
|
old,
|
|
)
|
|
if len(errs) != 0 {
|
|
t.Errorf("expected success: %v", errs)
|
|
}
|
|
|
|
errorCases := map[string]struct {
|
|
A rbac.RoleBinding
|
|
T field.ErrorType
|
|
F string
|
|
}{
|
|
"changedRef": {
|
|
A: rbac.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "master", ResourceVersion: "1"},
|
|
RoleRef: rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: "changed"},
|
|
},
|
|
T: field.ErrorTypeInvalid,
|
|
F: "roleRef",
|
|
},
|
|
}
|
|
for k, v := range errorCases {
|
|
errs := ValidateRoleBindingUpdate(&v.A, old)
|
|
if len(errs) == 0 {
|
|
t.Errorf("expected failure %s for %v", k, v.A)
|
|
continue
|
|
}
|
|
for i := range errs {
|
|
if errs[i].Type != v.T {
|
|
t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
|
|
}
|
|
if errs[i].Field != v.F {
|
|
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type ValidateRoleTest struct {
|
|
role rbac.Role
|
|
wantErr bool
|
|
errType field.ErrorType
|
|
field string
|
|
}
|
|
|
|
func (v ValidateRoleTest) test(t *testing.T) {
|
|
errs := ValidateRole(&v.role)
|
|
if len(errs) == 0 {
|
|
if v.wantErr {
|
|
t.Fatal("expected validation error")
|
|
}
|
|
return
|
|
}
|
|
if !v.wantErr {
|
|
t.Errorf("didn't expect error, got %v", errs)
|
|
return
|
|
}
|
|
for i := range errs {
|
|
if errs[i].Type != v.errType {
|
|
t.Errorf("expected errors to have type %s: %v", v.errType, errs[i])
|
|
}
|
|
if errs[i].Field != v.field {
|
|
t.Errorf("expected errors to have field %s: %v", v.field, errs[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
type ValidateClusterRoleTest struct {
|
|
role rbac.ClusterRole
|
|
wantErr bool
|
|
errType field.ErrorType
|
|
field string
|
|
}
|
|
|
|
func (v ValidateClusterRoleTest) test(t *testing.T) {
|
|
errs := ValidateClusterRole(&v.role)
|
|
if len(errs) == 0 {
|
|
if v.wantErr {
|
|
t.Fatal("expected validation error")
|
|
}
|
|
return
|
|
}
|
|
if !v.wantErr {
|
|
t.Errorf("didn't expect error, got %v", errs)
|
|
return
|
|
}
|
|
for i := range errs {
|
|
if errs[i].Type != v.errType {
|
|
t.Errorf("expected errors to have type %s: %v", v.errType, errs[i])
|
|
}
|
|
if errs[i].Field != v.field {
|
|
t.Errorf("expected errors to have field %s: %v", v.field, errs[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRoleZeroLengthNamespace(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeRequired,
|
|
field: "metadata.namespace",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleZeroLengthName(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: "default"},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeRequired,
|
|
field: "metadata.name",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleValidRole(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "default",
|
|
},
|
|
},
|
|
wantErr: false,
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleValidRoleNoNamespace(t *testing.T) {
|
|
ValidateClusterRoleTest{
|
|
role: rbac.ClusterRole{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
},
|
|
},
|
|
wantErr: false,
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleNonResourceURL(t *testing.T) {
|
|
ValidateClusterRoleTest{
|
|
role: rbac.ClusterRole{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{"get"},
|
|
NonResourceURLs: []string{"/*"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleNamespacedNonResourceURL(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
// non-resource URLs are invalid for namespaced rules
|
|
Verbs: []string{"get"},
|
|
NonResourceURLs: []string{"/*"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeInvalid,
|
|
field: "rules[0].nonResourceURLs",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleNonResourceURLNoVerbs(t *testing.T) {
|
|
ValidateClusterRoleTest{
|
|
role: rbac.ClusterRole{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{},
|
|
NonResourceURLs: []string{"/*"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeRequired,
|
|
field: "rules[0].verbs",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleMixedNonResourceAndResource(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
Namespace: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{"get"},
|
|
NonResourceURLs: []string{"/*"},
|
|
APIGroups: []string{"v1"},
|
|
Resources: []string{"pods"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeInvalid,
|
|
field: "rules[0].nonResourceURLs",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleValidResource(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
Namespace: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{"get"},
|
|
APIGroups: []string{"v1"},
|
|
Resources: []string{"pods"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleNoAPIGroup(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
Namespace: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{"get"},
|
|
Resources: []string{"pods"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeRequired,
|
|
field: "rules[0].apiGroups",
|
|
}.test(t)
|
|
}
|
|
|
|
func TestValidateRoleNoResources(t *testing.T) {
|
|
ValidateRoleTest{
|
|
role: rbac.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
Namespace: "default",
|
|
},
|
|
Rules: []rbac.PolicyRule{
|
|
{
|
|
Verbs: []string{"get"},
|
|
APIGroups: []string{"v1"},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errType: field.ErrorTypeRequired,
|
|
field: "rules[0].resources",
|
|
}.test(t)
|
|
}
|