From de3b89bac2ceb187c7b7cc4a64bc43c86041e63c Mon Sep 17 00:00:00 2001 From: Vince Prignano Date: Tue, 25 Jun 2024 21:26:02 -0700 Subject: [PATCH] Allow CAPBK to generate JoinConfiguration discovery kubeconfig Signed-off-by: Vince Prignano --- .../kubeadm/api/v1beta1/kubeadm_types.go | 24 ++ .../api/v1beta1/zz_generated.deepcopy.go | 29 +- ...strap.cluster.x-k8s.io_kubeadmconfigs.yaml | 257 ++++++++++++++++ ...uster.x-k8s.io_kubeadmconfigtemplates.yaml | 271 +++++++++++++++++ .../controllers/kubeadmconfig_controller.go | 83 +++++- .../kubeadmconfig_controller_test.go | 135 ++++++++- .../types/upstreamv1beta2/conversion.go | 5 + .../zz_generated.conversion.go | 36 ++- .../types/upstreamv1beta3/conversion.go | 5 + .../zz_generated.conversion.go | 36 ++- .../types/upstreamv1beta4/conversion.go | 5 + .../zz_generated.conversion.go | 36 ++- ...cluster.x-k8s.io_kubeadmcontrolplanes.yaml | 262 +++++++++++++++++ ...x-k8s.io_kubeadmcontrolplanetemplates.yaml | 275 ++++++++++++++++++ .../bootstrap/kubeadm/v1alpha3/conversion.go | 8 + .../bootstrap/kubeadm/v1alpha4/conversion.go | 13 + .../v1alpha4/zz_generated.conversion.go | 26 +- 17 files changed, 1460 insertions(+), 46 deletions(-) diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go b/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go index 67b4f51cd764..9bcd08c6e215 100644 --- a/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go +++ b/bootstrap/kubeadm/api/v1beta1/kubeadm_types.go @@ -24,6 +24,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" bootstrapapi "k8s.io/cluster-bootstrap/token/api" bootstraputil "k8s.io/cluster-bootstrap/token/util" ) @@ -512,6 +513,29 @@ type BootstrapTokenDiscovery struct { type FileDiscovery struct { // KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to load cluster information KubeConfigPath string `json:"kubeConfigPath"` + + // KubeConfig is used (optionally) to generate a KubeConfig based on the Cluster's information. + // The kubeconfig is generated with a server and context matching the Cluster's name, + // Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + // Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + // +optional + KubeConfig *FileDiscoveryKubeConfig `json:"kubeConfig,omitempty"` +} + +// FileDiscoveryKubeConfig contains elements describing how to generate the kubeconfig for bootstrapping. +type FileDiscoveryKubeConfig struct { + // Cluster contains information about how to communicate with the kubernetes cluster. + // + // By default the following fields are automatically populated: + // - Name with the Cluster's name. + // - Server with the Cluster's ControlPlaneEndpoint. + // - CertificateAuthorityData with the Cluster's CA certificate. + // +optional + Cluster *clientcmdv1.Cluster `json:"cluster,omitempty"` + + // User contains information that describes identity information. + // This is use to tell the kubernetes cluster who you are. + User clientcmdv1.AuthInfo `json:"user"` } // HostPathMount contains elements describing volumes that are mounted from the diff --git a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go index 425b90edab0b..904d0dd00149 100644 --- a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go +++ b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" apiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -279,7 +280,7 @@ func (in *Discovery) DeepCopyInto(out *Discovery) { if in.File != nil { in, out := &in.File, &out.File *out = new(FileDiscovery) - **out = **in + (*in).DeepCopyInto(*out) } if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout @@ -395,6 +396,11 @@ func (in *File) DeepCopy() *File { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileDiscovery) DeepCopyInto(out *FileDiscovery) { *out = *in + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = new(FileDiscoveryKubeConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscovery. @@ -407,6 +413,27 @@ func (in *FileDiscovery) DeepCopy() *FileDiscovery { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FileDiscoveryKubeConfig) DeepCopyInto(out *FileDiscoveryKubeConfig) { + *out = *in + if in.Cluster != nil { + in, out := &in.Cluster, &out.Cluster + *out = new(apiv1.Cluster) + (*in).DeepCopyInto(*out) + } + in.User.DeepCopyInto(&out.User) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscoveryKubeConfig. +func (in *FileDiscoveryKubeConfig) DeepCopy() *FileDiscoveryKubeConfig { + if in == nil { + return nil + } + out := new(FileDiscoveryKubeConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileSource) DeepCopyInto(out *FileSource) { *out = *in diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml index 3375f583c6cc..5879e55594c7 100644 --- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml +++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml @@ -2840,6 +2840,263 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the Cluster's information. + The kubeconfig is generated with a server and context matching the Cluster's name, + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Name with the Cluster's name. + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority: + description: CertificateAuthority is the path + to a cert file for the certificate authority. + type: string + certificate-authority-data: + description: CertificateAuthorityData contains + PEM-encoded certificate authority certificates. + Overrides CertificateAuthority + format: byte + type: string + disable-compression: + description: |- + DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful + to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on + compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296. + type: boolean + extensions: + description: Extensions holds additional information. + This is useful for extenders so that reads and + writes don't clobber unknown fields + items: + description: NamedExtension relates nicknames + to extension information + properties: + extension: + description: Extension holds the extension + information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname for this + Extension + type: string + required: + - extension + - name + type: object + type: array + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips the validity + check for the server's certificate. This will + make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of the kubernetes + cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to check server + certificate. If TLSServerName is empty, the + hostname used to contact the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is use to tell the kubernetes cluster who you are. + properties: + as: + description: Impersonate is the username to impersonate. The + name matches the flag. + type: string + as-groups: + description: ImpersonateGroups is the groups to + impersonate. + items: + type: string + type: array + as-uid: + description: ImpersonateUID is the uid to impersonate. + type: string + as-user-extra: + additionalProperties: + items: + type: string + type: array + description: ImpersonateUserExtra contains additional + information for impersonated user. + type: object + auth-provider: + description: AuthProvider specifies a custom authentication + plugin for the kubernetes cluster. + properties: + config: + additionalProperties: + type: string + type: object + name: + type: string + required: + - config + - name + type: object + client-certificate: + description: ClientCertificate is the path to + a client cert file for TLS. + type: string + client-certificate-data: + description: ClientCertificateData contains PEM-encoded + data from a client cert file for TLS. Overrides + ClientCertificate + format: byte + type: string + client-key: + description: ClientKey is the path to a client + key file for TLS. + type: string + client-key-data: + description: ClientKeyData contains PEM-encoded + data from a client key file for TLS. Overrides + ClientKey + format: byte + type: string + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to the command + when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + ExecEnvVar is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + installHint: + description: |- + This text is shown to the user when the executable doesn't seem to be + present. For example, `brew install foo-cli` might be a good InstallHint for + foo-cli on Mac OS systems. + type: string + interactiveMode: + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + extensions: + description: Extensions holds additional information. + This is useful for extenders so that reads and + writes don't clobber unknown fields + items: + description: NamedExtension relates nicknames + to extension information + properties: + extension: + description: Extension holds the extension + information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname for this + Extension + type: string + required: + - extension + - name + type: object + type: array + password: + description: Password is the password for basic + authentication to the kubernetes cluster. + type: string + token: + description: Token is the bearer token for authentication + to the kubernetes cluster. + type: string + tokenFile: + description: TokenFile is a pointer to a file + that contains a bearer token (as described above). If + both Token and TokenFile are present, Token + takes precedence. + type: string + username: + description: Username is the username for basic + authentication to the kubernetes cluster. + type: string + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml index b9970098e05d..121a121b75cc 100644 --- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml +++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml @@ -2805,6 +2805,277 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the Cluster's information. + The kubeconfig is generated with a server and context matching the Cluster's name, + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Name with the Cluster's name. + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority: + description: CertificateAuthority is the + path to a cert file for the certificate + authority. + type: string + certificate-authority-data: + description: CertificateAuthorityData + contains PEM-encoded certificate authority + certificates. Overrides CertificateAuthority + format: byte + type: string + disable-compression: + description: |- + DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful + to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on + compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296. + type: boolean + extensions: + description: Extensions holds additional + information. This is useful for extenders + so that reads and writes don't clobber + unknown fields + items: + description: NamedExtension relates + nicknames to extension information + properties: + extension: + description: Extension holds the + extension information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname + for this Extension + type: string + required: + - extension + - name + type: object + type: array + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips + the validity check for the server's + certificate. This will make your HTTPS + connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of + the kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to + check server certificate. If TLSServerName + is empty, the hostname used to contact + the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is use to tell the kubernetes cluster who you are. + properties: + as: + description: Impersonate is the username + to impersonate. The name matches the + flag. + type: string + as-groups: + description: ImpersonateGroups is the + groups to impersonate. + items: + type: string + type: array + as-uid: + description: ImpersonateUID is the uid + to impersonate. + type: string + as-user-extra: + additionalProperties: + items: + type: string + type: array + description: ImpersonateUserExtra contains + additional information for impersonated + user. + type: object + auth-provider: + description: AuthProvider specifies a + custom authentication plugin for the + kubernetes cluster. + properties: + config: + additionalProperties: + type: string + type: object + name: + type: string + required: + - config + - name + type: object + client-certificate: + description: ClientCertificate is the + path to a client cert file for TLS. + type: string + client-certificate-data: + description: ClientCertificateData contains + PEM-encoded data from a client cert + file for TLS. Overrides ClientCertificate + format: byte + type: string + client-key: + description: ClientKey is the path to + a client key file for TLS. + type: string + client-key-data: + description: ClientKeyData contains PEM-encoded + data from a client key file for TLS. + Overrides ClientKey + format: byte + type: string + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes + cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to + the command when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + ExecEnvVar is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + installHint: + description: |- + This text is shown to the user when the executable doesn't seem to be + present. For example, `brew install foo-cli` might be a good InstallHint for + foo-cli on Mac OS systems. + type: string + interactiveMode: + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + extensions: + description: Extensions holds additional + information. This is useful for extenders + so that reads and writes don't clobber + unknown fields + items: + description: NamedExtension relates + nicknames to extension information + properties: + extension: + description: Extension holds the + extension information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname + for this Extension + type: string + required: + - extension + - name + type: object + type: array + password: + description: Password is the password + for basic authentication to the kubernetes + cluster. + type: string + token: + description: Token is the bearer token + for authentication to the kubernetes + cluster. + type: string + tokenFile: + description: TokenFile is a pointer to + a file that contains a bearer token + (as described above). If both Token + and TokenFile are present, Token takes + precedence. + type: string + username: + description: Username is the username + for basic authentication to the kubernetes + cluster. + type: string + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go index 0cf55f48e373..c6dda2a66a9f 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" kerrors "k8s.io/apimachinery/pkg/util/errors" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" bootstrapapi "k8s.io/cluster-bootstrap/token/api" bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets" "k8s.io/klog/v2" @@ -40,6 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/yaml" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" @@ -741,6 +743,15 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S return ctrl.Result{}, err } + if discoveryFile := scope.Config.Spec.JoinConfiguration.Discovery.File; discoveryFile != nil && discoveryFile.KubeConfig != nil { + kubeconfig, err := r.resolveDiscoveryKubeConfig(scope.Config) + if err != nil { + conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error()) + return ctrl.Result{}, err + } + files = append(files, *kubeconfig) + } + controlPlaneJoinInput := &cloudinit.ControlPlaneJoinInput{ JoinConfiguration: joinData, Certificates: certificates, @@ -842,6 +853,47 @@ func (r *KubeadmConfigReconciler) resolveUsers(ctx context.Context, cfg *bootstr return collected, nil } +func (r *KubeadmConfigReconciler) resolveDiscoveryKubeConfig(config *bootstrapv1.KubeadmConfig) (*bootstrapv1.File, error) { + cfg := config.Spec.JoinConfiguration.Discovery.File + if cfg == nil || cfg.KubeConfig == nil { + return nil, errors.New("no discovery configuration file to resolve") + } + kubeconfig := clientcmdv1.Config{ + CurrentContext: "default", + Contexts: []clientcmdv1.NamedContext{ + { + Name: "default", + Context: clientcmdv1.Context{ + Cluster: "default", + AuthInfo: "default", + }, + }, + }, + Clusters: []clientcmdv1.NamedCluster{ + { + Name: "default", + Cluster: *cfg.KubeConfig.Cluster, + }, + }, + AuthInfos: []clientcmdv1.NamedAuthInfo{ + { + Name: "default", + AuthInfo: cfg.KubeConfig.User, + }, + }, + } + b, err := yaml.Marshal(kubeconfig) + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal kubeconfig from JoinConfiguration.Discovery.File.KubeConfig") + } + return &bootstrapv1.File{ + Path: cfg.KubeConfigPath, + Owner: "root:root", + Permissions: "0640", + Content: string(b), + }, nil +} + // resolveSecretUserContent returns passwd fetched from a referenced secret object. func (r *KubeadmConfigReconciler) resolveSecretPasswordContent(ctx context.Context, ns string, source bootstrapv1.User) ([]byte, error) { secret := &corev1.Secret{} @@ -970,7 +1022,7 @@ func (r *KubeadmConfigReconciler) reconcileDiscovery(ctx context.Context, cluste // if config already contains a file discovery configuration, respect it without further validations if config.Spec.JoinConfiguration.Discovery.File != nil { - return ctrl.Result{}, nil + return r.reconcileDiscoveryFile(ctx, cluster, config, certificates) } // otherwise it is necessary to ensure token discovery is properly configured @@ -1026,6 +1078,35 @@ func (r *KubeadmConfigReconciler) reconcileDiscovery(ctx context.Context, cluste return ctrl.Result{}, nil } +func (r *KubeadmConfigReconciler) reconcileDiscoveryFile(ctx context.Context, cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig, certificates secret.Certificates) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + cfg := config.Spec.JoinConfiguration.Discovery.File.KubeConfig + if config.Spec.JoinConfiguration.Discovery.File.KubeConfig == nil { + // Nothing else to do. + return ctrl.Result{}, nil + } + + if cfg.Cluster == nil { + cfg.Cluster = &clientcmdv1.Cluster{} + } + + if cfg.Cluster.Server == "" { + if !cluster.Spec.ControlPlaneEndpoint.IsValid() { + log.V(1).Info("Waiting for Cluster Controller to set Cluster.Spec.ControlPlaneEndpoint") + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + cfg.Cluster.Server = fmt.Sprintf("https://%s", cluster.Spec.ControlPlaneEndpoint.String()) + log.V(3).Info("Altering JoinConfiguration.Discovery.File.KubeConfig.Cluster.Server", "apiServerEndpoint", cfg.Cluster.Server) + } + + if len(cfg.Cluster.CertificateAuthorityData) == 0 { + cfg.Cluster.CertificateAuthorityData = certificates.GetByPurpose(secret.ClusterCA).KeyPair.Cert + log.V(3).Info("Altering JoinConfiguration.Discovery.File.KubeConfig.CertificateAuthorityData") + } + + return ctrl.Result{}, nil +} + // reconcileTopLevelObjectSettings injects into config.ClusterConfiguration values from top level objects like cluster and machine. // The implementation func respect user provided config values, but in case some of them are missing, values from top level objects are used. func (r *KubeadmConfigReconciler) reconcileTopLevelObjectSettings(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine, config *bootstrapv1.KubeadmConfig) { diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go index a2f02c3cc1a0..bcfd16466380 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go @@ -29,6 +29,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" bootstrapapi "k8s.io/cluster-bootstrap/token/api" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" @@ -46,9 +47,11 @@ import ( "sigs.k8s.io/cluster-api/feature" "sigs.k8s.io/cluster-api/internal/test/builder" "sigs.k8s.io/cluster-api/util" + "sigs.k8s.io/cluster-api/util/certs" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/cluster-api/util/secret" + utilyaml "sigs.k8s.io/cluster-api/util/yaml" ) // MachineToBootstrapMapFunc return kubeadm bootstrap configref name when configref exists. @@ -1497,6 +1500,37 @@ func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileBehaviors(t *testin return nil }, }, + { + name: "Respect discoveryConfiguration.File.KubeConfig", + cluster: goodcluster, + config: &bootstrapv1.KubeadmConfig{ + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: &bootstrapv1.JoinConfiguration{ + Discovery: bootstrapv1.Discovery{ + File: &bootstrapv1.FileDiscovery{ + KubeConfigPath: "/bootstrap-kubeconfig.yaml", + KubeConfig: &bootstrapv1.FileDiscoveryKubeConfig{ + User: clientcmdv1.AuthInfo{ + Username: "admin", + Password: "admin", + }, + }, + }, + }, + }, + }, + }, + validateDiscovery: func(g *WithT, c *bootstrapv1.KubeadmConfig) error { + d := c.Spec.JoinConfiguration.Discovery + g.Expect(d.BootstrapToken).To(BeNil()) + g.Expect(d.File.KubeConfig.User.Username).To(Equal("admin")) + g.Expect(d.File.KubeConfig.User.Password).To(Equal("admin")) + g.Expect(d.File.KubeConfig.Cluster).ToNot(BeNil()) + g.Expect(d.File.KubeConfig.Cluster.Server).To(Equal("https://example.com:6443")) + g.Expect(d.File.KubeConfig.Cluster.CertificateAuthorityData).To(BeEquivalentTo("ca-data")) + return nil + }, + }, { name: "Respect discoveryConfiguration.BootstrapToken.APIServerEndpoint", cluster: goodcluster, @@ -1573,7 +1607,15 @@ func TestKubeadmConfigReconciler_Reconcile_DiscoveryReconcileBehaviors(t *testin KubeadmInitLock: &myInitLocker{}, } - res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{}) + res, err := k.reconcileDiscovery(ctx, tc.cluster, tc.config, secret.Certificates{ + &secret.Certificate{ + Purpose: secret.ClusterCA, + KeyPair: &certs.KeyPair{ + Cert: []byte("ca-data"), + Key: []byte("ca-key"), + }, + }, + }) g.Expect(res.IsZero()).To(BeTrue()) g.Expect(err).ToNot(HaveOccurred()) @@ -2147,6 +2189,97 @@ func TestKubeadmConfigReconciler_ResolveFiles(t *testing.T) { } } +func TestKubeadmConfigReconciler_ResolveDiscoveryFileKubeConfig(t *testing.T) { + cases := map[string]struct { + cfg *bootstrapv1.KubeadmConfig + expect *bootstrapv1.File + err string + }{ + "should generate the bootstrap kubeconfig correctly": { + cfg: &bootstrapv1.KubeadmConfig{ + Spec: bootstrapv1.KubeadmConfigSpec{ + JoinConfiguration: &bootstrapv1.JoinConfiguration{ + Discovery: bootstrapv1.Discovery{ + File: &bootstrapv1.FileDiscovery{ + KubeConfigPath: "/bootstrap-kubeconfig.yaml", + KubeConfig: &bootstrapv1.FileDiscoveryKubeConfig{ + User: clientcmdv1.AuthInfo{ + Username: "admin", + Password: "admin", + }, + }, + }, + }, + }, + }, + }, + expect: &bootstrapv1.File{ + Path: "/bootstrap-kubeconfig.yaml", + Owner: "root:root", + Permissions: "0640", + Content: utilyaml.Raw(` +clusters: +- cluster: + certificate-authority-data: Y2EtZGF0YQ== + server: https://example.com:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +preferences: {} +users: +- name: default + user: + password: admin + username: admin +`), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + + myclient := fake.NewClientBuilder().Build() + k := &KubeadmConfigReconciler{ + Client: myclient, + SecretCachingClient: myclient, + KubeadmInitLock: &myInitLocker{}, + } + + _, err := k.reconcileDiscoveryFile(ctx, &clusterv1.Cluster{ + Spec: clusterv1.ClusterSpec{ + ControlPlaneEndpoint: clusterv1.APIEndpoint{ + Host: "example.com", + Port: 6443, + }, + }, + }, tc.cfg, secret.Certificates{ + &secret.Certificate{ + Purpose: secret.ClusterCA, + KeyPair: &certs.KeyPair{ + Cert: []byte("ca-data"), + Key: []byte("ca-key"), + }, + }, + }) + g.Expect(err).ToNot(HaveOccurred()) + + file, err := k.resolveDiscoveryKubeConfig(tc.cfg) + if tc.err != "" { + g.Expect(err).To(MatchError(ContainSubstring(tc.err))) + return + } + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(file).To(BeEquivalentTo(tc.expect)) + }) + } +} + func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) { fakePasswd := "bar" testSecret := &corev1.Secret{ diff --git a/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go index 486e8962528a..f40ac2ab3cdc 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta2/conversion.go @@ -101,3 +101,8 @@ func Convert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta2_NodeRegistration // NodeRegistrationOptions.IgnorePreflightErrors and ImagePullPolicy does not exist in kubeadm v1beta2, dropping those info. return autoConvert_v1beta1_NodeRegistrationOptions_To_upstreamv1beta2_NodeRegistrationOptions(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in, out, s) +} diff --git a/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go index 0769ceb3a65f..c9d29c1d2ba2 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta2/zz_generated.conversion.go @@ -153,11 +153,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta2_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -233,6 +228,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.InitConfiguration)(nil), (*InitConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_InitConfiguration_To_upstreamv1beta2_InitConfiguration(a.(*v1beta1.InitConfiguration), b.(*InitConfiguration), scope) }); err != nil { @@ -505,7 +505,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta2_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta2_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta2_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -518,7 +526,15 @@ func Convert_upstreamv1beta2_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta2_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -589,14 +605,10 @@ func Convert_upstreamv1beta2_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta2_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta2_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go index feef95647790..b6ff0ac5bdb3 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta3/conversion.go @@ -64,3 +64,8 @@ func Convert_upstreamv1beta3_JoinControlPlane_To_v1beta1_JoinControlPlane(in *Jo // JoinControlPlane.CertificateKey does not exist in CABPK v1beta1 version, because Cluster API does not use automatic copy certs. return autoConvert_upstreamv1beta3_JoinControlPlane_To_v1beta1_JoinControlPlane(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in, out, s) +} diff --git a/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go index 084d9d501c84..cfd45bf23834 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta3/zz_generated.conversion.go @@ -153,11 +153,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta3_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -248,6 +243,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } return nil } @@ -493,7 +493,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta3_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta3_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta3_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -506,7 +514,15 @@ func Convert_upstreamv1beta3_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta3_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -577,14 +593,10 @@ func Convert_upstreamv1beta3_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta3_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta3_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go b/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go index 5a34620fefa4..b8b8987ae000 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta4/conversion.go @@ -176,6 +176,11 @@ func Convert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in *bootstrapv1.Disc return autoConvert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in, out, s) } +func Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apimachineryconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in kubeadm because it's internal to Cluster API, dropping those info. + return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in, out, s) +} + // convertToArgs takes a argument map and converts it to a slice of arguments. // Te resulting argument slice is sorted alpha-numerically. func convertToArgs(in map[string]string) []Arg { diff --git a/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go b/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go index b2cde96018a9..fd2350a66e96 100644 --- a/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go +++ b/bootstrap/kubeadm/types/upstreamv1beta4/zz_generated.conversion.go @@ -123,11 +123,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*v1beta1.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_upstreamv1beta4_HostPathMount_To_v1beta1_HostPathMount(a.(*HostPathMount), b.(*v1beta1.HostPathMount), scope) }); err != nil { @@ -233,6 +228,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(a.(*v1beta1.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.JoinConfiguration)(nil), (*JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_JoinConfiguration_To_upstreamv1beta4_JoinConfiguration(a.(*v1beta1.JoinConfiguration), b.(*JoinConfiguration), scope) }); err != nil { @@ -473,7 +473,15 @@ func Convert_v1beta1_DNS_To_upstreamv1beta4_DNS(in *v1beta1.DNS, out *DNS, s con func autoConvert_upstreamv1beta4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_upstreamv1beta4_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken return nil } @@ -485,7 +493,15 @@ func Convert_upstreamv1beta4_Discovery_To_v1beta1_Discovery(in *Discovery, out * func autoConvert_v1beta1_Discovery_To_upstreamv1beta4_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken // WARNING: in.Timeout requires manual conversion: does not exist in peer-type return nil @@ -567,14 +583,10 @@ func Convert_upstreamv1beta4_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDisc func autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_upstreamv1beta4_FileDiscovery(in, out, s) -} - func autoConvert_upstreamv1beta4_HostPathMount_To_v1beta1_HostPathMount(in *HostPathMount, out *v1beta1.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml index 1171314f06ed..6cca3da1a24f 100644 --- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml @@ -3315,6 +3315,268 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the Cluster's information. + The kubeconfig is generated with a server and context matching the Cluster's name, + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Name with the Cluster's name. + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority: + description: CertificateAuthority is the path + to a cert file for the certificate authority. + type: string + certificate-authority-data: + description: CertificateAuthorityData contains + PEM-encoded certificate authority certificates. + Overrides CertificateAuthority + format: byte + type: string + disable-compression: + description: |- + DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful + to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on + compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296. + type: boolean + extensions: + description: Extensions holds additional information. + This is useful for extenders so that reads + and writes don't clobber unknown fields + items: + description: NamedExtension relates nicknames + to extension information + properties: + extension: + description: Extension holds the extension + information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname for + this Extension + type: string + required: + - extension + - name + type: object + type: array + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips the + validity check for the server's certificate. + This will make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of the + kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to check + server certificate. If TLSServerName is + empty, the hostname used to contact the + server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is use to tell the kubernetes cluster who you are. + properties: + as: + description: Impersonate is the username to + impersonate. The name matches the flag. + type: string + as-groups: + description: ImpersonateGroups is the groups + to impersonate. + items: + type: string + type: array + as-uid: + description: ImpersonateUID is the uid to + impersonate. + type: string + as-user-extra: + additionalProperties: + items: + type: string + type: array + description: ImpersonateUserExtra contains + additional information for impersonated + user. + type: object + auth-provider: + description: AuthProvider specifies a custom + authentication plugin for the kubernetes + cluster. + properties: + config: + additionalProperties: + type: string + type: object + name: + type: string + required: + - config + - name + type: object + client-certificate: + description: ClientCertificate is the path + to a client cert file for TLS. + type: string + client-certificate-data: + description: ClientCertificateData contains + PEM-encoded data from a client cert file + for TLS. Overrides ClientCertificate + format: byte + type: string + client-key: + description: ClientKey is the path to a client + key file for TLS. + type: string + client-key-data: + description: ClientKeyData contains PEM-encoded + data from a client key file for TLS. Overrides + ClientKey + format: byte + type: string + exec: + description: Exec specifies a custom exec-based + authentication plugin for the kubernetes + cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass to the + command when executing it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + ExecEnvVar is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + installHint: + description: |- + This text is shown to the user when the executable doesn't seem to be + present. For example, `brew install foo-cli` might be a good InstallHint for + foo-cli on Mac OS systems. + type: string + interactiveMode: + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + extensions: + description: Extensions holds additional information. + This is useful for extenders so that reads + and writes don't clobber unknown fields + items: + description: NamedExtension relates nicknames + to extension information + properties: + extension: + description: Extension holds the extension + information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname for + this Extension + type: string + required: + - extension + - name + type: object + type: array + password: + description: Password is the password for + basic authentication to the kubernetes cluster. + type: string + token: + description: Token is the bearer token for + authentication to the kubernetes cluster. + type: string + tokenFile: + description: TokenFile is a pointer to a file + that contains a bearer token (as described + above). If both Token and TokenFile are + present, Token takes precedence. + type: string + username: + description: Username is the username for + basic authentication to the kubernetes cluster. + type: string + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml index 44f0656ef654..5b364acb695a 100644 --- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml @@ -2013,6 +2013,281 @@ spec: File is used to specify a file or URL to a kubeconfig file from which to load cluster information BootstrapToken and File are mutually exclusive properties: + kubeConfig: + description: |- + KubeConfig is used (optionally) to generate a KubeConfig based on the Cluster's information. + The kubeconfig is generated with a server and context matching the Cluster's name, + Host address (server field) information is automatically populated based on the Cluster's ControlPlaneEndpoint. + Certificate Authority (certificate-authority-data field) is gathered from the cluster's CA secret. + properties: + cluster: + description: |- + Cluster contains information about how to communicate with the kubernetes cluster. + + + By default the following fields are automatically populated: + - Name with the Cluster's name. + - Server with the Cluster's ControlPlaneEndpoint. + - CertificateAuthorityData with the Cluster's CA certificate. + properties: + certificate-authority: + description: CertificateAuthority + is the path to a cert file for the + certificate authority. + type: string + certificate-authority-data: + description: CertificateAuthorityData + contains PEM-encoded certificate + authority certificates. Overrides + CertificateAuthority + format: byte + type: string + disable-compression: + description: |- + DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful + to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on + compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296. + type: boolean + extensions: + description: Extensions holds additional + information. This is useful for + extenders so that reads and writes + don't clobber unknown fields + items: + description: NamedExtension relates + nicknames to extension information + properties: + extension: + description: Extension holds + the extension information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname + for this Extension + type: string + required: + - extension + - name + type: object + type: array + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify + skips the validity check for the + server's certificate. This will + make your HTTPS connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address + of the kubernetes cluster (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used + to check server certificate. If + TLSServerName is empty, the hostname + used to contact the server is used. + type: string + required: + - server + type: object + user: + description: |- + User contains information that describes identity information. + This is use to tell the kubernetes cluster who you are. + properties: + as: + description: Impersonate is the username + to impersonate. The name matches + the flag. + type: string + as-groups: + description: ImpersonateGroups is + the groups to impersonate. + items: + type: string + type: array + as-uid: + description: ImpersonateUID is the + uid to impersonate. + type: string + as-user-extra: + additionalProperties: + items: + type: string + type: array + description: ImpersonateUserExtra + contains additional information + for impersonated user. + type: object + auth-provider: + description: AuthProvider specifies + a custom authentication plugin for + the kubernetes cluster. + properties: + config: + additionalProperties: + type: string + type: object + name: + type: string + required: + - config + - name + type: object + client-certificate: + description: ClientCertificate is + the path to a client cert file for + TLS. + type: string + client-certificate-data: + description: ClientCertificateData + contains PEM-encoded data from a + client cert file for TLS. Overrides + ClientCertificate + format: byte + type: string + client-key: + description: ClientKey is the path + to a client key file for TLS. + type: string + client-key-data: + description: ClientKeyData contains + PEM-encoded data from a client key + file for TLS. Overrides ClientKey + format: byte + type: string + exec: + description: Exec specifies a custom + exec-based authentication plugin + for the kubernetes cluster. + properties: + apiVersion: + description: |- + Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + the same encoding version as the input. + type: string + args: + description: Arguments to pass + to the command when executing + it. + items: + type: string + type: array + command: + description: Command to execute. + type: string + env: + description: |- + Env defines additional environment variables to expose to the process. These + are unioned with the host's environment, as well as variables client-go uses + to pass argument to the plugin. + items: + description: |- + ExecEnvVar is used for setting environment variables when executing an exec-based + credential plugin. + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + installHint: + description: |- + This text is shown to the user when the executable doesn't seem to be + present. For example, `brew install foo-cli` might be a good InstallHint for + foo-cli on Mac OS systems. + type: string + interactiveMode: + description: |- + InteractiveMode determines this plugin's relationship with standard input. Valid + values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + exec plugin wants to use standard input if it is available), or "Always" (this exec + plugin requires standard input to function). See ExecInteractiveMode values for more + details. + + + If APIVersion is client.authentication.k8s.io/v1alpha1 or + client.authentication.k8s.io/v1beta1, then this field is optional and defaults + to "IfAvailable" when unset. Otherwise, this field is required. + type: string + provideClusterInfo: + description: |- + ProvideClusterInfo determines whether or not to provide cluster information, + which could potentially contain very large CA data, to this exec plugin as a + part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + reading this environment variable. + type: boolean + required: + - command + - provideClusterInfo + type: object + extensions: + description: Extensions holds additional + information. This is useful for + extenders so that reads and writes + don't clobber unknown fields + items: + description: NamedExtension relates + nicknames to extension information + properties: + extension: + description: Extension holds + the extension information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname + for this Extension + type: string + required: + - extension + - name + type: object + type: array + password: + description: Password is the password + for basic authentication to the + kubernetes cluster. + type: string + token: + description: Token is the bearer token + for authentication to the kubernetes + cluster. + type: string + tokenFile: + description: TokenFile is a pointer + to a file that contains a bearer + token (as described above). If + both Token and TokenFile are present, + Token takes precedence. + type: string + username: + description: Username is the username + for basic authentication to the + kubernetes cluster. + type: string + type: object + required: + - user + type: object kubeConfigPath: description: KubeConfigPath is used to specify the actual file path or URL to the kubeconfig diff --git a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go index 12e4c657c1d1..5b23e44d6a48 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha3/conversion.go @@ -93,6 +93,10 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy } + if restored.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.JoinConfiguration.Discovery.File.KubeConfig + } + return nil } @@ -193,6 +197,10 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.Template.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy } + if restored.Spec.Template.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig + } + return nil } diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go index 576fceebdf3f..7796fb6d89e2 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/conversion.go @@ -78,6 +78,10 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy } + if restored.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.JoinConfiguration.Discovery.File.KubeConfig + } + return nil } @@ -159,6 +163,10 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy = restored.Spec.Template.Spec.InitConfiguration.NodeRegistration.ImagePullPolicy } + if restored.Spec.Template.Spec.JoinConfiguration.Discovery.File != nil && restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig != nil { + dst.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig = restored.Spec.Template.Spec.JoinConfiguration.Discovery.File.KubeConfig + } + return nil } @@ -220,3 +228,8 @@ func Convert_v1beta1_KubeadmConfigTemplateResource_To_v1alpha4_KubeadmConfigTemp // KubeadmConfigTemplateResource.metadata does not exist in kubeadm v1alpha4. return autoConvert_v1beta1_KubeadmConfigTemplateResource_To_v1alpha4_KubeadmConfigTemplateResource(in, out, s) } + +func Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *bootstrapv1.FileDiscovery, out *FileDiscovery, s apiconversion.Scope) error { + // JoinConfiguration.Discovery.File.KubeConfig does not exist in v1alpha4 APIs. + return autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in, out, s) +} diff --git a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go index 5b88cc6fad97..72eeb7be0e23 100644 --- a/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go +++ b/internal/apis/bootstrap/kubeadm/v1alpha4/zz_generated.conversion.go @@ -675,7 +675,15 @@ func Convert_v1beta1_DNS_To_v1alpha4_DNS(in *v1beta1.DNS, out *DNS, s conversion func autoConvert_v1alpha4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1.Discovery, s conversion.Scope) error { out.BootstrapToken = (*v1beta1.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*v1beta1.FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(v1beta1.FileDiscovery) + if err := Convert_v1alpha4_FileDiscovery_To_v1beta1_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -688,7 +696,15 @@ func Convert_v1alpha4_Discovery_To_v1beta1_Discovery(in *Discovery, out *v1beta1 func autoConvert_v1beta1_Discovery_To_v1alpha4_Discovery(in *v1beta1.Discovery, out *Discovery, s conversion.Scope) error { out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) - out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + if err := Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(*in, *out, s); err != nil { + return err + } + } else { + out.File = nil + } out.TLSBootstrapToken = in.TLSBootstrapToken out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) return nil @@ -807,14 +823,10 @@ func Convert_v1alpha4_FileDiscovery_To_v1beta1_FileDiscovery(in *FileDiscovery, func autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { out.KubeConfigPath = in.KubeConfigPath + // WARNING: in.KubeConfig requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery is an autogenerated conversion function. -func Convert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in *v1beta1.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { - return autoConvert_v1beta1_FileDiscovery_To_v1alpha4_FileDiscovery(in, out, s) -} - func autoConvert_v1alpha4_FileSource_To_v1beta1_FileSource(in *FileSource, out *v1beta1.FileSource, s conversion.Scope) error { if err := Convert_v1alpha4_SecretFileSource_To_v1beta1_SecretFileSource(&in.Secret, &out.Secret, s); err != nil { return err