From ff641f6eb229e9d48a439bd98bcb057403838951 Mon Sep 17 00:00:00 2001 From: Shihang Zhang Date: Fri, 17 Jul 2020 11:56:52 -0700 Subject: [PATCH] mv TokenRequest and TokenRequestProjection to GA --- api/openapi-spec/swagger.json | 52 ++++++++ cmd/kube-apiserver/app/options/options.go | 2 +- cmd/kube-apiserver/app/options/validation.go | 8 -- .../app/controllermanager.go | 5 - .../app/phases/controlplane/manifests.go | 30 +++-- .../app/phases/controlplane/manifests_test.go | 48 +++++-- hack/make-rules/test-cmd.sh | 13 ++ hack/update-openapi-spec.sh | 15 ++- pkg/api/pod/util.go | 31 +---- pkg/api/pod/util_test.go | 118 +---------------- pkg/features/kube_features.go | 6 +- pkg/kubeapiserver/authenticator/BUILD | 2 - pkg/kubeapiserver/authenticator/config.go | 4 +- pkg/kubeapiserver/options/authentication.go | 21 ++- pkg/registry/core/rest/storage_core.go | 2 +- pkg/volume/projected/BUILD | 2 - pkg/volume/projected/projected.go | 6 - .../admission/noderestriction/admission.go | 7 +- .../noderestriction/admission_test.go | 9 -- .../auth/authorizer/node/node_authorizer.go | 5 +- .../authorizer/node/node_authorizer_test.go | 42 ++---- .../authorizer/rbac/bootstrappolicy/policy.go | 11 +- .../testdata/cluster-roles.yaml | 12 +- test/e2e/auth/service_accounts.go | 124 ++++++++++++------ test/integration/auth/dynamic_client_test.go | 5 - test/integration/auth/svcaccttoken_test.go | 1 - 26 files changed, 254 insertions(+), 327 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index f2008c07c0d..a618dc27427 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -18905,6 +18905,32 @@ "version": "unversioned" }, "paths": { + "/.well-known/openid-configuration/": { + "get": { + "description": "get service account issuer OpenID configuration, also known as the 'OIDC discovery doc'", + "operationId": "getServiceAccountIssuerOpenIDConfiguration", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "WellKnown" + ] + } + }, "/api/": { "get": { "consumes": [ @@ -105104,6 +105130,32 @@ } ] }, + "/openid/v1/jwks/": { + "get": { + "description": "get service account issuer OpenID JSON Web Key Set (contains public token verification keys)", + "operationId": "getServiceAccountIssuerOpenIDKeyset", + "produces": [ + "application/jwk-set+json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + } + }, + "schemes": [ + "https" + ], + "tags": [ + "openid" + ] + } + }, "/version/": { "get": { "consumes": [ diff --git a/cmd/kube-apiserver/app/options/options.go b/cmd/kube-apiserver/app/options/options.go index 595c929307c..fc709597a75 100644 --- a/cmd/kube-apiserver/app/options/options.go +++ b/cmd/kube-apiserver/app/options/options.go @@ -270,7 +270,7 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) { "Turns on aggregator routing requests to endpoints IP rather than cluster IP.") fs.StringVar(&s.ServiceAccountSigningKeyFile, "service-account-signing-key-file", s.ServiceAccountSigningKeyFile, ""+ - "Path to the file that contains the current private key of the service account token issuer. The issuer will sign issued ID tokens with this private key. (Requires the 'TokenRequest' feature gate.)") + "Path to the file that contains the current private key of the service account token issuer. The issuer will sign issued ID tokens with this private key.") return fss } diff --git a/cmd/kube-apiserver/app/options/validation.go b/cmd/kube-apiserver/app/options/validation.go index 05e1d29c0ba..cdeb43b37f2 100644 --- a/cmd/kube-apiserver/app/options/validation.go +++ b/cmd/kube-apiserver/app/options/validation.go @@ -120,14 +120,6 @@ func validateTokenRequest(options *ServerRunOptions) []error { enableSucceeded := options.ServiceAccountIssuer != nil - if enableAttempted && !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { - errs = append(errs, errors.New("the TokenRequest feature is not enabled but --service-account-signing-key-file, --service-account-issuer and/or --api-audiences flags were passed")) - } - - if utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) && !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { - errs = append(errs, errors.New("the BoundServiceAccountTokenVolume feature depends on the TokenRequest feature, but the TokenRequest features is not enabled")) - } - if !enableAttempted && utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) { errs = append(errs, errors.New("--service-account-signing-key-file and --service-account-issuer are required flags")) } diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index ec25b0d604d..7783d0ff281 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -39,7 +39,6 @@ import ( "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/apiserver/pkg/server/mux" - utilfeature "k8s.io/apiserver/pkg/util/feature" cacheddiscovery "k8s.io/client-go/discovery/cached" "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" @@ -66,7 +65,6 @@ import ( "k8s.io/kubernetes/pkg/controller" kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -620,9 +618,6 @@ func readCA(file string) ([]byte, error) { } func shouldTurnOnDynamicClient(client clientset.Interface) bool { - if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { - return false - } apiResourceList, err := client.Discovery().ServerResourcesForGroupVersion(v1.SchemeGroupVersion.String()) if err != nil { klog.Warningf("fetch api resource lists failed, use legacy client builder: %v", err) diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index 619b6d40efb..8181bea63a4 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -132,20 +132,22 @@ func CreateStaticPodFiles(manifestDir, patchesDir string, cfg *kubeadmapi.Cluste // getAPIServerCommand builds the right API server command from the given config object and version func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) []string { defaultArguments := map[string]string{ - "advertise-address": localAPIEndpoint.AdvertiseAddress, - "insecure-port": "0", - "enable-admission-plugins": "NodeRestriction", - "service-cluster-ip-range": cfg.Networking.ServiceSubnet, - "service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName), - "client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName), - "tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName), - "tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName), - "kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName), - "kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName), - "enable-bootstrap-token-auth": "true", - "secure-port": fmt.Sprintf("%d", localAPIEndpoint.BindPort), - "allow-privileged": "true", - "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname", + "advertise-address": localAPIEndpoint.AdvertiseAddress, + "insecure-port": "0", + "enable-admission-plugins": "NodeRestriction", + "service-cluster-ip-range": cfg.Networking.ServiceSubnet, + "service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName), + "service-account-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName), + "service-account-issuer": fmt.Sprintf("https://kubernetes.default.svc.%s", cfg.Networking.DNSDomain), + "client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName), + "tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName), + "tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName), + "kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName), + "kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName), + "enable-bootstrap-token-auth": "true", + "secure-port": fmt.Sprintf("%d", localAPIEndpoint.BindPort), + "allow-privileged": "true", + "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname", // add options to configure the front proxy. Without the generated client cert, this will never be useable // so add it unconditionally with recommended values "requestheader-username-headers": "X-Remote-User", diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index a29680914c0..0a443670d16 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -197,7 +197,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "testing defaults", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, }, endpoint: &kubeadmapi.APIEndpoint{BindPort: 123, AdvertiseAddress: "1.2.3.4"}, @@ -207,6 +207,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -234,7 +236,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "ipv6 advertise address", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, }, endpoint: &kubeadmapi.APIEndpoint{BindPort: 123, AdvertiseAddress: "2001:db8::1"}, @@ -244,6 +246,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -271,7 +275,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "an external etcd with custom ca, certs and keys", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, Etcd: kubeadmapi.Etcd{ External: &kubeadmapi.ExternalEtcd{ Endpoints: []string{"https://[2001:abcd:bcda::1]:2379", "https://[2001:abcd:bcda::2]:2379"}, @@ -289,6 +293,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -316,7 +322,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "an insecure etcd", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, Etcd: kubeadmapi.Etcd{ External: &kubeadmapi.ExternalEtcd{ Endpoints: []string{"http://[::1]:2379", "http://[::1]:2380"}, @@ -331,6 +337,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -355,7 +363,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "test APIServer.ExtraArgs works as expected", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, APIServer: kubeadmapi.APIServer{ ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{ @@ -375,6 +383,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=baz", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -404,7 +414,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "authorization-mode extra-args ABAC", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, APIServer: kubeadmapi.APIServer{ ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{ @@ -421,6 +431,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -448,7 +460,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "insecure-port extra-args", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, APIServer: kubeadmapi.APIServer{ ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{ @@ -465,6 +477,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -492,7 +506,7 @@ func TestGetAPIServerCommand(t *testing.T) { { name: "authorization-mode extra-args Webhook", cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "bar", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, APIServer: kubeadmapi.APIServer{ ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{ @@ -513,6 +527,8 @@ func TestGetAPIServerCommand(t *testing.T) { "--enable-admission-plugins=NodeRestriction", "--service-cluster-ip-range=bar", "--service-account-key-file=" + testCertsDir + "/sa.pub", + "--service-account-signing-key-file=" + testCertsDir + "/sa.key", + "--service-account-issuer=https://kubernetes.default.svc.cluster.local", "--client-ca-file=" + testCertsDir + "/ca.crt", "--tls-cert-file=" + testCertsDir + "/apiserver.crt", "--tls-private-key-file=" + testCertsDir + "/apiserver.key", @@ -628,7 +644,7 @@ func TestGetControllerManagerCommand(t *testing.T) { { name: "custom cluster-cidr for " + cpVersion, cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16"}, + Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16", DNSDomain: "cluster.local"}, CertificatesDir: testCertsDir, KubernetesVersion: cpVersion, }, @@ -657,7 +673,9 @@ func TestGetControllerManagerCommand(t *testing.T) { cfg: &kubeadmapi.ClusterConfiguration{ Networking: kubeadmapi.Networking{ PodSubnet: "10.0.1.15/16", - ServiceSubnet: "172.20.0.0/24"}, + ServiceSubnet: "172.20.0.0/24", + DNSDomain: "cluster.local", + }, CertificatesDir: testCertsDir, KubernetesVersion: cpVersion, }, @@ -685,7 +703,7 @@ func TestGetControllerManagerCommand(t *testing.T) { { name: "custom extra-args for " + cpVersion, cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16"}, + Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16", DNSDomain: "cluster.local"}, ControllerManager: kubeadmapi.ControlPlaneComponent{ ExtraArgs: map[string]string{"node-cidr-mask-size": "20"}, }, @@ -719,7 +737,9 @@ func TestGetControllerManagerCommand(t *testing.T) { Networking: kubeadmapi.Networking{ PodSubnet: "2001:db8::/64", ServiceSubnet: "fd03::/112", + DNSDomain: "cluster.local", }, + CertificatesDir: testCertsDir, KubernetesVersion: cpVersion, }, @@ -750,6 +770,7 @@ func TestGetControllerManagerCommand(t *testing.T) { Networking: kubeadmapi.Networking{ PodSubnet: "2001:db8::/64,10.1.0.0/16", ServiceSubnet: "fd03::/112,192.168.0.0/16", + DNSDomain: "cluster.local", }, CertificatesDir: testCertsDir, KubernetesVersion: cpVersion, @@ -780,7 +801,10 @@ func TestGetControllerManagerCommand(t *testing.T) { { name: "dual-stack networking custom extra-args for " + cpVersion, cfg: &kubeadmapi.ClusterConfiguration{ - Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16,2001:db8::/64"}, + Networking: kubeadmapi.Networking{ + PodSubnet: "10.0.1.15/16,2001:db8::/64", + DNSDomain: "cluster.local", + }, ControllerManager: kubeadmapi.ControlPlaneComponent{ ExtraArgs: map[string]string{"node-cidr-mask-size-ipv4": "20", "node-cidr-mask-size-ipv6": "80"}, }, diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 3a9e09b764a..e3f065b92a8 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -34,6 +34,15 @@ source "${KUBE_ROOT}/hack/lib/init.sh" source "${KUBE_ROOT}/hack/lib/test.sh" source "${KUBE_ROOT}/test/cmd/legacy-script.sh" +# setup envs for TokenRequest required flags +SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-true} +SERVICE_ACCOUNT_KEY=${SERVICE_ACCOUNT_KEY:-/tmp/kube-serviceaccount.key} +# Generate ServiceAccount key if needed +if [[ ! -f "${SERVICE_ACCOUNT_KEY}" ]]; then + mkdir -p "$(dirname "${SERVICE_ACCOUNT_KEY}")" + openssl genrsa -out "${SERVICE_ACCOUNT_KEY}" 2048 2>/dev/null +fi + # Runs kube-apiserver # # Exports: @@ -64,6 +73,10 @@ function run_kube_apiserver() { --disable-admission-plugins="${DISABLE_ADMISSION_PLUGINS}" \ --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --runtime-config=api/v1 \ + --service-account-key-file="${SERVICE_ACCOUNT_KEY}" \ + --service-account-lookup="${SERVICE_ACCOUNT_LOOKUP}" \ + --service-account-issuer="https://kubernetes.default.svc" \ + --service-account-signing-key-file="${SERVICE_ACCOUNT_KEY}" \ --storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \ --cert-dir="${TMPDIR:-/tmp/}" \ --service-cluster-ip-range="10.0.0.0/24" \ diff --git a/hack/update-openapi-spec.sh b/hack/update-openapi-spec.sh index 379878ed98e..b9f4b734a4a 100755 --- a/hack/update-openapi-spec.sh +++ b/hack/update-openapi-spec.sh @@ -58,6 +58,15 @@ kube::etcd::start echo "dummy_token,admin,admin" > "${TMP_DIR}/tokenauth.csv" +# setup envs for TokenRequest required flags +SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-true} +SERVICE_ACCOUNT_KEY=${SERVICE_ACCOUNT_KEY:-/tmp/kube-serviceaccount.key} +# Generate ServiceAccount key if needed +if [[ ! -f "${SERVICE_ACCOUNT_KEY}" ]]; then + mkdir -p "$(dirname "${SERVICE_ACCOUNT_KEY}")" + openssl genrsa -out "${SERVICE_ACCOUNT_KEY}" 2048 2>/dev/null +fi + # Start kube-apiserver kube::log::status "Starting kube-apiserver" "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \ @@ -69,8 +78,10 @@ kube::log::status "Starting kube-apiserver" --runtime-config="api/all=true" \ --token-auth-file="${TMP_DIR}/tokenauth.csv" \ --authorization-mode=RBAC \ - --service-account-issuer="https://kubernetes.default.svc/" \ - --service-account-signing-key-file="${KUBE_ROOT}/staging/src/k8s.io/client-go/util/cert/testdata/dontUseThisKey.pem" \ + --service-account-key-file="${SERVICE_ACCOUNT_KEY}" \ + --service-account-lookup="${SERVICE_ACCOUNT_LOOKUP}" \ + --service-account-issuer="https://kubernetes.default.svc" \ + --service-account-signing-key-file="${SERVICE_ACCOUNT_KEY}" \ --logtostderr \ --v=2 \ --service-cluster-ip-range="10.0.0.0/24" >"${API_LOGFILE}" 2>&1 & diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 8cd6ed55412..623debf6c36 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -19,7 +19,7 @@ package pod import ( "strings" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" api "k8s.io/kubernetes/pkg/apis/core" @@ -349,18 +349,6 @@ func dropDisabledFields( podSpec = &api.PodSpec{} } - if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequestProjection) && - !tokenRequestProjectionInUse(oldPodSpec) { - for i := range podSpec.Volumes { - if podSpec.Volumes[i].Projected != nil { - for j := range podSpec.Volumes[i].Projected.Sources { - podSpec.Volumes[i].Projected.Sources[j].ServiceAccountToken = nil - } - } - - } - } - if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) && !appArmorInUse(oldPodAnnotations) { for k := range podAnnotations { if strings.HasPrefix(k, v1.AppArmorBetaContainerAnnotationKeyPrefix) { @@ -593,23 +581,6 @@ func appArmorInUse(podAnnotations map[string]string) bool { return false } -func tokenRequestProjectionInUse(podSpec *api.PodSpec) bool { - if podSpec == nil { - return false - } - for _, v := range podSpec.Volumes { - if v.Projected == nil { - continue - } - for _, s := range v.Projected.Sources { - if s.ServiceAccountToken != nil { - return true - } - } - } - return false -} - // podPriorityInUse returns true if the pod spec is non-nil and has Priority or PriorityClassName set. func podPriorityInUse(podSpec *api.PodSpec) bool { if podSpec == nil { diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index 693ca2a0ae7..7499f71703d 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -24,7 +24,7 @@ import ( "github.com/google/go-cmp/cmp" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" @@ -1106,122 +1106,6 @@ func TestDropAppArmor(t *testing.T) { } } -func TestDropTokenRequestProjection(t *testing.T) { - podWithoutTRProjection := func() *api.Pod { - return &api.Pod{ - Spec: api.PodSpec{ - Volumes: []api.Volume{{ - VolumeSource: api.VolumeSource{ - Projected: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{{ - ServiceAccountToken: nil, - }}, - }}}, - }, - }, - } - } - podWithoutProjectedVolumeSource := func() *api.Pod { - return &api.Pod{ - Spec: api.PodSpec{ - Volumes: []api.Volume{ - {VolumeSource: api.VolumeSource{ - ConfigMap: &api.ConfigMapVolumeSource{}, - }}, - }, - }, - } - } - podWithTRProjection := func() *api.Pod { - return &api.Pod{ - Spec: api.PodSpec{ - Volumes: []api.Volume{{ - VolumeSource: api.VolumeSource{ - Projected: &api.ProjectedVolumeSource{ - Sources: []api.VolumeProjection{{ - ServiceAccountToken: &api.ServiceAccountTokenProjection{ - Audience: "api", - ExpirationSeconds: 3600, - Path: "token", - }}, - }}, - }, - }, - }, - }} - } - podInfo := []struct { - description string - hasTRProjection bool - pod func() *api.Pod - }{ - { - description: "has TokenRequestProjection", - hasTRProjection: true, - pod: podWithTRProjection, - }, - { - description: "does not have TokenRequestProjection", - hasTRProjection: false, - pod: podWithoutTRProjection, - }, - { - description: "does not have ProjectedVolumeSource", - hasTRProjection: false, - pod: podWithoutProjectedVolumeSource, - }, - { - description: "is nil", - hasTRProjection: false, - pod: func() *api.Pod { return nil }, - }, - } - for _, enabled := range []bool{true, false} { - for _, oldPodInfo := range podInfo { - for _, newPodInfo := range podInfo { - oldPodhasTRProjection, oldPod := oldPodInfo.hasTRProjection, oldPodInfo.pod() - newPodhasTRProjection, newPod := newPodInfo.hasTRProjection, newPodInfo.pod() - if newPod == nil { - continue - } - t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequestProjection, enabled)() - var oldPodSpec *api.PodSpec - if oldPod != nil { - oldPodSpec = &oldPod.Spec - } - dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil) - // old pod should never be changed - if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) { - t.Errorf("old pod changed: %v", diff.ObjectReflectDiff(oldPod, oldPodInfo.pod())) - } - switch { - case enabled || oldPodhasTRProjection: - if !reflect.DeepEqual(newPod, newPodInfo.pod()) { - t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod())) - } - case newPodhasTRProjection: - // new pod should be changed - if reflect.DeepEqual(newPod, newPodInfo.pod()) { - t.Errorf("%v", oldPod) - t.Errorf("%v", newPod) - t.Errorf("new pod was not changed") - } - if !reflect.DeepEqual(newPod, podWithoutTRProjection()) { - t.Errorf("new pod had Tokenrequestprojection: %v", diff.ObjectReflectDiff(newPod, podWithoutTRProjection())) - } - default: - // new pod should not need to be changed - if !reflect.DeepEqual(newPod, newPodInfo.pod()) { - t.Errorf("new pod changed: %v", diff.ObjectReflectDiff(newPod, newPodInfo.pod())) - } - } - }) - } - } - } -} - func TestDropRunAsGroup(t *testing.T) { group := func() *int64 { testGroup := int64(1000) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index e8cf9bc65f4..165040aa182 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -211,12 +211,14 @@ const ( // owner: @mikedanese // beta: v1.12 + // ga: v1.20 // // Implement TokenRequest endpoint on service account resources. TokenRequest featuregate.Feature = "TokenRequest" // owner: @mikedanese // beta: v1.12 + // ga: v1.20 // // Enable ServiceAccountTokenVolumeProjection support in ProjectedVolumes. TokenRequestProjection featuregate.Feature = "TokenRequestProjection" @@ -686,8 +688,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS SupportPodPidsLimit: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21 SupportNodePidsLimit: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21 HyperVContainer: {Default: false, PreRelease: featuregate.Deprecated}, - TokenRequest: {Default: true, PreRelease: featuregate.Beta}, - TokenRequestProjection: {Default: true, PreRelease: featuregate.Beta}, + TokenRequest: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21 + TokenRequestProjection: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21 BoundServiceAccountTokenVolume: {Default: false, PreRelease: featuregate.Alpha}, ServiceAccountIssuerDiscovery: {Default: true, PreRelease: featuregate.Beta}, CRIContainerLogRotation: {Default: true, PreRelease: featuregate.Beta}, diff --git a/pkg/kubeapiserver/authenticator/BUILD b/pkg/kubeapiserver/authenticator/BUILD index 106f075aa0a..4d6525b6a05 100644 --- a/pkg/kubeapiserver/authenticator/BUILD +++ b/pkg/kubeapiserver/authenticator/BUILD @@ -10,7 +10,6 @@ go_library( srcs = ["config.go"], importpath = "k8s.io/kubernetes/pkg/kubeapiserver/authenticator", deps = [ - "//pkg/features:go_default_library", "//pkg/serviceaccount:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", @@ -26,7 +25,6 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/token/union:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc:go_default_library", "//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library", "//staging/src/k8s.io/client-go/plugin/pkg/client/auth:go_default_library", diff --git a/pkg/kubeapiserver/authenticator/config.go b/pkg/kubeapiserver/authenticator/config.go index 4e1eee4b5ef..60c710f7972 100644 --- a/pkg/kubeapiserver/authenticator/config.go +++ b/pkg/kubeapiserver/authenticator/config.go @@ -35,14 +35,12 @@ import ( "k8s.io/apiserver/pkg/authentication/token/tokenfile" tokenunion "k8s.io/apiserver/pkg/authentication/token/union" "k8s.io/apiserver/pkg/server/dynamiccertificates" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" // Initialize all known client auth plugins. _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/util/keyutil" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -127,7 +125,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er } tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) } - if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" { + if config.ServiceAccountIssuer != "" { serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index 44e14d314b4..fc368cc3d19 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -192,10 +192,6 @@ func (o *BuiltInAuthenticationOptions) Validate() []error { } } if o.ServiceAccounts != nil && utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) { - if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) || !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequestProjection) { - allErrors = append(allErrors, errors.New("if the BoundServiceAccountTokenVolume feature is enabled,"+ - " the TokenRequest and TokenRequestProjection features must also be enabled")) - } if len(o.ServiceAccounts.Issuer) == 0 { allErrors = append(allErrors, errors.New("service-account-issuer is a required flag when BoundServiceAccountTokenVolume is enabled")) } @@ -313,7 +309,7 @@ func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { "that this value comply with the OpenID spec: https://openid.net/specs/openid-connect-discovery-1_0.html. "+ "In practice, this means that service-account-issuer must be an https URL. It is also highly "+ "recommended that this URL be capable of serving OpenID discovery documents at "+ - "`{service-account-issuer}/.well-known/openid-configuration`.") + "{service-account-issuer}/.well-known/openid-configuration.") fs.StringVar(&o.ServiceAccounts.JWKSURI, "service-account-jwks-uri", o.ServiceAccounts.JWKSURI, ""+ "Overrides the URI for the JSON Web Key Set in the discovery doc served at "+ @@ -464,14 +460,13 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(authInfo *genericapiserver.Authen authInfo.APIAudiences = authenticator.Audiences{o.ServiceAccounts.Issuer} } - if o.ServiceAccounts.Lookup || utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { - authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient( - extclient, - versionedInformer.Core().V1().Secrets().Lister(), - versionedInformer.Core().V1().ServiceAccounts().Lister(), - versionedInformer.Core().V1().Pods().Lister(), - ) - } + authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient( + extclient, + versionedInformer.Core().V1().Secrets().Lister(), + versionedInformer.Core().V1().ServiceAccounts().Lister(), + versionedInformer.Core().V1().Pods().Lister(), + ) + authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator( versionedInformer.Core().V1().Secrets().Lister().Secrets(metav1.NamespaceSystem), ) diff --git a/pkg/registry/core/rest/storage_core.go b/pkg/registry/core/rest/storage_core.go index feaff9d148e..d4a33c9e222 100644 --- a/pkg/registry/core/rest/storage_core.go +++ b/pkg/registry/core/rest/storage_core.go @@ -181,7 +181,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi } var serviceAccountStorage *serviceaccountstore.REST - if c.ServiceAccountIssuer != nil && utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { + if c.ServiceAccountIssuer != nil { serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, c.ServiceAccountIssuer, c.APIAudiences, c.ServiceAccountMaxExpiration, podStorage.Pod.Store, secretStorage.Store, c.ExtendExpiration) } else { serviceAccountStorage, err = serviceaccountstore.NewREST(restOptionsGetter, nil, nil, 0, nil, nil, false) diff --git a/pkg/volume/projected/BUILD b/pkg/volume/projected/BUILD index 99eb31bbf9d..6adc0b82d6e 100644 --- a/pkg/volume/projected/BUILD +++ b/pkg/volume/projected/BUILD @@ -36,7 +36,6 @@ go_library( srcs = ["projected.go"], importpath = "k8s.io/kubernetes/pkg/volume/projected", deps = [ - "//pkg/features:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/configmap:go_default_library", "//pkg/volume/downwardapi:go_default_library", @@ -48,7 +47,6 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library", "//vendor/k8s.io/utils/strings:go_default_library", ], diff --git a/pkg/volume/projected/projected.go b/pkg/volume/projected/projected.go index 25908049a30..bac72a6e05b 100644 --- a/pkg/volume/projected/projected.go +++ b/pkg/volume/projected/projected.go @@ -25,9 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/configmap" "k8s.io/kubernetes/pkg/volume/downwardapi" @@ -322,10 +320,6 @@ func (s *projectedVolumeMounter) collectData(mounterArgs volume.MounterArgs) (ma payload[k] = v } case source.ServiceAccountToken != nil: - if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequestProjection) { - errlist = append(errlist, fmt.Errorf("pod request ServiceAccountToken projection but the TokenRequestProjection feature was not enabled")) - continue - } tp := source.ServiceAccountToken // When FsGroup is set, we depend on SetVolumeOwnership to diff --git a/plugin/pkg/admission/noderestriction/admission.go b/plugin/pkg/admission/noderestriction/admission.go index fde207187d0..23a379086ac 100644 --- a/plugin/pkg/admission/noderestriction/admission.go +++ b/plugin/pkg/admission/noderestriction/admission.go @@ -71,7 +71,6 @@ type Plugin struct { podsGetter corev1lister.PodLister nodesGetter corev1lister.NodeLister - tokenRequestEnabled bool csiNodeInfoEnabled bool expandPersistentVolumesEnabled bool } @@ -84,7 +83,6 @@ var ( // InspectFeatureGates allows setting bools without taking a dep on a global variable func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) { - p.tokenRequestEnabled = featureGates.Enabled(features.TokenRequest) p.csiNodeInfoEnabled = featureGates.Enabled(features.CSINodeInfo) p.expandPersistentVolumesEnabled = featureGates.Enabled(features.ExpandPersistentVolumes) } @@ -159,10 +157,7 @@ func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission. } case svcacctResource: - if p.tokenRequestEnabled { - return p.admitServiceAccount(nodeName, a) - } - return nil + return p.admitServiceAccount(nodeName, a) case leaseResource: return p.admitLease(nodeName, a) diff --git a/plugin/pkg/admission/noderestriction/admission_test.go b/plugin/pkg/admission/noderestriction/admission_test.go index 8e089917c0b..16948329333 100644 --- a/plugin/pkg/admission/noderestriction/admission_test.go +++ b/plugin/pkg/admission/noderestriction/admission_test.go @@ -48,7 +48,6 @@ import ( ) var ( - trEnabledFeature = featuregate.NewFeatureGate() csiNodeInfoEnabledFeature = featuregate.NewFeatureGate() csiNodeInfoDisabledFeature = featuregate.NewFeatureGate() ) @@ -56,15 +55,12 @@ var ( func init() { // all features need to be set on all featuregates for the tests. We set everything and then then the if's below override it. relevantFeatures := map[featuregate.Feature]featuregate.FeatureSpec{ - features.TokenRequest: {Default: false}, features.CSINodeInfo: {Default: false}, features.ExpandPersistentVolumes: {Default: false}, } - utilruntime.Must(trEnabledFeature.Add(relevantFeatures)) utilruntime.Must(csiNodeInfoEnabledFeature.Add(relevantFeatures)) utilruntime.Must(csiNodeInfoDisabledFeature.Add(relevantFeatures)) - utilruntime.Must(trEnabledFeature.SetFromMap(map[string]bool{string(features.TokenRequest): true})) utilruntime.Must(csiNodeInfoEnabledFeature.SetFromMap(map[string]bool{string(features.CSINodeInfo): true})) } @@ -1086,35 +1082,30 @@ func Test_nodePlugin_Admit(t *testing.T) { { name: "forbid create of unbound token", podsGetter: noExistingPods, - features: trEnabledFeature, attributes: admission.NewAttributesRecord(makeTokenRequest("", ""), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode), err: "not bound to a pod", }, { name: "forbid create of token bound to nonexistant pod", podsGetter: noExistingPods, - features: trEnabledFeature, attributes: admission.NewAttributesRecord(makeTokenRequest("nopod", "someuid"), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode), err: "not found", }, { name: "forbid create of token bound to pod without uid", podsGetter: existingPods, - features: trEnabledFeature, attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, ""), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode), err: "pod binding without a uid", }, { name: "forbid create of token bound to pod scheduled on another node", podsGetter: existingPods, - features: trEnabledFeature, attributes: admission.NewAttributesRecord(makeTokenRequest(coreotherpod.Name, coreotherpod.UID), nil, tokenrequestKind, coreotherpod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode), err: "pod scheduled on a different node", }, { name: "allow create of token bound to pod scheduled this node", podsGetter: existingPods, - features: trEnabledFeature, attributes: admission.NewAttributesRecord(makeTokenRequest(coremypod.Name, coremypod.UID), nil, tokenrequestKind, coremypod.Namespace, "mysa", svcacctResource, "token", admission.Create, &metav1.CreateOptions{}, false, mynode), }, diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer.go b/plugin/pkg/auth/authorizer/node/node_authorizer.go index 39157c73f6c..1da0c20ccec 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer.go @@ -123,10 +123,7 @@ func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attribu case vaResource: return r.authorizeGet(nodeName, vaVertexType, attrs) case svcAcctResource: - if r.features.Enabled(features.TokenRequest) { - return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs) - } - return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.TokenRequest), nil + return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs) case leaseResource: return r.authorizeLease(nodeName, attrs) case csiNodeResource: diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer_test.go b/plugin/pkg/auth/authorizer/node/node_authorizer_test.go index 7cef6b27cec..7c617043b52 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer_test.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer_test.go @@ -42,19 +42,11 @@ import ( ) var ( - trEnabledFeature = featuregate.NewFeatureGate() - trDisabledFeature = featuregate.NewFeatureGate() csiNodeInfoEnabledFeature = featuregate.NewFeatureGate() csiNodeInfoDisabledFeature = featuregate.NewFeatureGate() ) func init() { - if err := trEnabledFeature.Add(map[featuregate.Feature]featuregate.FeatureSpec{features.TokenRequest: {Default: true}}); err != nil { - panic(err) - } - if err := trDisabledFeature.Add(map[featuregate.Feature]featuregate.FeatureSpec{features.TokenRequest: {Default: false}}); err != nil { - panic(err) - } if err := csiNodeInfoEnabledFeature.Add(map[featuregate.Feature]featuregate.FeatureSpec{features.CSINodeInfo: {Default: true}}); err != nil { panic(err) } @@ -189,34 +181,24 @@ func TestAuthorizer(t *testing.T) { expect: authorizer.DecisionAllow, }, { - name: "allowed svcacct token create - feature enabled", - attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, - features: trEnabledFeature, - expect: authorizer.DecisionAllow, + name: "allowed svcacct token create", + attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, + expect: authorizer.DecisionAllow, }, { - name: "disallowed svcacct token create - serviceaccount not attached to node", - attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node1", Namespace: "ns0"}, - features: trEnabledFeature, - expect: authorizer.DecisionNoOpinion, + name: "disallowed svcacct token create - serviceaccount not attached to node", + attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node1", Namespace: "ns0"}, + expect: authorizer.DecisionNoOpinion, }, { - name: "disallowed svcacct token create - feature disabled", - attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, - features: trDisabledFeature, - expect: authorizer.DecisionNoOpinion, + name: "disallowed svcacct token create - no subresource", + attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Name: "svcacct0-node0", Namespace: "ns0"}, + expect: authorizer.DecisionNoOpinion, }, { - name: "disallowed svcacct token create - no subresource", - attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Name: "svcacct0-node0", Namespace: "ns0"}, - features: trEnabledFeature, - expect: authorizer.DecisionNoOpinion, - }, - { - name: "disallowed svcacct token create - non create", - attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, - features: trEnabledFeature, - expect: authorizer.DecisionNoOpinion, + name: "disallowed svcacct token create - non create", + attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"}, + expect: authorizer.DecisionNoOpinion, }, { name: "disallowed get lease in namespace other than kube-node-lease - feature enabled", diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go index e35186a9ddc..82bc5c6174e 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go @@ -153,6 +153,10 @@ func NodeRules() []rbacv1.PolicyRule { // CSI rbacv1helpers.NewRule("get").Groups(storageGroup).Resources("volumeattachments").RuleOrDie(), + + // Use the Node authorization to limit a node to create tokens for service accounts running on that node + // Use the NodeRestriction admission plugin to limit a node to create tokens bound to pods on that node + rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts/token").RuleOrDie(), } if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) { @@ -162,13 +166,6 @@ func NodeRules() []rbacv1.PolicyRule { nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule) } - if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) { - // Use the Node authorization to limit a node to create tokens for service accounts running on that node - // Use the NodeRestriction admission plugin to limit a node to create tokens bound to pods on that node - tokenRequestRule := rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts/token").RuleOrDie() - nodePolicyRules = append(nodePolicyRules, tokenRequestRule) - } - // CSI csiDriverRule := rbacv1helpers.NewRule("get", "watch", "list").Groups("storage.k8s.io").Resources("csidrivers").RuleOrDie() nodePolicyRules = append(nodePolicyRules, csiDriverRule) diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml index 9aa25219f3f..2f571c764c4 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml @@ -1018,6 +1018,12 @@ items: - volumeattachments verbs: - get + - apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create - apiGroups: - "" resources: @@ -1026,12 +1032,6 @@ items: - get - patch - update - - apiGroups: - - "" - resources: - - serviceaccounts/token - verbs: - - create - apiGroups: - storage.k8s.io resources: diff --git a/test/e2e/auth/service_accounts.go b/test/e2e/auth/service_accounts.go index cef99fa6234..674559d86cd 100644 --- a/test/e2e/auth/service_accounts.go +++ b/test/e2e/auth/service_accounts.go @@ -41,6 +41,7 @@ import ( e2epod "k8s.io/kubernetes/test/e2e/framework/pod" e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" imageutils "k8s.io/kubernetes/test/utils/image" + utilptr "k8s.io/utils/pointer" "github.com/onsi/ginkgo" ) @@ -418,6 +419,59 @@ var _ = SIGDescribe("ServiceAccounts", func() { } }) + /* + Release : v1.20 + Testname: TokenRequestProjection should mount a projected volume with token using TokenRequest API. + Description: Ensure that projected service account token is mounted. + */ + ginkgo.It("should mount projected service account token when requested", func() { + + var ( + podName = "test-pod-" + string(uuid.NewUUID()) + volumeName = "test-volume" + volumeMountPath = "/test-volume" + tokenVolumePath = "/test-volume/token" + ) + + volumes := []v1.Volume{ + { + Name: volumeName, + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + ServiceAccountToken: &v1.ServiceAccountTokenProjection{ + Path: "token", + ExpirationSeconds: utilptr.Int64Ptr(60 * 60), + }, + }, + }, + }, + }, + }, + } + volumeMounts := []v1.VolumeMount{ + { + Name: volumeName, + MountPath: volumeMountPath, + ReadOnly: true, + }, + } + mounttestArgs := []string{ + "mounttest", + fmt.Sprintf("--file_content=%v", tokenVolumePath), + } + + pod := e2epod.NewAgnhostPod(f.Namespace.Name, podName, volumes, volumeMounts, nil, mounttestArgs...) + pod.Spec.RestartPolicy = v1.RestartPolicyNever + + output := []string{ + fmt.Sprintf("content of file \"%v\": %s", tokenVolumePath, `[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*`), + } + + f.TestContainerOutputRegexp("service account token: ", pod, 0, output) + }) + /* Testname: Projected service account token file ownership and permission. Description: Ensure that Projected Service Account Token is mounted with @@ -431,61 +485,49 @@ var _ = SIGDescribe("ServiceAccounts", func() { Containers MUST verify that the projected service account token can be read and has correct file mode set including ownership and permission. */ - ginkgo.It("should set ownership and permission when RunAsUser or FsGroup is present [LinuxOnly] [NodeFeature:FSGroup] [Feature:TokenRequestProjection]", func() { + ginkgo.It("should set ownership and permission when RunAsUser or FsGroup is present [LinuxOnly] [NodeFeature:FSGroup]", func() { e2eskipper.SkipIfNodeOSDistroIs("windows") var ( podName = "test-pod-" + string(uuid.NewUUID()) - containerName = "test-container" volumeName = "test-volume" volumeMountPath = "/test-volume" tokenVolumePath = "/test-volume/token" - int64p = func(i int64) *int64 { return &i } ) - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: podName, - }, - Spec: v1.PodSpec{ - Volumes: []v1.Volume{ - { - Name: volumeName, - VolumeSource: v1.VolumeSource{ - Projected: &v1.ProjectedVolumeSource{ - Sources: []v1.VolumeProjection{ - { - ServiceAccountToken: &v1.ServiceAccountTokenProjection{ - Path: "token", - ExpirationSeconds: int64p(60 * 60), - }, - }, + volumes := []v1.Volume{ + { + Name: volumeName, + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + ServiceAccountToken: &v1.ServiceAccountTokenProjection{ + Path: "token", + ExpirationSeconds: utilptr.Int64Ptr(60 * 60), }, }, }, }, }, - Containers: []v1.Container{ - { - Name: containerName, - Image: imageutils.GetE2EImage(imageutils.Agnhost), - Args: []string{ - "mounttest", - fmt.Sprintf("--file_perm=%v", tokenVolumePath), - fmt.Sprintf("--file_owner=%v", tokenVolumePath), - fmt.Sprintf("--file_content=%v", tokenVolumePath), - }, - VolumeMounts: []v1.VolumeMount{ - { - Name: volumeName, - MountPath: volumeMountPath, - }, - }, - }, - }, - RestartPolicy: v1.RestartPolicyNever, }, } + volumeMounts := []v1.VolumeMount{ + { + Name: volumeName, + MountPath: volumeMountPath, + ReadOnly: true, + }, + } + mounttestArgs := []string{ + "mounttest", + fmt.Sprintf("--file_perm=%v", tokenVolumePath), + fmt.Sprintf("--file_owner=%v", tokenVolumePath), + fmt.Sprintf("--file_content=%v", tokenVolumePath), + } + + pod := e2epod.NewAgnhostPod(f.Namespace.Name, podName, volumes, volumeMounts, nil, mounttestArgs...) + pod.Spec.RestartPolicy = v1.RestartPolicyNever testcases := []struct { runAsUser bool @@ -531,7 +573,7 @@ var _ = SIGDescribe("ServiceAccounts", func() { output := []string{ fmt.Sprintf("perms of file \"%v\": %s", tokenVolumePath, tc.wantPerm), - fmt.Sprintf("content of file \"%v\": %s", tokenVolumePath, ".+"), + fmt.Sprintf("content of file \"%v\": %s", tokenVolumePath, `[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*`), fmt.Sprintf("owner UID of \"%v\": %d", tokenVolumePath, tc.wantUID), fmt.Sprintf("owner GID of \"%v\": %d", tokenVolumePath, tc.wantGID), } @@ -539,7 +581,7 @@ var _ = SIGDescribe("ServiceAccounts", func() { } }) - ginkgo.It("should support InClusterConfig with token rotation [Slow] [Feature:TokenRequestProjection]", func() { + ginkgo.It("should support InClusterConfig with token rotation [Slow]", func() { cfg, err := framework.LoadConfig() framework.ExpectNoError(err) diff --git a/test/integration/auth/dynamic_client_test.go b/test/integration/auth/dynamic_client_test.go index 0c1e2e1a0a4..4c4f0bb7fea 100644 --- a/test/integration/auth/dynamic_client_test.go +++ b/test/integration/auth/dynamic_client_test.go @@ -26,21 +26,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authorization/authorizerfactory" - utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controlplane" - "k8s.io/kubernetes/pkg/features" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" "k8s.io/kubernetes/test/integration/framework" ) func TestDynamicClientBuilder(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequest, true)() - tmpfile, err := ioutil.TempFile("/tmp", "key") if err != nil { t.Fatalf("create temp file failed: %v", err) diff --git a/test/integration/auth/svcaccttoken_test.go b/test/integration/auth/svcaccttoken_test.go index 6494f6713d6..70dca7ccb9b 100644 --- a/test/integration/auth/svcaccttoken_test.go +++ b/test/integration/auth/svcaccttoken_test.go @@ -64,7 +64,6 @@ AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0 -----END EC PRIVATE KEY-----` func TestServiceAccountTokenCreate(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequest, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountIssuerDiscovery, true)() // Build client config, clientset, and informers