mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Merge pull request #93258 from zshihang/token
mv TokenRequest and TokenRequestProjection to GA
This commit is contained in:
commit
bf67247124
52
api/openapi-spec/swagger.json
generated
52
api/openapi-spec/swagger.json
generated
@ -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": [
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
|
@ -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"
|
||||
@ -67,7 +66,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"
|
||||
)
|
||||
|
||||
@ -621,9 +619,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)
|
||||
|
@ -137,6 +137,8 @@ func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint
|
||||
"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),
|
||||
|
@ -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"},
|
||||
},
|
||||
|
@ -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" \
|
||||
|
@ -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 &
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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},
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
|
||||
versionedInformer.Core().V1().Secrets().Lister().Secrets(metav1.NamespaceSystem),
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
case leaseResource:
|
||||
return p.admitLease(nodeName, a)
|
||||
|
@ -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),
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
case leaseResource:
|
||||
return r.authorizeLease(nodeName, attrs)
|
||||
case csiNodeResource:
|
||||
|
@ -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,33 +181,23 @@ func TestAuthorizer(t *testing.T) {
|
||||
expect: authorizer.DecisionAllow,
|
||||
},
|
||||
{
|
||||
name: "allowed svcacct token create - feature enabled",
|
||||
name: "allowed svcacct token create",
|
||||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node0", Namespace: "ns0"},
|
||||
features: trEnabledFeature,
|
||||
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 - 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"},
|
||||
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,
|
||||
},
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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,24 +485,17 @@ 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{
|
||||
volumes := []v1.Volume{
|
||||
{
|
||||
Name: volumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
@ -457,36 +504,31 @@ var _ = SIGDescribe("ServiceAccounts", func() {
|
||||
{
|
||||
ServiceAccountToken: &v1.ServiceAccountTokenProjection{
|
||||
Path: "token",
|
||||
ExpirationSeconds: int64p(60 * 60),
|
||||
ExpirationSeconds: utilptr.Int64Ptr(60 * 60),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
}
|
||||
volumeMounts := []v1.VolumeMount{
|
||||
{
|
||||
Name: containerName,
|
||||
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||
Args: []string{
|
||||
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),
|
||||
},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: volumeName,
|
||||
MountPath: volumeMountPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
|
||||
pod := e2epod.NewAgnhostPod(f.Namespace.Name, podName, volumes, volumeMounts, nil, mounttestArgs...)
|
||||
pod.Spec.RestartPolicy = v1.RestartPolicyNever
|
||||
|
||||
testcases := []struct {
|
||||
runAsUser bool
|
||||
fsGroup 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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user