Skip to content

Commit

Permalink
Default enableStaticTokenKubeconfig to false for Shoots with K8s ve…
Browse files Browse the repository at this point in the history
…rsion >= 1.26

This commit also adapts most of the testmachinery integration tests to use the `shoots/adminkubeconfig` subresource instead of the static kubeconfig.
The Shoot creation intergration is still using the static kubeconfig and it is downloading it to `$TM_KUBECONFIG_PATH/shoot.config`. This commit sets `enableStaticTokenKubeconfig=true` until we figure out which tests/components are using this downloaded kubeconfig.
  • Loading branch information
ialidzhikov committed Jan 4, 2023
1 parent 365f9ec commit a9c5e99
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 87 deletions.
5 changes: 3 additions & 2 deletions docs/api-reference/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -5657,8 +5657,9 @@ bool
</td>
<td>
<em>(Optional)</em>
<p>EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret should be present in garden cluster
(default: true).</p>
<p>EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret will be created for the Shoot cluster.
Defaults to true for Shoots with Kubernetes versions &lt; 1.26. Defaults to false for Shoots with Kubernetes versions &gt;= 1.26.
Starting Kubernetes 1.27 the field will be locked to false.</p>
</td>
</tr>
</tbody>
Expand Down
8 changes: 7 additions & 1 deletion docs/usage/shoot_access.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ It is **not** the recommended method to access the shoot cluster as the static t
- The static token in the `kubeconfig` doesn't have any expiration date. Read [this document](shoot_credentials_rotation.md#kubeconfig) to learn how to rotate the static token.
- The static token doesn't have any user identity associated with it. The user in that token will always be `system:cluster-admin` irrespective of the person accessing the cluster. Hence, it is impossible to audit the events in cluster.

When `enableStaticTokenKubeconfig` field is not explicitly set in the Shoot spec:
- for Shoot clusters using Kubernetes version < 1.26 the field is defaulted to `true`.
- for Shoot clusters using Kubernetes version >= 1.26 the field is defaulted to `false`.

