-
Notifications
You must be signed in to change notification settings - Fork 311
Pass cluster info to exec credential plugins (provideClusterInfo) #1808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3749bdf
5e2729f
615741e
cce84f7
df9c643
4db8289
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| using System.Net; | ||
| using System.Runtime.InteropServices; | ||
| using System.Security.Cryptography.X509Certificates; | ||
| using System.Text.Json.Nodes; | ||
|
|
||
| namespace k8s | ||
| { | ||
|
|
@@ -277,6 +278,10 @@ | |
| SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify; | ||
| TlsServerName = clusterDetails.ClusterEndpoint.TlsServerName; | ||
|
|
||
| // Reset CA data so it always reflects the cluster currently being resolved | ||
| // and is never carried over from a prior state. | ||
| CertificateAuthorityData = null; | ||
|
|
||
| if (!Uri.TryCreate(Host, UriKind.Absolute, out var uri)) | ||
| { | ||
| throw new KubeConfigException($"Bad server host URL `{Host}` (cannot be parsed)"); | ||
|
|
@@ -306,26 +311,28 @@ | |
| { | ||
| if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthorityData)) | ||
| { | ||
| var data = clusterDetails.ClusterEndpoint.CertificateAuthorityData; | ||
| CertificateAuthorityData = clusterDetails.ClusterEndpoint.CertificateAuthorityData; | ||
| #if NET9_0_OR_GREATER | ||
| SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificate(Convert.FromBase64String(data))); | ||
| SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificate(Convert.FromBase64String(CertificateAuthorityData))); | ||
| #else | ||
| string nullPassword = null; | ||
| // This null password is to change the constructor to fix this KB: | ||
| // https://support.microsoft.com/en-us/topic/kb5025823-change-in-how-net-applications-import-x-509-certificates-bf81c936-af2b-446e-9f7a-016f4713b46b | ||
| SslCaCerts = new X509Certificate2Collection(new X509Certificate2(Convert.FromBase64String(data), nullPassword)); | ||
| SslCaCerts = new X509Certificate2Collection(new X509Certificate2(Convert.FromBase64String(CertificateAuthorityData), nullPassword)); | ||
| #endif | ||
| } | ||
| else if (!string.IsNullOrEmpty(clusterDetails.ClusterEndpoint.CertificateAuthority)) | ||
| { | ||
|
mutsaddi-deshaw marked this conversation as resolved.
|
||
| var caPath = GetFullPath(k8SConfig, clusterDetails.ClusterEndpoint.CertificateAuthority); | ||
| CertificateAuthorityData = Convert.ToBase64String(File.ReadAllBytes(caPath)); | ||
|
|
||
| // File-path loaders auto-detect cert format (PEM/DER/PFX), which the | ||
| // byte-based APIs do not reliably do on pre-.NET 9. The second read is | ||
|
Comment on lines
+326
to
+330
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can a non-PEM CA file can realistically occur? kubeconfig CA files are PEM per spec, and I think such a file wouldn't work with kubectl or client-go either. client-go appears to forward the raw bytes unchanged and the non-AOT C# client also seems to assume PEM (CertUtils.LoadPemFileCert). Do we have a real scenario that produces a DER/PFX CA in a kubeconfig? If not, I'd rather not add handling for input the spec disallows. |
||
| // intentional to preserve format flexibility for SslCaCerts. | ||
| #if NET9_0_OR_GREATER | ||
| SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificateFromFile(GetFullPath( | ||
| k8SConfig, | ||
| clusterDetails.ClusterEndpoint.CertificateAuthority))); | ||
| SslCaCerts = new X509Certificate2Collection(X509CertificateLoader.LoadCertificateFromFile(caPath)); | ||
| #else | ||
| SslCaCerts = new X509Certificate2Collection(new X509Certificate2(GetFullPath( | ||
| k8SConfig, | ||
| clusterDetails.ClusterEndpoint.CertificateAuthority))); | ||
| SslCaCerts = new X509Certificate2Collection(new X509Certificate2(caPath)); | ||
| #endif | ||
| } | ||
| } | ||
|
|
@@ -416,7 +423,19 @@ | |
| throw new KubeConfigException("External command execution missing ApiVersion key"); | ||
| } | ||
|
|
||
| var response = ExecuteExternalCommand(userDetails.UserCredentials.ExternalExecution); | ||
| ClusterEndpoint clusterEndpoint = null; | ||
| if (userDetails.UserCredentials.ExternalExecution.ProvideClusterInfo) | ||
| { | ||
| clusterEndpoint = new ClusterEndpoint | ||
| { | ||
| Server = this.Host, | ||
| SkipTlsVerify = this.SkipTlsVerify, | ||
| TlsServerName = this.TlsServerName, | ||
| CertificateAuthorityData = this.CertificateAuthorityData, | ||
| }; | ||
| } | ||
|
|
||
| var response = ExecuteExternalCommand(userDetails.UserCredentials.ExternalExecution, clusterEndpoint); | ||
| AccessToken = response.Status.Token; | ||
| // When reading ClientCertificateData from a config file it will be base64 encoded, and code later in the system (see CertUtils.GeneratePfx) | ||
| // expects ClientCertificateData and ClientCertificateKeyData to be base64 encoded because of this. However the string returned by external | ||
|
|
@@ -429,7 +448,7 @@ | |
| // TODO: support client certificates here too. | ||
| if (AccessToken != null) | ||
| { | ||
| TokenProvider = new ExecTokenProvider(userDetails.UserCredentials.ExternalExecution); | ||
| TokenProvider = new ExecTokenProvider(userDetails.UserCredentials.ExternalExecution, clusterEndpoint); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -440,16 +459,79 @@ | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Converts a resolved <see cref="ClusterEndpoint"/> into the | ||
| /// <c>spec.cluster</c> JSON representation defined by the exec credential plugin | ||
| /// protocol (client.authentication.k8s.io/v1). Returns <c>null</c> if | ||
| /// <paramref name="cluster"/> is <c>null</c>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The AOT <see cref="ClusterEndpoint"/> does not include Extensions (dynamic types | ||
| /// are incompatible with AOT), so <c>spec.cluster.config</c> is not populated. | ||
| /// </remarks> | ||
| /// <seealso href="https://kubernetes.io/docs/reference/config-api/client-authentication.v1/#Cluster"/> | ||
| internal static JsonNode ToExecClusterInfo(ClusterEndpoint cluster) | ||
|
Check warning on line 473 in src/KubernetesClient.Aot/KubernetesClientConfiguration.ConfigFile.cs
|
||
| { | ||
| if (cluster == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var node = new JsonObject | ||
| { | ||
| ["server"] = cluster.Server, | ||
| }; | ||
|
|
||
| if (cluster.SkipTlsVerify) | ||
| { | ||
| node["insecure-skip-tls-verify"] = true; | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(cluster.CertificateAuthorityData)) | ||
| { | ||
| node["certificate-authority-data"] = cluster.CertificateAuthorityData; | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(cluster.TlsServerName)) | ||
| { | ||
| node["tls-server-name"] = cluster.TlsServerName; | ||
| } | ||
|
|
||
| return node; | ||
| } | ||
|
|
||
| public static Process CreateRunnableExternalProcess(ExternalExecution config, EventHandler<DataReceivedEventArgs> captureStdError = null) | ||
| { | ||
| return CreateRunnableExternalProcess(config, captureStdError, null); | ||
| } | ||
|
|
||
| public static Process CreateRunnableExternalProcess(ExternalExecution config, EventHandler<DataReceivedEventArgs> captureStdError, ClusterEndpoint cluster) | ||
| { | ||
| if (config == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(config)); | ||
| } | ||
|
|
||
| var spec = new JsonObject { ["interactive"] = Environment.UserInteractive }; | ||
| if (config.ProvideClusterInfo) | ||
| { | ||
| var clusterNode = ToExecClusterInfo(cluster); | ||
| if (clusterNode != null) | ||
| { | ||
| spec["cluster"] = clusterNode; | ||
| } | ||
| } | ||
|
mutsaddi-deshaw marked this conversation as resolved.
|
||
|
|
||
| var execInfo = new JsonObject | ||
| { | ||
| ["apiVersion"] = config.ApiVersion, | ||
| ["kind"] = "ExecCredentials", | ||
| ["spec"] = spec, | ||
| }; | ||
|
|
||
| var process = new Process(); | ||
|
|
||
| process.StartInfo.EnvironmentVariables.Add("KUBERNETES_EXEC_INFO", $"{{ \"apiVersion\":\"{config.ApiVersion}\",\"kind\":\"ExecCredentials\",\"spec\":{{ \"interactive\":{Environment.UserInteractive.ToString().ToLower()} }} }}"); | ||
| process.StartInfo.EnvironmentVariables.Add("KUBERNETES_EXEC_INFO", execInfo.ToJsonString()); | ||
| if (config.EnvironmentVariables != null) | ||
| { | ||
| foreach (var configEnvironmentVariable in config.EnvironmentVariables) | ||
|
|
@@ -493,14 +575,19 @@ | |
| /// The token, client certificate data, and the client key data received from the external command execution | ||
| /// </returns> | ||
| public static ExecCredentialResponse ExecuteExternalCommand(ExternalExecution config) | ||
| { | ||
| return ExecuteExternalCommand(config, null); | ||
| } | ||
|
|
||
| public static ExecCredentialResponse ExecuteExternalCommand(ExternalExecution config, ClusterEndpoint cluster) | ||
| { | ||
| if (config == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(config)); | ||
| } | ||
|
|
||
| var captureStdError = ExecStdError; | ||
| var process = CreateRunnableExternalProcess(config, captureStdError); | ||
| var process = CreateRunnableExternalProcess(config, captureStdError, cluster); | ||
|
|
||
| try | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may i know reason why this is introduced?