Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubeadm: preserve order of user specified apiserver authorization-mode #82616

Merged
merged 1 commit into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 40 additions & 8 deletions cmd/kubeadm/app/phases/controlplane/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,52 @@ func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint
}

// getAuthzModes gets the authorization-related parameters to the api server
// Node,RBAC should be fixed in this order at the beginning
// AlwaysAllow and AlwaysDeny is ignored as they are only for testing
// Node,RBAC is the default mode if nothing is passed to kubeadm. User provided modes override
// the default.
func getAuthzModes(authzModeExtraArgs string) string {
modes := []string{
defaultMode := []string{
kubeadmconstants.ModeNode,
kubeadmconstants.ModeRBAC,
}
if strings.Contains(authzModeExtraArgs, kubeadmconstants.ModeABAC) {
modes = append(modes, kubeadmconstants.ModeABAC)

if len(authzModeExtraArgs) > 0 {
mode := []string{}
for _, requested := range strings.Split(authzModeExtraArgs, ",") {
if isValidAuthzMode(requested) {
mode = append(mode, requested)
} else {
klog.Warningf("ignoring unknown kube-apiserver authorization-mode %q", requested)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, a typo here is worth bailing out. This can end in an insecure or non-operational cluster.

}
ghouscht marked this conversation as resolved.
Show resolved Hide resolved
}

// only return the user provided mode if at least one was valid
if len(mode) > 0 {
klog.Warningf("the default kube-apiserver authorization-mode is %q; using %q",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically speaking, we don't need the warning if defaultMode is a subset of mode.

strings.Join(defaultMode, ","),
strings.Join(mode, ","),
)
return strings.Join(mode, ",")
}
}
return strings.Join(defaultMode, ",")
}

func isValidAuthzMode(authzMode string) bool {
allModes := []string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems allModes can be a map - the lookup would be more straight forward

kubeadmconstants.ModeNode,
kubeadmconstants.ModeRBAC,
kubeadmconstants.ModeWebhook,
kubeadmconstants.ModeABAC,
kubeadmconstants.ModeAlwaysAllow,
kubeadmconstants.ModeAlwaysDeny,
}
if strings.Contains(authzModeExtraArgs, kubeadmconstants.ModeWebhook) {
modes = append(modes, kubeadmconstants.ModeWebhook)

for _, mode := range allModes {
if authzMode == mode {
return true
}
}
return strings.Join(modes, ",")
return false
}

// calcNodeCidrSize determines the size of the subnets used on each node, based
Expand Down
92 changes: 58 additions & 34 deletions cmd/kubeadm/app/phases/controlplane/manifests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ func TestGetAPIServerCommand(t *testing.T) {
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
"--requestheader-allowed-names=front-proxy-client",
"--authorization-mode=Node,RBAC,ABAC",
"--authorization-mode=ABAC",
"--advertise-address=1.2.3.4",
fmt.Sprintf("--etcd-servers=https://127.0.0.1:%d", kubeadmconstants.EtcdListenClientPort),
"--etcd-cafile=" + testCertsDir + "/etcd/ca.crt",
Expand Down Expand Up @@ -500,7 +500,11 @@ func TestGetAPIServerCommand(t *testing.T) {
APIServer: kubeadmapi.APIServer{
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
ExtraArgs: map[string]string{
"authorization-mode": kubeadmconstants.ModeWebhook,
"authorization-mode": strings.Join([]string{
kubeadmconstants.ModeNode,
kubeadmconstants.ModeRBAC,
kubeadmconstants.ModeWebhook,
}, ","),
},
},
},
Expand Down Expand Up @@ -950,62 +954,82 @@ func TestGetAuthzModes(t *testing.T) {
expected: "Node,RBAC",
},
{
name: "add missing Node",
authMode: []string{kubeadmconstants.ModeRBAC},
name: "default non empty",
authMode: []string{kubeadmconstants.ModeNode, kubeadmconstants.ModeRBAC},
expected: "Node,RBAC",
},
{
name: "add missing RBAC",
authMode: []string{kubeadmconstants.ModeNode},
name: "single unspecified returning default",
authMode: []string{"FooAuthzMode"},
expected: "Node,RBAC",
},
{
name: "add defaults to ABAC",
authMode: []string{kubeadmconstants.ModeABAC},
expected: "Node,RBAC,ABAC",
name: "multiple ignored",
authMode: []string{kubeadmconstants.ModeNode, "foo", kubeadmconstants.ModeRBAC, "bar"},
expected: "Node,RBAC",
},
{
name: "add defaults to RBAC+Webhook",
authMode: []string{kubeadmconstants.ModeRBAC, kubeadmconstants.ModeWebhook},
expected: "Node,RBAC,Webhook",
name: "single mode",
authMode: []string{kubeadmconstants.ModeAlwaysDeny},
expected: "AlwaysDeny",
},
{
name: "add default to Webhook",
authMode: []string{kubeadmconstants.ModeWebhook},
expected: "Node,RBAC,Webhook",
name: "multiple special order",
authMode: []string{kubeadmconstants.ModeNode, kubeadmconstants.ModeWebhook, kubeadmconstants.ModeRBAC, kubeadmconstants.ModeABAC},
expected: "Node,Webhook,RBAC,ABAC",
},
}

for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
actual := getAuthzModes(strings.Join(rt.authMode, ","))
if actual != rt.expected {
t.Errorf("failed getAuthzModes:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}
})
}
}

func TestIsValidAuthzMode(t *testing.T) {
var tests = []struct {
mode string
valid bool
}{
{
name: "AlwaysAllow ignored",
authMode: []string{kubeadmconstants.ModeAlwaysAllow},
expected: "Node,RBAC",
mode: "Node",
valid: true,
},
{
name: "AlwaysDeny ignored",
authMode: []string{kubeadmconstants.ModeAlwaysDeny},
expected: "Node,RBAC",
mode: "RBAC",
valid: true,
},
{
name: "Unspecified ignored",
authMode: []string{"FooAuthzMode"},
expected: "Node,RBAC",
mode: "ABAC",
valid: true,
},
{
name: "Multiple ignored",
authMode: []string{kubeadmconstants.ModeAlwaysAllow, kubeadmconstants.ModeAlwaysDeny, "foo"},
expected: "Node,RBAC",
mode: "AlwaysAllow",
valid: true,
},
{
name: "all",
authMode: []string{kubeadmconstants.ModeNode, kubeadmconstants.ModeRBAC, kubeadmconstants.ModeWebhook, kubeadmconstants.ModeABAC},
expected: "Node,RBAC,ABAC,Webhook",
mode: "Webhook",
valid: true,
},
{
mode: "AlwaysDeny",
valid: true,
},
{
mode: "Foo",
valid: false,
},
}

for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
actual := getAuthzModes(strings.Join(rt.authMode, ","))
if actual != rt.expected {
t.Errorf("failed getAuthzParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
t.Run(rt.mode, func(t *testing.T) {
isValid := isValidAuthzMode(rt.mode)
if isValid != rt.valid {
t.Errorf("failed isValidAuthzMode:\nexpected:\n%v\nsaw:\n%v", rt.valid, isValid)
}
})
}
Expand Down