667 lines
25 KiB
Go
667 lines
25 KiB
Go
/*
|
|
Copyright 2019 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 flowcontrol
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"k8s.io/utils/clock"
|
|
"k8s.io/utils/ptr"
|
|
|
|
flowcontrol "k8s.io/api/flowcontrol/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
fcboot "k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
|
"k8s.io/apiserver/pkg/endpoints/request"
|
|
fq "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing"
|
|
fqtesting "k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/testing"
|
|
fcfmt "k8s.io/apiserver/pkg/util/flowcontrol/format"
|
|
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
|
|
)
|
|
|
|
var noRestraintQSF = fqtesting.NewNoRestraintFactory()
|
|
|
|
// genPL creates a valid PriorityLevelConfiguration with the given
|
|
// name and randomly generated spec. The given name must not be one
|
|
// of the mandatory ones.
|
|
func genPL(rng *rand.Rand, name string) *flowcontrol.PriorityLevelConfiguration {
|
|
plc := &flowcontrol.PriorityLevelConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
|
Spec: flowcontrol.PriorityLevelConfigurationSpec{
|
|
Type: flowcontrol.PriorityLevelEnablementLimited,
|
|
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
|
|
NominalConcurrencyShares: ptr.To(int32(rng.Int31n(100) + 1)),
|
|
LimitResponse: flowcontrol.LimitResponse{
|
|
Type: flowcontrol.LimitResponseTypeReject}}}}
|
|
if rng.Float32() < 0.95 {
|
|
plc.Spec.Limited.LimitResponse.Type = flowcontrol.LimitResponseTypeQueue
|
|
hs := rng.Int31n(5) + 1
|
|
plc.Spec.Limited.LimitResponse.Queuing = &flowcontrol.QueuingConfiguration{
|
|
Queues: hs + rng.Int31n(20),
|
|
HandSize: hs,
|
|
QueueLengthLimit: 5}
|
|
}
|
|
labelVals := []string{"test"}
|
|
_, err := queueSetCompleterForPL(noRestraintQSF, nil, plc, metrics.RatioedGaugeVecPhasedElementPair(metrics.PriorityLevelConcurrencyGaugeVec, 1, 1, labelVals), metrics.PriorityLevelExecutionSeatsGaugeVec.NewForLabelValuesSafe(0, 1, labelVals), fq.NewNamedIntegrator(clock.RealClock{}, name))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return plc
|
|
}
|
|
|
|
// A FlowSchema together with characteristics relevant to testing
|
|
type fsTestingRecord struct {
|
|
fs *flowcontrol.FlowSchema
|
|
// Does this reference an existing priority level?
|
|
wellFormed bool
|
|
matchesAllResourceRequests bool
|
|
matchesAllNonResourceRequests bool
|
|
// maps `matches bool` to `isResourceRequest bool` to digests
|
|
digests map[bool]map[bool][]RequestDigest
|
|
}
|
|
|
|
func (ftr *fsTestingRecord) addDigest(digest RequestDigest, matches bool) {
|
|
ftr.digests[matches][digest.RequestInfo.IsResourceRequest] = append(ftr.digests[matches][digest.RequestInfo.IsResourceRequest], digest)
|
|
}
|
|
|
|
func (ftr *fsTestingRecord) addDigests(digests []RequestDigest, matches bool) {
|
|
for _, digest := range digests {
|
|
ftr.addDigest(digest, matches)
|
|
}
|
|
}
|
|
|
|
var flowDistinguisherMethodTypes = sets.NewString(
|
|
string(flowcontrol.FlowDistinguisherMethodByUserType),
|
|
string(flowcontrol.FlowDistinguisherMethodByNamespaceType),
|
|
)
|
|
|
|
var mandFTRExempt = &fsTestingRecord{
|
|
fs: fcboot.MandatoryFlowSchemaExempt,
|
|
wellFormed: true,
|
|
digests: map[bool]map[bool][]RequestDigest{
|
|
false: {
|
|
false: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: false,
|
|
Path: "/foo/bar",
|
|
Verb: "frobulate"},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, "nogroup"},
|
|
},
|
|
}},
|
|
true: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: true,
|
|
Verb: "mandate",
|
|
APIGroup: "nogroup",
|
|
Namespace: "nospace",
|
|
Resource: "nons",
|
|
},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, "nogroup"},
|
|
},
|
|
}},
|
|
},
|
|
true: {
|
|
false: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: false,
|
|
Path: "/foo/bar",
|
|
Verb: "frobulate"},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, user.SystemPrivilegedGroup},
|
|
},
|
|
}},
|
|
true: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: true,
|
|
Verb: "mandate",
|
|
APIGroup: "nogroup",
|
|
Namespace: "nospace",
|
|
Resource: "nons",
|
|
},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, user.SystemPrivilegedGroup},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
|
|
var mandFTRCatchAll = &fsTestingRecord{
|
|
fs: fcboot.MandatoryFlowSchemaCatchAll,
|
|
wellFormed: true,
|
|
digests: map[bool]map[bool][]RequestDigest{
|
|
false: {},
|
|
true: {
|
|
false: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: false,
|
|
Path: "/foo/bar",
|
|
Verb: "frobulate"},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, "nogroup"},
|
|
},
|
|
}},
|
|
true: {{
|
|
RequestInfo: &request.RequestInfo{
|
|
IsResourceRequest: true,
|
|
Verb: "mandate",
|
|
APIGroup: "nogroup",
|
|
Namespace: "nospace",
|
|
Resource: "nons",
|
|
},
|
|
User: &user.DefaultInfo{
|
|
Name: "nobody",
|
|
Groups: []string{user.AllAuthenticated, "nogroup"},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
|
|
// genFS creates a valid FlowSchema with the given name and randomly
|
|
// generated spec, along with characteristics relevant to testing.
|
|
// When all the FlowSchemas in a collection are generated with
|
|
// different names: (a) the matching digests match only the schema for
|
|
// which they were generated, and (b) the non-matching digests do not
|
|
// match any schema in the collection. The generated spec is
|
|
// relatively likely to be well formed but might not be. An ill
|
|
// formed spec references a priority level drawn from badPLNames.
|
|
// goodPLNames may be empty, but badPLNames may not.
|
|
func genFS(t *testing.T, rng *rand.Rand, name string, mayMatchClusterScope bool, goodPLNames, badPLNames sets.String) *fsTestingRecord {
|
|
fs := &flowcontrol.FlowSchema{
|
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
|
Spec: flowcontrol.FlowSchemaSpec{}}
|
|
// 5% chance of zero rules, otherwise draw from 1--6 biased low
|
|
nRules := (1 + rng.Intn(3)) * (1 + rng.Intn(2)) * ((19 + rng.Intn(20)) / 20)
|
|
ftr := &fsTestingRecord{fs: fs,
|
|
wellFormed: true,
|
|
matchesAllResourceRequests: nRules > 0 && rng.Float32() < 0.1,
|
|
matchesAllNonResourceRequests: nRules > 0 && rng.Float32() < 0.1,
|
|
digests: map[bool]map[bool][]RequestDigest{
|
|
false: {false: {}, true: {}},
|
|
true: {false: {}, true: {}}},
|
|
}
|
|
dangleStatus := flowcontrol.ConditionFalse
|
|
if rng.Float32() < 0.9 && len(goodPLNames) > 0 {
|
|
fs.Spec.PriorityLevelConfiguration = flowcontrol.PriorityLevelConfigurationReference{Name: pickSetString(rng, goodPLNames)}
|
|
} else {
|
|
fs.Spec.PriorityLevelConfiguration = flowcontrol.PriorityLevelConfigurationReference{Name: pickSetString(rng, badPLNames)}
|
|
ftr.wellFormed = false
|
|
dangleStatus = flowcontrol.ConditionTrue
|
|
}
|
|
fs.Status.Conditions = []flowcontrol.FlowSchemaCondition{{
|
|
Type: flowcontrol.FlowSchemaConditionDangling,
|
|
Status: dangleStatus}}
|
|
fs.Spec.MatchingPrecedence = rng.Int31n(9997) + 2
|
|
if rng.Float32() < 0.8 {
|
|
fdmt := flowcontrol.FlowDistinguisherMethodType(pickSetString(rng, flowDistinguisherMethodTypes))
|
|
fs.Spec.DistinguisherMethod = &flowcontrol.FlowDistinguisherMethod{Type: fdmt}
|
|
}
|
|
fs.Spec.Rules = []flowcontrol.PolicyRulesWithSubjects{}
|
|
everyResourceMatcher := -1
|
|
if ftr.matchesAllResourceRequests {
|
|
if mayMatchClusterScope {
|
|
everyResourceMatcher = 0
|
|
} else {
|
|
everyResourceMatcher = rng.Intn(nRules)
|
|
}
|
|
}
|
|
everyNonResourceMatcher := -1
|
|
if ftr.matchesAllNonResourceRequests {
|
|
if mayMatchClusterScope {
|
|
everyNonResourceMatcher = 0
|
|
} else {
|
|
everyNonResourceMatcher = rng.Intn(nRules)
|
|
}
|
|
}
|
|
// Allow only one rule if mayMatchClusterScope because that breaks
|
|
// cross-rule exclusion.
|
|
for i := 0; i < nRules && (i == 0 || !mayMatchClusterScope); i++ {
|
|
rule, ruleMatchingRDigests, ruleMatchingNDigests, ruleSkippingRDigests, ruleSkippingNDigests := genPolicyRuleWithSubjects(t, rng, fmt.Sprintf("%s-%d", name, i+1), mayMatchClusterScope, ftr.matchesAllResourceRequests, ftr.matchesAllNonResourceRequests, i == everyResourceMatcher, i == everyNonResourceMatcher)
|
|
fs.Spec.Rules = append(fs.Spec.Rules, rule)
|
|
ftr.addDigests(ruleMatchingRDigests, true)
|
|
ftr.addDigests(ruleMatchingNDigests, true)
|
|
ftr.addDigests(ruleSkippingRDigests, false)
|
|
ftr.addDigests(ruleSkippingNDigests, false)
|
|
}
|
|
if nRules == 0 {
|
|
var skippingRDigests, skippingNDigests []RequestDigest
|
|
_, _, _, skippingRDigests, skippingNDigests = genPolicyRuleWithSubjects(t, rng, name+"-1", false, false, false, false, false)
|
|
ftr.addDigests(skippingRDigests, false)
|
|
ftr.addDigests(skippingNDigests, false)
|
|
}
|
|
if testDebugLogs {
|
|
t.Logf("Returning name=%s, plRef=%q, wellFormed=%v, matchesAllResourceRequests=%v, matchesAllNonResourceRequests=%v for mayMatchClusterScope=%v", fs.Name, fs.Spec.PriorityLevelConfiguration.Name, ftr.wellFormed, ftr.matchesAllResourceRequests, ftr.matchesAllNonResourceRequests, mayMatchClusterScope)
|
|
}
|
|
return ftr
|
|
}
|
|
|
|
var noextra = make(map[string][]string)
|
|
|
|
// Generate one valid PolicyRulesWithSubjects. Also returns: matching
|
|
// resource-style digests, matching non-resource-style digests,
|
|
// skipping (i.e., not matching the generated rule) resource-style
|
|
// digests, skipping non-resource-style digests. When a collection of
|
|
// rules is generated with unique prefixes, the skipping digests for
|
|
// each rule match no rules in the collection. The
|
|
// someMatchesAllResourceRequests and
|
|
// someMatchesAllNonResourceRequests parameters indicate whether any
|
|
// rule in the collection matches all of the relevant sort of request;
|
|
// these imply the respective returned slice of counterexamples will
|
|
// be empty. The matchAllResourceRequests and
|
|
// matchAllNonResourceRequests parameters indicate whether the
|
|
// generated rule should match all of the relevant sort. The
|
|
// cross-rule exclusion is based on using names that start with the
|
|
// given prefix --- which can not be done for the namespace of a
|
|
// cluster-scoped request. Thus, these are normally excluded. When
|
|
// mayMatchClusterScope==true the generated rule may be cluster-scoped
|
|
// and there is no promise of cross-rule exclusion.
|
|
func genPolicyRuleWithSubjects(t *testing.T, rng *rand.Rand, pfx string, mayMatchClusterScope, someMatchesAllResourceRequests, someMatchesAllNonResourceRequests, matchAllResourceRequests, matchAllNonResourceRequests bool) (flowcontrol.PolicyRulesWithSubjects, []RequestDigest, []RequestDigest, []RequestDigest, []RequestDigest) {
|
|
subjects := []flowcontrol.Subject{}
|
|
matchingUIs := []user.Info{}
|
|
skippingUIs := []user.Info{}
|
|
resourceRules := []flowcontrol.ResourcePolicyRule{}
|
|
nonResourceRules := []flowcontrol.NonResourcePolicyRule{}
|
|
matchingRRIs := []*request.RequestInfo{}
|
|
skippingRRIs := []*request.RequestInfo{}
|
|
matchingNRIs := []*request.RequestInfo{}
|
|
skippingNRIs := []*request.RequestInfo{}
|
|
nSubj := rng.Intn(4)
|
|
for i := 0; i < nSubj; i++ {
|
|
subject, smus, ssus := genSubject(rng, fmt.Sprintf("%s-%d", pfx, i+1))
|
|
subjects = append(subjects, subject)
|
|
matchingUIs = append(matchingUIs, smus...)
|
|
skippingUIs = append(skippingUIs, ssus...)
|
|
}
|
|
if matchAllResourceRequests || matchAllNonResourceRequests {
|
|
switch rng.Intn(3) {
|
|
case 0:
|
|
subjects = append(subjects, mkUserSubject("*"))
|
|
case 1:
|
|
subjects = append(subjects, mkGroupSubject("*"))
|
|
default:
|
|
subjects = append(subjects, mkGroupSubject("system:authenticated"), mkGroupSubject("system:unauthenticated"))
|
|
}
|
|
matchingUIs = append(matchingUIs, skippingUIs...)
|
|
}
|
|
if someMatchesAllResourceRequests || someMatchesAllNonResourceRequests {
|
|
skippingUIs = []user.Info{}
|
|
} else if nSubj == 0 {
|
|
_, _, skippingUIs = genSubject(rng, pfx+"-o")
|
|
}
|
|
var nRR, nNRR int
|
|
for nRR+nNRR == 0 || matchAllResourceRequests && nRR == 0 || matchAllNonResourceRequests && nNRR == 0 {
|
|
nRR = rng.Intn(4)
|
|
nNRR = rng.Intn(4)
|
|
}
|
|
allResourceMatcher := -1
|
|
if matchAllResourceRequests {
|
|
allResourceMatcher = rng.Intn(nRR)
|
|
}
|
|
// Allow only one resource rule if mayMatchClusterScope because
|
|
// that breaks cross-rule exclusion.
|
|
for i := 0; i < nRR && (i == 0 || !mayMatchClusterScope); i++ {
|
|
rr, rmrs, rsrs := genResourceRule(rng, fmt.Sprintf("%s-%d", pfx, i+1), mayMatchClusterScope, i == allResourceMatcher, someMatchesAllResourceRequests)
|
|
resourceRules = append(resourceRules, rr)
|
|
matchingRRIs = append(matchingRRIs, rmrs...)
|
|
skippingRRIs = append(skippingRRIs, rsrs...)
|
|
}
|
|
if nRR == 0 {
|
|
_, _, skippingRRIs = genResourceRule(rng, pfx+"-o", mayMatchClusterScope, false, someMatchesAllResourceRequests)
|
|
}
|
|
allNonResourceMatcher := -1
|
|
if matchAllNonResourceRequests {
|
|
allNonResourceMatcher = rng.Intn(nNRR)
|
|
}
|
|
for i := 0; i < nNRR; i++ {
|
|
nrr, nmrs, nsrs := genNonResourceRule(rng, fmt.Sprintf("%s-%d", pfx, i+1), i == allNonResourceMatcher, someMatchesAllNonResourceRequests)
|
|
nonResourceRules = append(nonResourceRules, nrr)
|
|
matchingNRIs = append(matchingNRIs, nmrs...)
|
|
skippingNRIs = append(skippingNRIs, nsrs...)
|
|
}
|
|
if nRR == 0 {
|
|
_, _, skippingNRIs = genNonResourceRule(rng, pfx+"-o", false, someMatchesAllNonResourceRequests)
|
|
}
|
|
rule := flowcontrol.PolicyRulesWithSubjects{Subjects: subjects, ResourceRules: resourceRules, NonResourceRules: nonResourceRules}
|
|
if testDebugLogs {
|
|
t.Logf("For pfx=%s, mayMatchClusterScope=%v, someMatchesAllResourceRequests=%v, someMatchesAllNonResourceRequests=%v, marr=%v, manrr=%v: generated prws=%s, mu=%s, su=%s, mrr=%s, mnr=%s, srr=%s, snr=%s", pfx, mayMatchClusterScope, someMatchesAllResourceRequests, someMatchesAllNonResourceRequests, matchAllResourceRequests, matchAllNonResourceRequests, fcfmt.Fmt(rule), fcfmt.Fmt(matchingUIs), fcfmt.Fmt(skippingUIs), fcfmt.Fmt(matchingRRIs), fcfmt.Fmt(matchingNRIs), fcfmt.Fmt(skippingRRIs), fcfmt.Fmt(skippingNRIs))
|
|
}
|
|
matchingRDigests := cross(matchingUIs, matchingRRIs)
|
|
skippingRDigests := append(append(cross(matchingUIs, skippingRRIs),
|
|
cross(skippingUIs, matchingRRIs)...),
|
|
cross(skippingUIs, skippingRRIs)...)
|
|
matchingNDigests := cross(matchingUIs, matchingNRIs)
|
|
skippingNDigests := append(append(cross(matchingUIs, skippingNRIs),
|
|
cross(skippingUIs, matchingNRIs)...),
|
|
cross(skippingUIs, skippingNRIs)...)
|
|
matchingRDigests = shuffleAndTakeDigests(t, rng, &rule, true, matchingRDigests, (1+rng.Intn(2))*(1+rng.Intn(2)))
|
|
skippingRDigests = shuffleAndTakeDigests(t, rng, &rule, false, skippingRDigests, (1+rng.Intn(2))*(1+rng.Intn(2)))
|
|
matchingNDigests = shuffleAndTakeDigests(t, rng, &rule, true, matchingNDigests, (1+rng.Intn(2))*(1+rng.Intn(2)))
|
|
skippingNDigests = shuffleAndTakeDigests(t, rng, &rule, false, skippingNDigests, (1+rng.Intn(2))*(1+rng.Intn(2)))
|
|
return rule, matchingRDigests, matchingNDigests, skippingRDigests, skippingNDigests
|
|
}
|
|
|
|
func cross(uis []user.Info, ris []*request.RequestInfo) []RequestDigest {
|
|
ans := make([]RequestDigest, 0, len(uis)*len(ris))
|
|
for _, ui := range uis {
|
|
for _, ri := range ris {
|
|
ans = append(ans, RequestDigest{RequestInfo: ri, User: ui})
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
func shuffleAndTakeDigests(t *testing.T, rng *rand.Rand, rule *flowcontrol.PolicyRulesWithSubjects, toMatch bool, digests []RequestDigest, n int) []RequestDigest {
|
|
ans := make([]RequestDigest, 0, n)
|
|
for len(ans) < n && len(digests) > 0 {
|
|
i := rng.Intn(len(digests))
|
|
digest := digests[i]
|
|
ans = append(ans, digest)
|
|
digests[i] = digests[len(digests)-1]
|
|
digests = digests[:len(digests)-1]
|
|
if rule != nil {
|
|
thisMatches := matchesPolicyRule(digest, rule)
|
|
if toMatch {
|
|
if testDebugLogs {
|
|
t.Logf("Added matching digest %#+v", digest)
|
|
}
|
|
if !thisMatches {
|
|
t.Errorf("Fail in check: rule %s does not match digest %#+v", fcfmt.Fmt(rule), digest)
|
|
}
|
|
} else {
|
|
if testDebugLogs {
|
|
t.Logf("Added skipping digest %#+v", digest)
|
|
}
|
|
if thisMatches {
|
|
t.Errorf("Fail in check: rule %s matches digest %#+v", fcfmt.Fmt(rule), digest)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
var uCounter uint32 = 1
|
|
|
|
func uniqify(in RequestDigest) RequestDigest {
|
|
u1 := in.User.(*user.DefaultInfo)
|
|
u2 := *u1
|
|
u2.Extra = map[string][]string{"u": {fmt.Sprintf("z%d", atomic.AddUint32(&uCounter, 1))}}
|
|
return RequestDigest{User: &u2, RequestInfo: in.RequestInfo}
|
|
}
|
|
|
|
// genSubject returns a randomly generated valid Subject that matches
|
|
// on some name(s) starting with the given prefix. The first returned
|
|
// list contains members that match the generated Subject and involve
|
|
// names that begin with the given prefix. The second returned list
|
|
// contains members that mismatch the generated Subject and involve
|
|
// names that begin with the given prefix.
|
|
func genSubject(rng *rand.Rand, pfx string) (flowcontrol.Subject, []user.Info, []user.Info) {
|
|
subject := flowcontrol.Subject{}
|
|
var matchingUIs, skippingUIs []user.Info
|
|
x := rng.Float32()
|
|
switch {
|
|
case x < 0.33:
|
|
subject.Kind = flowcontrol.SubjectKindUser
|
|
subject.User, matchingUIs, skippingUIs = genUser(rng, pfx)
|
|
case x < 0.67:
|
|
subject.Kind = flowcontrol.SubjectKindGroup
|
|
subject.Group, matchingUIs, skippingUIs = genGroup(rng, pfx)
|
|
default:
|
|
subject.Kind = flowcontrol.SubjectKindServiceAccount
|
|
subject.ServiceAccount, matchingUIs, skippingUIs = genServiceAccount(rng, pfx)
|
|
}
|
|
return subject, matchingUIs, skippingUIs
|
|
}
|
|
|
|
func genUser(rng *rand.Rand, pfx string) (*flowcontrol.UserSubject, []user.Info, []user.Info) {
|
|
mui := &user.DefaultInfo{
|
|
Name: pfx + "-u",
|
|
UID: "good-id",
|
|
Groups: []string{pfx + "-g1", mg(rng), pfx + "-g2"},
|
|
Extra: noextra}
|
|
skips := []user.Info{&user.DefaultInfo{
|
|
Name: mui.Name + "x",
|
|
UID: mui.UID,
|
|
Groups: mui.Groups,
|
|
Extra: mui.Extra}}
|
|
return &flowcontrol.UserSubject{Name: mui.Name}, []user.Info{mui}, skips
|
|
}
|
|
|
|
var groupCover = []string{"system:authenticated", "system:unauthenticated"}
|
|
|
|
func mg(rng *rand.Rand) string {
|
|
return groupCover[rng.Intn(len(groupCover))]
|
|
}
|
|
|
|
func mkUserSubject(username string) flowcontrol.Subject {
|
|
return flowcontrol.Subject{
|
|
Kind: flowcontrol.SubjectKindUser,
|
|
User: &flowcontrol.UserSubject{Name: username},
|
|
}
|
|
}
|
|
|
|
func mkGroupSubject(group string) flowcontrol.Subject {
|
|
return flowcontrol.Subject{
|
|
Kind: flowcontrol.SubjectKindGroup,
|
|
Group: &flowcontrol.GroupSubject{Name: group},
|
|
}
|
|
}
|
|
|
|
func genGroup(rng *rand.Rand, pfx string) (*flowcontrol.GroupSubject, []user.Info, []user.Info) {
|
|
name := pfx + "-g"
|
|
ui := &user.DefaultInfo{
|
|
Name: pfx + "-u",
|
|
UID: "good-id",
|
|
Groups: []string{name},
|
|
Extra: noextra}
|
|
if rng.Intn(2) == 0 {
|
|
ui.Groups = append([]string{mg(rng)}, ui.Groups...)
|
|
} else {
|
|
ui.Groups = append(ui.Groups, mg(rng))
|
|
}
|
|
if rng.Intn(3) == 0 {
|
|
ui.Groups = append([]string{pfx + "-h"}, ui.Groups...)
|
|
}
|
|
if rng.Intn(3) == 0 {
|
|
ui.Groups = append(ui.Groups, pfx+"-i")
|
|
}
|
|
skipper := &user.DefaultInfo{
|
|
Name: pfx + "-u",
|
|
UID: "bad-id",
|
|
Groups: []string{pfx + "-j", mg(rng)},
|
|
Extra: noextra}
|
|
if rng.Intn(2) == 0 {
|
|
skipper.Groups = append(skipper.Groups, pfx+"-k")
|
|
}
|
|
return &flowcontrol.GroupSubject{Name: name}, []user.Info{ui}, []user.Info{skipper}
|
|
}
|
|
|
|
func genServiceAccount(rng *rand.Rand, pfx string) (*flowcontrol.ServiceAccountSubject, []user.Info, []user.Info) {
|
|
ns := pfx + "-ns"
|
|
name := pfx + "-n"
|
|
mname := name
|
|
if rng.Float32() < 0.05 {
|
|
mname = "*"
|
|
}
|
|
mui := &user.DefaultInfo{
|
|
Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, name),
|
|
UID: "good-id",
|
|
Groups: []string{pfx + "-g1", mg(rng), pfx + "-g2"},
|
|
Extra: noextra}
|
|
var skips []user.Info
|
|
if mname == "*" || rng.Intn(2) == 0 {
|
|
skips = []user.Info{&user.DefaultInfo{
|
|
Name: fmt.Sprintf("system:serviceaccount:%sx:%s", ns, name),
|
|
UID: "bad-id",
|
|
Groups: mui.Groups,
|
|
Extra: mui.Extra}}
|
|
} else {
|
|
skips = []user.Info{&user.DefaultInfo{
|
|
Name: fmt.Sprintf("system:serviceaccount:%s:%sx", ns, name),
|
|
UID: "bad-id",
|
|
Groups: mui.Groups,
|
|
Extra: mui.Extra}}
|
|
}
|
|
return &flowcontrol.ServiceAccountSubject{Namespace: ns, Name: mname}, []user.Info{mui}, skips
|
|
}
|
|
|
|
// genResourceRule randomly generates a valid ResourcePolicyRule and lists
|
|
// of matching and non-matching `*request.RequestInfo`.
|
|
func genResourceRule(rng *rand.Rand, pfx string, mayMatchClusterScope, matchAllResources, someMatchesAllResources bool) (flowcontrol.ResourcePolicyRule, []*request.RequestInfo, []*request.RequestInfo) {
|
|
namespaces := []string{pfx + "-n1", pfx + "-n2", pfx + "-n3"}
|
|
rnamespaces := namespaces
|
|
if mayMatchClusterScope && rng.Float32() < 0.1 {
|
|
namespaces[0] = ""
|
|
rnamespaces = namespaces[1:]
|
|
}
|
|
rr := flowcontrol.ResourcePolicyRule{
|
|
Verbs: []string{pfx + "-v1", pfx + "-v2", pfx + "-v3"},
|
|
APIGroups: []string{pfx + ".g1", pfx + ".g2", pfx + ".g3"},
|
|
Resources: []string{pfx + "-r1s", pfx + "-r2s", pfx + "-r3s"},
|
|
ClusterScope: namespaces[0] == "",
|
|
Namespaces: rnamespaces}
|
|
matchingRIs := genRRIs(rng, 3, rr.Verbs, rr.APIGroups, rr.Resources, namespaces)
|
|
var skippingRIs []*request.RequestInfo
|
|
if !someMatchesAllResources {
|
|
skipNSs := []string{pfx + "-n4", pfx + "-n5", pfx + "-n6"}
|
|
if mayMatchClusterScope && rr.Namespaces[0] != "" && rng.Float32() < 0.1 {
|
|
skipNSs[0] = ""
|
|
}
|
|
skippingRIs = genRRIs(rng, 3,
|
|
[]string{pfx + "-v4", pfx + "-v5", pfx + "-v6"},
|
|
[]string{pfx + ".g4", pfx + ".g5", pfx + ".g6"},
|
|
[]string{pfx + "-r4s", pfx + "-r5s", pfx + "-r6s"},
|
|
skipNSs)
|
|
}
|
|
// choose a proper subset of fields to wildcard; only matters if not matching all
|
|
starMask := rng.Intn(15)
|
|
if matchAllResources || starMask&1 == 1 && rng.Float32() < 0.1 {
|
|
rr.Verbs = []string{flowcontrol.VerbAll}
|
|
}
|
|
if matchAllResources || starMask&2 == 2 && rng.Float32() < 0.1 {
|
|
rr.APIGroups = []string{flowcontrol.APIGroupAll}
|
|
}
|
|
if matchAllResources || starMask&4 == 4 && rng.Float32() < 0.1 {
|
|
rr.Resources = []string{flowcontrol.ResourceAll}
|
|
}
|
|
if matchAllResources || starMask&8 == 8 && rng.Float32() < 0.1 {
|
|
rr.ClusterScope = true
|
|
rr.Namespaces = []string{flowcontrol.NamespaceEvery}
|
|
}
|
|
return rr, matchingRIs, skippingRIs
|
|
}
|
|
|
|
func genRRIs(rng *rand.Rand, m int, verbs, apiGroups, resources, namespaces []string) []*request.RequestInfo {
|
|
nv := len(verbs)
|
|
ng := len(apiGroups)
|
|
nr := len(resources)
|
|
nn := len(namespaces)
|
|
coords := chooseInts(rng, nv*ng*nr*nn, m)
|
|
ans := make([]*request.RequestInfo, 0, m)
|
|
for _, coord := range coords {
|
|
ans = append(ans, &request.RequestInfo{
|
|
IsResourceRequest: true,
|
|
Verb: verbs[coord%nv],
|
|
APIGroup: apiGroups[coord/nv%ng],
|
|
Resource: resources[coord/nv/ng%nr],
|
|
Namespace: namespaces[coord/nv/ng/nr]})
|
|
}
|
|
return ans
|
|
}
|
|
|
|
func genNRRIs(rng *rand.Rand, m int, verbs, urls []string) []*request.RequestInfo {
|
|
nv := len(verbs)
|
|
nu := len(urls)
|
|
coords := chooseInts(rng, nv*nu, m)
|
|
ans := make([]*request.RequestInfo, 0, m)
|
|
for _, coord := range coords {
|
|
ri := &request.RequestInfo{
|
|
IsResourceRequest: false,
|
|
Verb: verbs[coord%nv],
|
|
Path: urls[coord/nv]}
|
|
if rng.Intn(2) == 1 {
|
|
ri.Path = ri.Path + "/more"
|
|
}
|
|
ans = append(ans, ri)
|
|
}
|
|
return ans
|
|
}
|
|
|
|
func chooseInts(rng *rand.Rand, n, m int) []int {
|
|
ans := sets.NewInt()
|
|
for len(ans) < m {
|
|
i := rng.Intn(n)
|
|
if ans.Has(i) {
|
|
continue
|
|
}
|
|
ans.Insert(i)
|
|
}
|
|
return ans.List()
|
|
}
|
|
|
|
// genNonResourceRule returns a randomly generated valid
|
|
// NonResourcePolicyRule and lists of matching and non-matching
|
|
// `*request.RequestInfo`.
|
|
func genNonResourceRule(rng *rand.Rand, pfx string, matchAllNonResources, someMatchesAllNonResources bool) (flowcontrol.NonResourcePolicyRule, []*request.RequestInfo, []*request.RequestInfo) {
|
|
nrr := flowcontrol.NonResourcePolicyRule{
|
|
Verbs: []string{pfx + "-v1", pfx + "-v2", pfx + "-v3"},
|
|
NonResourceURLs: []string{"/" + pfx + "/g/p1", "/" + pfx + "/g/p2", "/" + pfx + "/g/p3"},
|
|
}
|
|
matchingRIs := genNRRIs(rng, 3, nrr.Verbs, nrr.NonResourceURLs)
|
|
var skippingRIs []*request.RequestInfo
|
|
if !someMatchesAllNonResources {
|
|
skippingRIs = genNRRIs(rng, 3,
|
|
[]string{pfx + "-v4", pfx + "-v5", pfx + "-v6"},
|
|
[]string{"/" + pfx + "/b/p1", "/" + pfx + "/b/p2", "/" + pfx + "/b/p3"})
|
|
}
|
|
// choose a proper subset of fields to consider wildcarding; only matters if not matching all
|
|
starMask := rng.Intn(3)
|
|
if matchAllNonResources || starMask&1 == 1 && rng.Float32() < 0.1 {
|
|
nrr.Verbs = []string{flowcontrol.VerbAll}
|
|
}
|
|
if matchAllNonResources || starMask&2 == 2 && rng.Float32() < 0.1 {
|
|
nrr.NonResourceURLs = []string{"*"}
|
|
} else {
|
|
nrr.NonResourceURLs[rng.Intn(3)] = "/" + pfx + "/g/*"
|
|
nrr.NonResourceURLs[rng.Intn(3)] = "/" + pfx + "/g"
|
|
}
|
|
return nrr, matchingRIs, skippingRIs
|
|
}
|
|
|
|
func pickSetString(rng *rand.Rand, set sets.String) string {
|
|
i, n := 0, rng.Intn(len(set))
|
|
for s := range set {
|
|
if i == n {
|
|
return s
|
|
}
|
|
i++
|
|
}
|
|
panic("empty set")
|
|
}
|