2018-09-27 10:50:39 -07:00
|
|
|
using System;
|
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Http;
|
|
|
|
|
using System.Net.Security;
|
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
using k8s.Exceptions;
|
|
|
|
|
using k8s.Models;
|
|
|
|
|
using Microsoft.Rest;
|
|
|
|
|
|
|
|
|
|
namespace k8s
|
|
|
|
|
{
|
|
|
|
|
public partial class Kubernetes
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name='config'>
|
2019-09-23 21:33:25 -07:00
|
|
|
/// The kube config to use.
|
2018-09-27 10:50:39 -07:00
|
|
|
/// </param>
|
2019-09-23 21:33:25 -07:00
|
|
|
/// <param name="httpClient">
|
|
|
|
|
/// The <see cref="HttpClient" /> to use for all requests.
|
2018-09-27 10:50:39 -07:00
|
|
|
/// </param>
|
2019-09-23 21:33:25 -07:00
|
|
|
public Kubernetes(KubernetesClientConfiguration config, HttpClient httpClient) : this(config, httpClient, false)
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
2019-09-23 21:33:25 -07:00
|
|
|
}
|
2018-09-27 10:50:39 -07:00
|
|
|
|
2019-09-23 21:33:25 -07:00
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name='config'>
|
|
|
|
|
/// The kube config to use.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <param name="httpClient">
|
|
|
|
|
/// The <see cref="HttpClient" /> to use for all requests.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <param name="disposeHttpClient">
|
|
|
|
|
/// Whether or not the <see cref="Kubernetes"/> object should own the lifetime of <paramref name="httpClient"/>.
|
|
|
|
|
/// </param>
|
2020-04-23 11:40:06 -07:00
|
|
|
public Kubernetes(KubernetesClientConfiguration config, HttpClient httpClient, bool disposeHttpClient) : this(
|
|
|
|
|
httpClient, disposeHttpClient)
|
2019-09-23 21:33:25 -07:00
|
|
|
{
|
|
|
|
|
ValidateConfig(config);
|
2019-03-11 06:39:28 -07:00
|
|
|
CaCerts = config.SslCaCerts;
|
2018-09-27 10:50:39 -07:00
|
|
|
SkipTlsVerify = config.SkipTlsVerify;
|
2020-04-22 12:15:45 -07:00
|
|
|
SetCredentials(config);
|
2018-09-27 10:50:39 -07:00
|
|
|
}
|
2019-09-23 21:33:25 -07:00
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name='config'>
|
2019-09-23 21:33:25 -07:00
|
|
|
/// The kube config to use.
|
2018-09-27 10:50:39 -07:00
|
|
|
/// </param>
|
|
|
|
|
/// <param name="handlers">
|
|
|
|
|
/// Optional. The delegating handlers to add to the http client pipeline.
|
|
|
|
|
/// </param>
|
2019-09-23 21:33:25 -07:00
|
|
|
public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler[] handlers)
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
2020-04-16 10:54:47 -07:00
|
|
|
Initialize();
|
2019-09-23 21:33:25 -07:00
|
|
|
ValidateConfig(config);
|
2020-04-16 10:54:47 -07:00
|
|
|
CreateHttpClient(handlers);
|
2019-09-23 21:33:25 -07:00
|
|
|
CaCerts = config.SslCaCerts;
|
|
|
|
|
SkipTlsVerify = config.SkipTlsVerify;
|
|
|
|
|
InitializeFromConfig(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ValidateConfig(KubernetesClientConfiguration config)
|
|
|
|
|
{
|
|
|
|
|
if (config == null)
|
|
|
|
|
{
|
|
|
|
|
throw new KubeConfigException("KubeConfig must be provided");
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
if (string.IsNullOrWhiteSpace(config.Host))
|
|
|
|
|
{
|
|
|
|
|
throw new KubeConfigException("Host url must be set");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
BaseUri = new Uri(config.Host);
|
|
|
|
|
}
|
|
|
|
|
catch (UriFormatException e)
|
|
|
|
|
{
|
|
|
|
|
throw new KubeConfigException("Bad host url", e);
|
|
|
|
|
}
|
2019-09-23 21:33:25 -07:00
|
|
|
}
|
2018-09-27 10:50:39 -07:00
|
|
|
|
2019-09-23 21:33:25 -07:00
|
|
|
private void InitializeFromConfig(KubernetesClientConfiguration config)
|
|
|
|
|
{
|
2018-09-27 10:50:39 -07:00
|
|
|
if (BaseUri.Scheme == "https")
|
|
|
|
|
{
|
|
|
|
|
if (config.SkipTlsVerify)
|
|
|
|
|
{
|
|
|
|
|
#if NET452
|
2020-04-23 11:40:06 -07:00
|
|
|
((WebRequestHandler)HttpClientHandler).ServerCertificateValidationCallback =
|
2018-09-27 10:50:39 -07:00
|
|
|
(sender, certificate, chain, sslPolicyErrors) => true;
|
2019-09-23 21:33:25 -07:00
|
|
|
#elif XAMARINIOS1_0 || MONOANDROID8_1
|
2020-04-23 11:40:06 -07:00
|
|
|
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
|
|
|
|
|
(sender, certificate, chain, sslPolicyErrors) =>
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
#else
|
|
|
|
|
HttpClientHandler.ServerCertificateCustomValidationCallback =
|
|
|
|
|
(sender, certificate, chain, sslPolicyErrors) => true;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-03-11 06:39:28 -07:00
|
|
|
if (CaCerts == null)
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
2019-09-23 21:33:25 -07:00
|
|
|
throw new KubeConfigException("A CA must be set when SkipTlsVerify === false");
|
2018-09-27 10:50:39 -07:00
|
|
|
}
|
|
|
|
|
#if NET452
|
2020-04-23 11:40:06 -07:00
|
|
|
((WebRequestHandler)HttpClientHandler).ServerCertificateValidationCallback =
|
|
|
|
|
(sender, certificate, chain, sslPolicyErrors) =>
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
2019-03-11 06:39:28 -07:00
|
|
|
return Kubernetes.CertificateValidationCallBack(sender, CaCerts, certificate, chain, sslPolicyErrors);
|
2018-09-27 10:50:39 -07:00
|
|
|
};
|
|
|
|
|
#elif XAMARINIOS1_0
|
2020-04-23 11:40:06 -07:00
|
|
|
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
|
|
|
|
|
(sender, certificate, chain, sslPolicyErrors) =>
|
2018-09-27 10:50:39 -07:00
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
var cert
|
|
|
|
|
= new X509Certificate2(certificate);
|
2019-03-11 06:39:28 -07:00
|
|
|
return Kubernetes.CertificateValidationCallBack(sender, CaCerts, cert, chain, sslPolicyErrors);
|
2018-09-27 10:50:39 -07:00
|
|
|
};
|
2019-09-23 21:33:25 -07:00
|
|
|
#elif MONOANDROID8_1
|
|
|
|
|
var certList = new System.Collections.Generic.List<Java.Security.Cert.Certificate>();
|
|
|
|
|
|
|
|
|
|
foreach (X509Certificate2 caCert in CaCerts)
|
|
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
using (var certStream
|
|
|
|
|
= new System.IO.MemoryStream(caCert.RawData))
|
2019-09-23 21:33:25 -07:00
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
Java.Security.Cert.Certificate cert
|
|
|
|
|
= Java.Security.Cert.CertificateFactory.GetInstance("X509").GenerateCertificate(certStream);
|
2019-09-23 21:33:25 -07:00
|
|
|
|
|
|
|
|
certList.Add(cert);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-23 11:40:06 -07:00
|
|
|
var handler
|
|
|
|
|
= (Xamarin.Android.Net.AndroidClientHandler)this.HttpClientHandler;
|
2019-09-23 21:33:25 -07:00
|
|
|
|
2020-04-23 11:40:06 -07:00
|
|
|
handler.TrustedCerts
|
|
|
|
|
= certList;
|
2018-09-27 10:50:39 -07:00
|
|
|
#else
|
2020-04-23 11:40:06 -07:00
|
|
|
HttpClientHandler.ServerCertificateCustomValidationCallback =
|
|
|
|
|
(sender, certificate, chain, sslPolicyErrors) =>
|
|
|
|
|
{
|
|
|
|
|
return Kubernetes.CertificateValidationCallBack(sender, CaCerts, certificate, chain,
|
|
|
|
|
sslPolicyErrors);
|
|
|
|
|
};
|
2018-09-27 10:50:39 -07:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-06 23:28:17 -06:00
|
|
|
// set credentails for the kubernetes client
|
|
|
|
|
SetCredentials(config);
|
|
|
|
|
config.AddCertificates(HttpClientHandler);
|
2018-09-27 10:50:39 -07:00
|
|
|
}
|
|
|
|
|
|
2019-03-11 06:39:28 -07:00
|
|
|
private X509Certificate2Collection CaCerts { get; }
|
2018-09-27 10:50:39 -07:00
|
|
|
|
|
|
|
|
private bool SkipTlsVerify { get; }
|
|
|
|
|
|
|
|
|
|
partial void CustomInitialize()
|
|
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
#if NET452
|
2018-09-27 10:50:39 -07:00
|
|
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
|
|
|
|
#endif
|
|
|
|
|
DeserializationSettings.Converters.Add(new V1Status.V1StatusObjectViewConverter());
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 10:54:47 -07:00
|
|
|
/// <summary>A <see cref="DelegatingHandler"/> that simply forwards a request with no further processing.</summary>
|
|
|
|
|
private sealed class ForwardingHandler : DelegatingHandler
|
|
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
public ForwardingHandler(HttpMessageHandler handler) : base(handler)
|
|
|
|
|
{
|
|
|
|
|
}
|
2020-04-16 10:54:47 -07:00
|
|
|
}
|
|
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
private void AppendDelegatingHandler<T>() where T : DelegatingHandler, new()
|
|
|
|
|
{
|
|
|
|
|
var cur = FirstMessageHandler as DelegatingHandler;
|
|
|
|
|
|
|
|
|
|
while (cur != null)
|
|
|
|
|
{
|
|
|
|
|
var next = cur.InnerHandler as DelegatingHandler;
|
|
|
|
|
|
|
|
|
|
if (next == null)
|
|
|
|
|
{
|
|
|
|
|
// last one
|
|
|
|
|
// append watcher handler between to last handler
|
2020-04-23 11:40:06 -07:00
|
|
|
cur.InnerHandler = new T { InnerHandler = cur.InnerHandler };
|
2018-09-27 10:50:39 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur = next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 10:54:47 -07:00
|
|
|
// NOTE: this method replicates the logic that the base ServiceClient uses except that it doesn't insert the RetryDelegatingHandler
|
|
|
|
|
// and it does insert the WatcherDelegatingHandler. we don't want the RetryDelegatingHandler because it has a very broad definition
|
|
|
|
|
// of what requests have failed. it considers everything outside 2xx to be failed, including 1xx (e.g. 101 Switching Protocols) and
|
|
|
|
|
// 3xx. in particular, this prevents upgraded connections and certain generic/custom requests from working.
|
|
|
|
|
private void CreateHttpClient(DelegatingHandler[] handlers)
|
|
|
|
|
{
|
|
|
|
|
FirstMessageHandler = HttpClientHandler = CreateRootHandler();
|
|
|
|
|
if (handlers == null || handlers.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
// ensure we have at least one DelegatingHandler so AppendDelegatingHandler will work
|
|
|
|
|
FirstMessageHandler = new ForwardingHandler(HttpClientHandler);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (int i = handlers.Length - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
DelegatingHandler handler = handlers[i];
|
2020-04-23 11:40:06 -07:00
|
|
|
while (handler.InnerHandler is DelegatingHandler d)
|
|
|
|
|
{
|
|
|
|
|
handler = d;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 10:54:47 -07:00
|
|
|
handler.InnerHandler = FirstMessageHandler;
|
|
|
|
|
FirstMessageHandler = handlers[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-23 11:40:06 -07:00
|
|
|
|
2020-04-16 10:54:47 -07:00
|
|
|
AppendDelegatingHandler<WatcherDelegatingHandler>();
|
|
|
|
|
HttpClient = new HttpClient(FirstMessageHandler, false);
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
/// <summary>
|
|
|
|
|
/// Set credentials for the Client
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="config">k8s client configuration</param>
|
2020-04-20 09:39:40 -07:00
|
|
|
private void SetCredentials(KubernetesClientConfiguration config) => Credentials = CreateCredentials(config);
|
2018-09-27 10:50:39 -07:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SSl Cert Validation Callback
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sender">sender</param>
|
|
|
|
|
/// <param name="certificate">client certificate</param>
|
|
|
|
|
/// <param name="chain">chain</param>
|
|
|
|
|
/// <param name="sslPolicyErrors">ssl policy errors</param>
|
|
|
|
|
/// <returns>true if valid cert</returns>
|
|
|
|
|
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "Unused by design")]
|
|
|
|
|
public static bool CertificateValidationCallBack(
|
|
|
|
|
object sender,
|
2019-03-11 06:39:28 -07:00
|
|
|
X509Certificate2Collection caCerts,
|
2018-09-27 10:50:39 -07:00
|
|
|
X509Certificate certificate,
|
|
|
|
|
X509Chain chain,
|
|
|
|
|
SslPolicyErrors sslPolicyErrors)
|
|
|
|
|
{
|
|
|
|
|
// If the certificate is a valid, signed certificate, return true.
|
|
|
|
|
if (sslPolicyErrors == SslPolicyErrors.None)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there are errors in the certificate chain, look at each error to determine the cause.
|
|
|
|
|
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
|
|
|
|
|
{
|
|
|
|
|
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
|
|
|
|
|
2019-03-11 06:39:28 -07:00
|
|
|
// Added our trusted certificates to the chain
|
|
|
|
|
//
|
|
|
|
|
chain.ChainPolicy.ExtraStore.AddRange(caCerts);
|
|
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
|
2018-09-10 04:00:45 +01:00
|
|
|
var isValid = chain.Build((X509Certificate2)certificate);
|
|
|
|
|
|
2019-03-11 06:39:28 -07:00
|
|
|
var isTrusted = false;
|
|
|
|
|
|
2018-09-27 10:50:39 -07:00
|
|
|
var rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
|
|
|
|
|
|
2019-03-11 06:39:28 -07:00
|
|
|
// Make sure that one of our trusted certs exists in the chain provided by the server.
|
|
|
|
|
//
|
|
|
|
|
foreach (var cert in caCerts)
|
|
|
|
|
{
|
|
|
|
|
if (rootCert.RawData.SequenceEqual(cert.RawData))
|
|
|
|
|
{
|
|
|
|
|
isTrusted = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return isValid && isTrusted;
|
2018-09-27 10:50:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In all other cases, return false.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-04-20 09:39:40 -07:00
|
|
|
|
|
|
|
|
/// <summary>Creates <see cref="ServiceClientCredentials"/> based on the given config, or returns null if no such credentials are
|
|
|
|
|
/// needed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static ServiceClientCredentials CreateCredentials(KubernetesClientConfiguration config)
|
|
|
|
|
{
|
2020-04-23 11:40:06 -07:00
|
|
|
if (config == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(config));
|
|
|
|
|
}
|
2020-04-28 18:34:25 -04:00
|
|
|
if (config.TokenProvider != null)
|
|
|
|
|
{
|
|
|
|
|
return new TokenCredentials(config.TokenProvider);
|
|
|
|
|
}
|
|
|
|
|
else if (!string.IsNullOrEmpty(config.AccessToken))
|
2020-04-20 09:39:40 -07:00
|
|
|
{
|
|
|
|
|
return new TokenCredentials(config.AccessToken);
|
|
|
|
|
}
|
|
|
|
|
else if (!string.IsNullOrEmpty(config.Username))
|
|
|
|
|
{
|
|
|
|
|
return new BasicAuthenticationCredentials() { UserName = config.Username, Password = config.Password };
|
|
|
|
|
}
|
2020-04-23 11:40:06 -07:00
|
|
|
|
2020-04-20 09:39:40 -07:00
|
|
|
return null;
|
|
|
|
|
}
|
2018-09-27 10:50:39 -07:00
|
|
|
}
|
|
|
|
|
}
|