> Note: Starting Kubernetes 1.27 the `enableStaticTokenKubeconfig` field will be locked to `false`. The [`shoots/adminkubeconfig` subresource](#shootsadminkubeconfig-subresource) should be used instead.
## `shoots/adminkubeconfig` subresource

The [`shoots/adminkubeconfig`](../proposals/16-adminkubeconfig-subresource.md) subresource allows users to dynamically generate temporary `kubeconfig`s that can be used to access shoot cluster with `cluster-admin` privileges. The credentials associated with this `kubeconfig` are client certificates which have a very short validity and must be renewed before they expire (by calling the subresource endpoint again).
Expand Down Expand Up @@ -53,7 +59,7 @@ Here, the `kubeconfig-request.json` has the following content:
## OpenID Connect

The `kube-apiserver` of shoot clusters can be provided with [OpenID Connect configuration](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) via the `ShootSpec`:
The `kube-apiserver` of shoot clusters can be provided with [OpenID Connect configuration](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) via the Shoot spec:

```yaml
apiVersion: core.gardener.cloud/v1beta1
Expand Down
2 changes: 1 addition & 1 deletion hack/local-development/start-apiserver
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ apiserver_flags="
--tls-private-key-file $TLS_KEY_FILE \
--feature-gates HAControlPlanes=true \
--feature-gates SeedChange=true \
--shoot-admin-kubeconfig-max-expiration=1h \
--shoot-admin-kubeconfig-max-expiration=24h \
--enable-admission-plugins=ShootVPAEnabledByDefault \
--v 2"

Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/core/types_shoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,9 @@ type Kubernetes struct {
Version string
// VerticalPodAutoscaler contains the configuration flags for the Kubernetes vertical pod autoscaler.
VerticalPodAutoscaler *VerticalPodAutoscaler
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret will be created for shoot (default: true).
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret will be created for the Shoot cluster.
// Defaults to true for Shoots with Kubernetes versions < 1.26. Defaults to false for Shoots with Kubernetes versions >= 1.26.
// Starting Kubernetes 1.27 the field will be locked to false.
EnableStaticTokenKubeconfig *bool
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/core/v1alpha1/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,12 @@ func SetDefaults_Shoot(obj *Shoot) {
}

if obj.Spec.Kubernetes.EnableStaticTokenKubeconfig == nil {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(true)
// Error is ignored here because we cannot do anything meaningful with it - variable will default to "false".
if k8sLessThan126, _ := versionutils.CheckVersionMeetsConstraint(obj.Spec.Kubernetes.Version, "< 1.26"); k8sLessThan126 {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(true)
} else {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(false)
}
}

if obj.Spec.Addons == nil {
Expand Down
30 changes: 27 additions & 3 deletions pkg/apis/core/v1alpha1/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,11 +734,35 @@ var _ = Describe("Defaults", func() {
Expect(obj.Spec.SystemComponents).To(Equal(&SystemComponents{CoreDNS: &CoreDNS{Autoscaling: &CoreDNSAutoscaling{Mode: CoreDNSAutoscalingModeHorizontal}}}))
})

It("should default the enableStaticTokenKubeconfig field", func() {
SetDefaults_Shoot(obj)
Context("static token kubeconfig", func() {
It("should not default the enableStaticTokenKubeconfig field when it is set", func() {
obj.Spec.Kubernetes = Kubernetes{
Version: "1.24.0",
EnableStaticTokenKubeconfig: pointer.Bool(false),
}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeTrue()))
Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeFalse()))
})

It("should default the enableStaticTokenKubeconfig field to true for k8s version < 1.26", func() {
obj.Spec.Kubernetes = Kubernetes{Version: "1.25.0"}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeTrue()))
})

It("should default the enableStaticTokenKubeconfig field to false for k8s version >= 1.26", func() {
obj.Spec.Kubernetes = Kubernetes{Version: "1.26.0"}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeFalse()))
})
})

Context("k8s version < 1.25", func() {
BeforeEach(func() {
obj.Spec.Kubernetes = Kubernetes{
Expand Down
5 changes: 3 additions & 2 deletions pkg/apis/core/v1alpha1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pkg/apis/core/v1alpha1/types_shoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,9 @@ type Kubernetes struct {
// VerticalPodAutoscaler contains the configuration flags for the Kubernetes vertical pod autoscaler.
// +optional
VerticalPodAutoscaler *VerticalPodAutoscaler `json:"verticalPodAutoscaler,omitempty" protobuf:"bytes,9,opt,name=verticalPodAutoscaler"`
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret should be present in garden cluster
// (default: true).
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret will be created for the Shoot cluster.
// Defaults to true for Shoots with Kubernetes versions < 1.26. Defaults to false for Shoots with Kubernetes versions >= 1.26.
// Starting Kubernetes 1.27 the field will be locked to false.
// +optional
EnableStaticTokenKubeconfig *bool `json:"enableStaticTokenKubeconfig,omitempty" protobuf:"varint,10,opt,name=enableStaticTokenKubeconfig"`
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/apis/core/v1beta1/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,12 @@ func SetDefaults_Shoot(obj *Shoot) {
}

if obj.Spec.Kubernetes.EnableStaticTokenKubeconfig == nil {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(true)
// Error is ignored here because we cannot do anything meaningful with it - variable will default to "false".
if k8sLessThan126, _ := versionutils.CheckVersionMeetsConstraint(obj.Spec.Kubernetes.Version, "< 1.26"); k8sLessThan126 {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(true)
} else {
obj.Spec.Kubernetes.EnableStaticTokenKubeconfig = pointer.Bool(false)
}
}

if obj.Spec.Addons == nil {
Expand Down Expand Up @@ -296,7 +301,7 @@ func SetDefaults_Shoot(obj *Shoot) {

if k8sVersionGreaterOrEqualThan122, _ := versionutils.CompareVersions(kubernetesVersion, ">=", "1.22"); !k8sVersionGreaterOrEqualThan122 {
// Error is ignored here because we cannot do anything meaningful with it.
// k8sVersionLessThan116 and k8sVersionGreaterOrEqualThan122 will default to `false`.
// k8sVersionGreaterOrEqualThan122 will default to `false`.
continue
}

Expand Down
43 changes: 26 additions & 17 deletions pkg/apis/core/v1beta1/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,10 +734,33 @@ var _ = Describe("Defaults", func() {
Expect(obj.Spec.SystemComponents).To(Equal(&SystemComponents{CoreDNS: &CoreDNS{Autoscaling: &CoreDNSAutoscaling{Mode: CoreDNSAutoscalingModeHorizontal}}}))
})

It("should default the enableStaticTokenKubeconfig field", func() {
SetDefaults_Shoot(obj)
Context("static token kubeconfig", func() {
It("should not default the enableStaticTokenKubeconfig field when it is set", func() {
obj.Spec.Kubernetes = Kubernetes{
Version: "1.24.0",
EnableStaticTokenKubeconfig: pointer.Bool(false),
}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeFalse()))
})

It("should default the enableStaticTokenKubeconfig field to true for k8s version < 1.26", func() {
obj.Spec.Kubernetes = Kubernetes{Version: "1.25.0"}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeTrue()))
})

It("should default the enableStaticTokenKubeconfig field to false for k8s version >= 1.26", func() {
obj.Spec.Kubernetes = Kubernetes{Version: "1.26.0"}

SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeTrue()))
Expect(obj.Spec.Kubernetes.EnableStaticTokenKubeconfig).To(PointTo(BeFalse()))
})
})

Context("k8s version < 1.25", func() {
Expand Down Expand Up @@ -782,20 +805,6 @@ var _ = Describe("Defaults", func() {
})
})
})

Context("k8s version >= 1.25", func() {
BeforeEach(func() {
obj.Spec.Kubernetes.Version = "1.25.0"
})

Context("allowPrivilegedContainers field is not set", func() {
It("should not set the field", func() {
SetDefaults_Shoot(obj)

Expect(obj.Spec.Kubernetes.AllowPrivilegedContainers).To(BeNil())
})
})
})
})

Describe("#SetDefaults_Maintenance", func() {
Expand Down
5 changes: 3 additions & 2 deletions pkg/apis/core/v1beta1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pkg/apis/core/v1beta1/types_shoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,9 @@ type Kubernetes struct {
// VerticalPodAutoscaler contains the configuration flags for the Kubernetes vertical pod autoscaler.
// +optional
VerticalPodAutoscaler *VerticalPodAutoscaler `json:"verticalPodAutoscaler,omitempty" protobuf:"bytes,9,opt,name=verticalPodAutoscaler"`
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret should be present in garden cluster
// (default: true).
// EnableStaticTokenKubeconfig indicates whether static token kubeconfig secret will be created for the Shoot cluster.
// Defaults to true for Shoots with Kubernetes versions < 1.26. Defaults to false for Shoots with Kubernetes versions >= 1.26.
// Starting Kubernetes 1.27 the field will be locked to false.
// +optional
EnableStaticTokenKubeconfig *bool `json:"enableStaticTokenKubeconfig,omitempty" protobuf:"varint,10,opt,name=enableStaticTokenKubeconfig"`
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/openapi/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions test/framework/k8s_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,15 +396,25 @@ func GetObjectFromSecret(ctx context.Context, k8sClient kubernetes.Interface, na
return "", fmt.Errorf("secret %s/%s did not contain object key %q", namespace, secretName, objectKey)
}

// NewClientFromServiceAccount returns a kubernetes client for a service account.
func NewClientFromServiceAccount(ctx context.Context, k8sClient kubernetes.Interface, account *corev1.ServiceAccount) (kubernetes.Interface, error) {
// CreateTokenForServiceAccount requests a service account token.
func CreateTokenForServiceAccount(ctx context.Context, k8sClient kubernetes.Interface, serviceAccount *corev1.ServiceAccount, expirationSeconds *int64) (string, error) {
tokenRequest := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
ExpirationSeconds: pointer.Int64(3600),
ExpirationSeconds: expirationSeconds,
},
}

token, err := k8sClient.Kubernetes().CoreV1().ServiceAccounts(account.Namespace).CreateToken(ctx, account.Name, tokenRequest, metav1.CreateOptions{})
result, err := k8sClient.Kubernetes().CoreV1().ServiceAccounts(serviceAccount.Namespace).CreateToken(ctx, serviceAccount.Name, tokenRequest, metav1.CreateOptions{})
if err != nil {
return "", err
}

return result.Status.Token, nil
}

// NewClientFromServiceAccount returns a kubernetes client for a service account.
func NewClientFromServiceAccount(ctx context.Context, k8sClient kubernetes.Interface, serviceAccount *corev1.ServiceAccount) (kubernetes.Interface, error) {
token, err := CreateTokenForServiceAccount(ctx, k8sClient, serviceAccount, pointer.Int64(3600))
if err != nil {
return nil, err
}
Expand All @@ -415,7 +425,7 @@ func NewClientFromServiceAccount(ctx context.Context, k8sClient kubernetes.Inter
Insecure: false,
CAData: k8sClient.RESTConfig().CAData,
},
BearerToken: token.Status.Token,
BearerToken: token,
}

return kubernetes.NewWithConfig(
Expand Down
7 changes: 6 additions & 1 deletion test/framework/resources/templates/default-shoot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ metadata:
namespace: abc
spec:
kubernetes:
# Enable the static token kubeconfig for the test-machinery integration tests until we figure out
# which test/components need the Shoot kubeconfig that is downloaded to $TM_KUBECONFIG_PATH/shoot.config in the Shoot creation integration test.
# See https://github.com/gardener/gardener/blob/7c63e2a6c5d46f9f1cb676602f864275ab8cab40/test/framework/shootcreationframework.go#L423-L425
# TODO(ialidzhikov): Remove the enableStaticTokenKubeconfig field after we figure out which tests/components need it
# and let the default gardener-apiserver value to be used.
enableStaticTokenKubeconfig: true
kubeAPIServer:
enableBasicAuthentication: false
dns: {}
Expand All @@ -28,4 +34,3 @@ spec:
kubernetesDashboard:
enabled: true
authenticationMode: token

13 changes: 5 additions & 8 deletions test/framework/shootframework.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import (
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
"github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
"github.com/gardener/gardener/pkg/client/kubernetes"
gutil "github.com/gardener/gardener/pkg/utils/gardener"
kutil "github.com/gardener/gardener/pkg/utils/kubernetes"
"github.com/gardener/gardener/pkg/utils/retry"
"github.com/gardener/gardener/test/utils/shoots/access"

"github.com/onsi/ginkgo/v2"
appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -156,9 +156,8 @@ func (f *ShootFramework) AddShoot(ctx context.Context, shootName, shootNamespace
}

var (
shootClient kubernetes.Interface
shoot = &gardencorev1beta1.Shoot{}
err error
shoot = &gardencorev1beta1.Shoot{}
err error
)

if err := f.GardenClient.Client().Get(ctx, client.ObjectKey{Namespace: shootNamespace, Name: shootName}, shoot); err != nil {
Expand Down Expand Up @@ -190,11 +189,9 @@ func (f *ShootFramework) AddShoot(ctx context.Context, shootName, shootNamespace
}

if !f.GardenerFramework.Config.SkipAccessingShoot {
var shootClient kubernetes.Interface
if err := retry.UntilTimeout(ctx, k8sClientInitPollInterval, k8sClientInitTimeout, func(ctx context.Context) (bool, error) {
shootClient, err = kubernetes.NewClientFromSecret(ctx, f.GardenClient.Client(), shoot.Namespace, shoot.Name+"."+gutil.ShootProjectSecretSuffixKubeconfig,
kubernetes.WithClientOptions(client.Options{Scheme: kubernetes.ShootScheme}),
kubernetes.WithDisabledCachedClient(),
)
shootClient, err = access.CreateShootClientFromAdminKubeconfig(ctx, f.GardenClient, f.Shoot)
if err != nil {
return retry.MinorError(fmt.Errorf("could not construct Shoot client: %w", err))
}
Expand Down

0 comments on commit a9c5e99

Please sign in to comment.