diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 59550623b13..83bdd694290 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -99,8 +99,6 @@ ENABLE_TRACING=${ENABLE_TRACING:-false} # enable Kubernetes-CSI snapshotter ENABLE_CSI_SNAPSHOTTER=${ENABLE_CSI_SNAPSHOTTER:-false} -# RBAC Mode options -AUTHORIZATION_MODE=${AUTHORIZATION_MODE:-"Node,RBAC"} KUBECONFIG_TOKEN=${KUBECONFIG_TOKEN:-""} AUTH_ARGS=${AUTH_ARGS:-""} @@ -494,10 +492,19 @@ function start_apiserver { # Append security_admission plugin ENABLE_ADMISSION_PLUGINS="${ENABLE_ADMISSION_PLUGINS}${security_admission}" - authorizer_arg="" - if [[ -n "${AUTHORIZATION_MODE}" ]]; then - authorizer_arg="--authorization-mode=${AUTHORIZATION_MODE}" + authorizer_args=() + if [[ -n "${AUTHORIZATION_CONFIG:-}" ]]; then + authorizer_args+=("--authorization-config=${AUTHORIZATION_CONFIG}") + else + if [[ -n "${AUTHORIZATION_MODE:-Node,RBAC}" ]]; then + authorizer_args+=("--authorization-mode=${AUTHORIZATION_MODE:-Node,RBAC}") + fi + authorizer_args+=( + "--authorization-webhook-config-file=${AUTHORIZATION_WEBHOOK_CONFIG_FILE}" + "--authentication-token-webhook-config-file=${AUTHENTICATION_WEBHOOK_CONFIG_FILE}" + ) fi + priv_arg="" if [[ -n "${ALLOW_PRIVILEGED}" ]]; then priv_arg="--allow-privileged=${ALLOW_PRIVILEGED}" @@ -570,7 +577,7 @@ EOF APISERVER_LOG=${LOG_DIR}/kube-apiserver.log # shellcheck disable=SC2086 - ${CONTROLPLANE_SUDO} "${GO_OUT}/kube-apiserver" "${authorizer_arg}" "${priv_arg}" ${runtime_config} \ + ${CONTROLPLANE_SUDO} "${GO_OUT}/kube-apiserver" "${authorizer_args[@]}" "${priv_arg}" ${runtime_config} \ ${cloud_config_arg} \ "${advertise_address}" \ "${node_port_range}" \ @@ -578,8 +585,6 @@ EOF --vmodule="${LOG_SPEC}" \ --audit-policy-file="${AUDIT_POLICY_FILE}" \ --audit-log-path="${LOG_DIR}/kube-apiserver-audit.log" \ - --authorization-webhook-config-file="${AUTHORIZATION_WEBHOOK_CONFIG_FILE}" \ - --authentication-token-webhook-config-file="${AUTHENTICATION_WEBHOOK_CONFIG_FILE}" \ --cert-dir="${CERT_DIR}" \ --egress-selector-config-file="${EGRESS_SELECTOR_CONFIG_FILE:-}" \ --client-ca-file="${CERT_DIR}/client-ca.crt" \ @@ -613,14 +618,15 @@ EOF --cors-allowed-origins="${API_CORS_ALLOWED_ORIGINS}" >"${APISERVER_LOG}" 2>&1 & APISERVER_PID=$! + # Create kubeconfigs for all components, using client certs + kube::util::write_client_kubeconfig "${CONTROLPLANE_SUDO}" "${CERT_DIR}" "${ROOT_CA_FILE}" "${API_HOST}" "${API_SECURE_PORT}" admin + ${CONTROLPLANE_SUDO} chown "${USER}" "${CERT_DIR}/client-admin.key" # make readable for kubectl + # Wait for kube-apiserver to come up before launching the rest of the components. echo "Waiting for apiserver to come up" kube::util::wait_for_url "https://${API_HOST_IP}:${API_SECURE_PORT}/healthz" "apiserver: " 1 "${WAIT_FOR_URL_API_SERVER}" "${MAX_TIME_FOR_URL_API_SERVER}" \ || { echo "check apiserver logs: ${APISERVER_LOG}" ; exit 1 ; } - # Create kubeconfigs for all components, using client certs - kube::util::write_client_kubeconfig "${CONTROLPLANE_SUDO}" "${CERT_DIR}" "${ROOT_CA_FILE}" "${API_HOST}" "${API_SECURE_PORT}" admin - ${CONTROLPLANE_SUDO} chown "${USER}" "${CERT_DIR}/client-admin.key" # make readable for kubectl kube::util::write_client_kubeconfig "${CONTROLPLANE_SUDO}" "${CERT_DIR}" "${ROOT_CA_FILE}" "${API_HOST}" "${API_SECURE_PORT}" controller kube::util::write_client_kubeconfig "${CONTROLPLANE_SUDO}" "${CERT_DIR}" "${ROOT_CA_FILE}" "${API_HOST}" "${API_SECURE_PORT}" scheduler diff --git a/pkg/controlplane/apiserver/config.go b/pkg/controlplane/apiserver/config.go index 898225d6df0..07a3260f1f5 100644 --- a/pkg/controlplane/apiserver/config.go +++ b/pkg/controlplane/apiserver/config.go @@ -147,12 +147,13 @@ func BuildGenericConfig( return } - genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers) + var enablesRBAC bool + genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, enablesRBAC, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers) if err != nil { lastErr = fmt.Errorf("invalid authorization config: %v", err) return } - if s.Authorization != nil && !sets.NewString(s.Authorization.Modes...).Has(modes.ModeRBAC) { + if s.Authorization != nil && !enablesRBAC { genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName) } @@ -168,25 +169,35 @@ func BuildGenericConfig( return } -// BuildAuthorizer constructs the authorizer. If authorization is not set in s, it returns nil, nil, nil -func BuildAuthorizer(s controlplaneapiserver.CompletedOptions, EgressSelector *egressselector.EgressSelector, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) { +// BuildAuthorizer constructs the authorizer. If authorization is not set in s, it returns nil, nil, false, nil +func BuildAuthorizer(s controlplaneapiserver.CompletedOptions, egressSelector *egressselector.EgressSelector, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, bool, error) { authorizationConfig, err := s.Authorization.ToAuthorizationConfig(versionedInformers) if err != nil { - return nil, nil, err + return nil, nil, false, err } if authorizationConfig == nil { - return nil, nil, nil + return nil, nil, false, nil } - if EgressSelector != nil { - egressDialer, err := EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext()) + if egressSelector != nil { + egressDialer, err := egressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext()) if err != nil { - return nil, nil, err + return nil, nil, false, err } authorizationConfig.CustomDial = egressDialer } - return authorizationConfig.New() + enablesRBAC := false + for _, a := range authorizationConfig.AuthorizationConfiguration.Authorizers { + if string(a.Type) == modes.ModeRBAC { + enablesRBAC = true + break + } + } + + authorizer, ruleResolver, err := authorizationConfig.New() + + return authorizer, ruleResolver, enablesRBAC, err } // CreatePeerEndpointLeaseReconciler creates a apiserver endpoint lease reconciliation loop diff --git a/pkg/controlplane/apiserver/options/options.go b/pkg/controlplane/apiserver/options/options.go index a3a7b03442d..5a1c3ec9734 100644 --- a/pkg/controlplane/apiserver/options/options.go +++ b/pkg/controlplane/apiserver/options/options.go @@ -222,6 +222,9 @@ func (o *Options) Complete(alternateDNS []string, alternateIPs []net.IP) (Comple klog.Infof("external host was not specified, using %v", completed.GenericServerRunOptions.ExternalHost) } + // put authorization options in final state + completed.Authorization.Complete() + // adjust authentication for completed authorization completed.Authentication.ApplyAuthorization(completed.Authorization) // Use (ServiceAccountSigningKeyFile != "") as a proxy to the user enabling diff --git a/pkg/kubeapiserver/options/authorization.go b/pkg/kubeapiserver/options/authorization.go index 4e9e24fb729..02d8675e549 100644 --- a/pkg/kubeapiserver/options/authorization.go +++ b/pkg/kubeapiserver/options/authorization.go @@ -80,7 +80,7 @@ type BuiltInAuthorizationOptions struct { // NewBuiltInAuthorizationOptions create a BuiltInAuthorizationOptions with default value func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions { return &BuiltInAuthorizationOptions{ - Modes: []string{authzmodes.ModeAlwaysAllow}, + Modes: []string{}, WebhookVersion: "v1beta1", WebhookCacheAuthorizedTTL: 5 * time.Minute, WebhookCacheUnauthorizedTTL: 30 * time.Second, @@ -88,6 +88,14 @@ func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions { } } +// Complete modifies authorization options +func (o *BuiltInAuthorizationOptions) Complete() []error { + if len(o.AuthorizationConfigurationFile) == 0 && len(o.Modes) == 0 { + o.Modes = []string{authzmodes.ModeAlwaysAllow} + } + return nil +} + // Validate checks invalid config combination func (o *BuiltInAuthorizationOptions) Validate() []error { if o == nil { @@ -185,7 +193,7 @@ func (o *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { } fs.StringSliceVar(&o.Modes, authorizationModeFlag, o.Modes, ""+ - "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+ + "Ordered list of plug-ins to do authorization on secure port. Defaults to AlwaysAllow if --authorization-config is not used. Comma-delimited list of: "+ strings.Join(authzmodes.AuthorizationModeChoices, ",")+".") fs.StringVar(&o.PolicyFile, authorizationPolicyFileFlag, o.PolicyFile, ""+ diff --git a/test/integration/auth/authz_config_test.go b/test/integration/auth/authz_config_test.go index 3f5128aa6e4..b0ccc334998 100644 --- a/test/integration/auth/authz_config_test.go +++ b/test/integration/auth/authz_config_test.go @@ -17,6 +17,7 @@ limitations under the License. package auth import ( + "bytes" "context" "encoding/json" "fmt" @@ -34,6 +35,7 @@ import ( "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" featuregatetesting "k8s.io/component-base/featuregate/testing" kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" "k8s.io/kubernetes/test/integration/authutil" @@ -63,6 +65,16 @@ authorizers: ) t.Cleanup(server.TearDownFn) + // Make sure anonymous requests work + anonymousClient := clientset.NewForConfigOrDie(rest.AnonymousClientConfig(server.ClientConfig)) + healthzResult, err := anonymousClient.DiscoveryClient.RESTClient().Get().AbsPath("/healthz").Do(context.TODO()).Raw() + if !bytes.Equal(healthzResult, []byte(`ok`)) { + t.Fatalf("expected 'ok', got %s", string(healthzResult)) + } + if err != nil { + t.Fatal(err) + } + adminClient := clientset.NewForConfigOrDie(server.ClientConfig) sar := &authorizationv1.SubjectAccessReview{Spec: authorizationv1.SubjectAccessReviewSpec{