/// <summary> /// Updates the running proxies to match the current cluster /// (if there is one). This may only be called on the UI thread. /// </summary> private async Task UpdateProxiesAsync() { if (InvokeRequired) { throw new InvalidOperationException($"[{nameof(UpdateProxiesAsync)}()] may only be called on the UI thread."); } if (KubeHelper.CurrentContext == null) { StopProxies(); StopPortForwards(); } else { var cluster = Program.GetCluster(); // We're going to use the current Kubenetes context name and cluster ID // to determine whether we're still connected to the same cluster. if (proxiedContext != null) { if (proxiedContext.Name == KubeHelper.CurrentContext.Name && proxiedContext.Extension.ClusterId == KubeHelper.CurrentContext.Extension.ClusterId) { // We're still proxying the same cluster so no changes // are required. return; } } StopProxies(); StopPortForwards(); if (KubeHelper.CurrentContext == null) { // Wr're not logged into a cluster so don't start any proxies. return; } try { // Start the connecting animation if we're not already in the error state. if (!InErrorState) { StartNotifyAnimation(connectingAnimation, $"{KubeHelper.CurrentContextName}: Connecting...", isTransient: true); } //------------------------------------------------------------- // The Kubernetes dashboard reverse proxy. // Setup a callback that transparently adds an [Authentication] header // to all requests with the correct bearer token. We'll need to // obtain the token secret via two steps: // // 1. Identify the dashboard token secret by listing all secrets // in the [kube-system] namespace looking for one named like // [root-user-token-*]. // // 2. Reading that secret and extracting the value. var response = (ExecuteResponse)null; var secretName = string.Empty; var dashboardToken = string.Empty; await Task.Run( async() => { response = KubeHelper.Kubectl("--namespace", "kube-system", "get", "secrets", "-o=name"); await Task.CompletedTask; }); if (response.ExitCode != 0) { try { response.EnsureSuccess(); } catch (Exception e) { Program.LogError(e); SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure"); return; } } // Step 1: Determine the secret name. using (var reader = new StringReader(response.OutputText)) { const string secretPrefix = "secret/"; secretName = reader.Lines().FirstOrDefault(line => line.StartsWith($"{secretPrefix}root-user-token-")); Covenant.Assert(!string.IsNullOrEmpty(secretName)); secretName = secretName.Substring(secretPrefix.Length); } // Step 2: Describe the secret and extract the token value. This // is a bit of a hack because I'm making assumptions about // the output format. await Task.Run( async() => { response = KubeHelper.Kubectl("--namespace", "kube-system", "describe", "secret", secretName); await Task.CompletedTask; }); if (response.ExitCode != 0) { try { response.EnsureSuccess(); } catch (Exception e) { Program.LogError(e); SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure"); return; } } using (var reader = new StringReader(response.OutputText)) { var tokenLine = reader.Lines().FirstOrDefault(line => line.StartsWith("token:")); Covenant.Assert(!string.IsNullOrEmpty(tokenLine)); dashboardToken = tokenLine.Split(new char[] { ' ' }, 2).Skip(1).First().Trim(); } Action <RequestContext> dashboardRequestHandler = context => { context.Request.Headers.Add("Authorization", $"Bearer {dashboardToken}"); }; // Start the proxy. var userContext = KubeHelper.Config.GetUser(KubeHelper.CurrentContext.Properties.User); var certPem = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientCertificateData)); var keyPem = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientKeyData)); var dashboardCert = TlsCertificate.Parse(KubeHelper.CurrentContext.Extension.KubernetesDashboardCertificate).ToX509(publicOnly: true); var kubeDashboardProxy = new ReverseProxy( localPort: KubeHelper.ClientConfig.KubeDashboardProxyPort, remotePort: KubeHostPorts.KubeDashboard, remoteHost: cluster.GetReachableMaster().PrivateAddress.ToString(), validCertificate: dashboardCert, requestHandler: dashboardRequestHandler); proxies.Add(kubeDashboardProxy); var kibanaDashboardProxy = new PortForward( serviceName: "kibana-kibana", localPort: KubeConst.KibanaDashboardProxyPort, remotePort: KubeConst.KibanaDashboardProxyPort, @namespace: "logging"); portForwards.Add(kibanaDashboardProxy); var prometheusDashboardProxy = new PortForward( serviceName: "prometheus", localPort: KubeConst.PrometheusDashboardProxyPort, remotePort: KubeConst.PrometheusDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(prometheusDashboardProxy); var kialiDashboardProxy = new PortForward( serviceName: "kiali", localPort: KubeConst.KialiDashboardProxyPort, remotePort: KubeConst.KialiDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(kialiDashboardProxy); var grafanaDashboardProxy = new PortForward( serviceName: "grafana", localPort: KubeConst.GrafanaDashboardProxyPort, remotePort: KubeConst.GrafanaDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(grafanaDashboardProxy); //------------------------------------------------------------- // Remember which cluster context we're proxying. proxiedContext = KubeHelper.CurrentContext; } catch (Exception e) { Console.WriteLine(e); } finally { // Stop any non-error transient animation on the top of the notify stack. if (notifyStack.Count > 0 && notifyStack.Peek().IsTransient&& !notifyStack.Peek().IsError) { StopNotifyAnimation(); } } } }