Skip to content

Commit 332922d

Browse files
committed
improved outbound test (cert details), removed default swagger ui, fixed readme
1 parent 7e91120 commit 332922d

5 files changed

Lines changed: 145 additions & 66 deletions

File tree

AzFappDebugger.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>net6.0</TargetFramework>
44
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="DnsClient" Version="1.6.1" />
8-
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi" Version="1.0.0" />
9-
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
8+
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
109
</ItemGroup>
1110
<ItemGroup>
1211
<None Update="host.json">

HtmlBrandingHelper.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Security.Authentication;
45
using System.Text;
56
using System.Threading.Tasks;
67

@@ -80,7 +81,47 @@ internal static string GetBootstrapWhatItMeans(string uniqueId, string text, boo
8081
$"<div class='" + (isActive ? "" : "collapse") + $"' id='whatItMeans{uniqueId}'><div class='callout callout-info'>{text}</div></div>";
8182
}
8283

84+
internal static string NormalizeLength(string value, int maxLength)
85+
{
86+
return value.Length <= maxLength ? value : value.Substring(0, maxLength) + "...";
87+
}
88+
89+
internal static string NiceTlsProtocol(SslProtocols protocol)
90+
{
91+
switch (protocol)
92+
{
93+
default:
94+
return protocol.ToString();
95+
break;
8396

97+
case SslProtocols.None:
98+
return "<<none>>";
99+
break;
100+
101+
case SslProtocols.Ssl2:
102+
return "SSL 2.0";
103+
break;
104+
case SslProtocols.Ssl3:
105+
return "SSL 3.0";
106+
break;
107+
case SslProtocols.Tls:
108+
return "TLS 1.0";
109+
break;
110+
case SslProtocols.Default:
111+
return "SSL 3.0/TLS 1.0";
112+
break;
113+
case SslProtocols.Tls11:
114+
return "TLS 1.1";
115+
break;
116+
case SslProtocols.Tls12:
117+
return "TLS 1.2";
118+
break;
119+
case SslProtocols.Tls13:
120+
return "TLS 1.3";
121+
break;
122+
123+
}
124+
}
84125

85126
}
86127
}

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ The tool is released under the MIT License, see LICENSE file.
1313

1414
## Usage
1515

16-
Just install this codebase as package to given Azure FunctionApp.
16+
Just install this codebase as package to given Azure FunctionApp and visit `<<url>>/RunTests`
1717

18-
To control tool set Configuration variables:<br>
19-
`TEST_DNS_RESOLVE_DOMAINS` = <em>`<<comma-delimited list of domains>>`</em> - performs resolutions of given domains via all available DNS servers<br>
18+
For tool configuration set following Configuration variables:<br>
19+
- `TEST_DNS_RESOLVE_DOMAINS` = <em>`<<comma-delimited list of domains>>`</em> - performs resolutions of given domains via all available DNS servers<br>
2020
<em>e.g. `TEST_DNS_RESOLVE_DOMAINS` = `azure.com,someonpremresource.contoso.internal,vjirovsky.cz` </em>
2121

22-
`TEST_HTTPCLIENT_GET_URL` = <em>`<<url to test>>`</em> - performs HTTP request to given URL via same outbound connectivity configuration as the real application will use<br>
22+
- `TEST_HTTPCLIENT_GET_URL` = <em>`<<url to test>>`</em> - performs HTTP request to given URL via same outbound connectivity configuration as the real application will use<br>
2323
<em> e.g. `TEST_DNS_RESOLVE_DOMAINS` = `https://vjirovsky.cz` </em>
2424

2525

RunTestsFunction.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ namespace AzFappDebugger
2121
{
2222
public static class RunTestsFunction
2323
{
24-
private static HttpClient _httpClient = null;
24+
private static HttpClient _defaultHttpClient = null;
25+
private static HttpClientHandler _defaultHttpClientHandler = null;
2526

2627
private static IDictionary<string, string> _environmentVariablesDictionary = new Dictionary<string, string>();
2728

28-
29-
3029
static RunTestsFunction()
3130
{
3231

@@ -36,12 +35,12 @@ static RunTestsFunction()
3635
}
3736

3837

39-
4038
// ignore SSL cert errors for this debugger (e.g. DPI, proxy, etc.)
41-
var httpClientHandler = new HttpClientHandler();
39+
_defaultHttpClientHandler = new HttpClientHandler();
40+
41+
_defaultHttpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {return true;};
4242

43-
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>{return true;};
44-
_httpClient = new HttpClient(httpClientHandler);
43+
_defaultHttpClient = new HttpClient(_defaultHttpClientHandler);
4544

4645
}
4746

@@ -58,7 +57,7 @@ public static async Task<IActionResult> Run(
5857

5958

6059
var dnsTestsProvider = new DnsTests(_environmentVariablesDictionary);
61-
var outboundConnectivityTestsProvider = new OutboundConnectivityTests(_environmentVariablesDictionary, _httpClient);
60+
var outboundConnectivityTestsProvider = new OutboundConnectivityTests(_environmentVariablesDictionary, _defaultHttpClient);
6261
var overviewTestsProvider = new OverviewTests(_environmentVariablesDictionary);
6362
var contentStorageAccessTestsProvider = new ContentStorageAccessTests(_environmentVariablesDictionary);
6463

Tests/OutboundConnectivityTests.cs

Lines changed: 91 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
using System.Linq;
66
using System.Net;
77
using System.Net.Http;
8+
using System.Net.Security;
9+
using System.Net.Sockets;
10+
using System.Security.Cryptography.X509Certificates;
811
using System.Text;
912
using System.Threading.Tasks;
1013
using System.Web;
@@ -13,15 +16,14 @@ namespace AzFappDebugger.Tests
1316
{
1417
internal class OutboundConnectivityTests
1518
{
16-
private static IDictionary<string, string> _environmentVariablesDictionary = null;
19+
private IDictionary<string, string> _environmentVariablesDictionary = null;
20+
private HttpClient _defaultHttpClient;
1721

18-
private HttpClient _httpClient = null;
19-
20-
internal OutboundConnectivityTests(IDictionary<string, string> environmentVariablesDictionary, HttpClient httpClient)
22+
internal OutboundConnectivityTests(IDictionary<string, string> environmentVariablesDictionary, HttpClient defaultHttpClient)
2123
{
2224

2325
_environmentVariablesDictionary = environmentVariablesDictionary;
24-
_httpClient = httpClient;
26+
_defaultHttpClient = defaultHttpClient;
2527
}
2628

2729

@@ -44,9 +46,10 @@ internal async Task<string> RunAllTestsAsHtmlOutputAsync()
4446
{
4547
output += HtmlBrandingHelper.GetStandardTableRow("Integrated vNET", $"<code>{configItemValue}</code>");
4648
}
47-
else {
49+
else
50+
{
4851
output += HtmlBrandingHelper.GetStandardTableRow("Integrated vNET", $"<span class=\"badge text-bg-danger\">No vNET integration</span><br>" +
49-
$"The application is not integrated with any vNET.");
52+
$"The application is not integrated with any vNET.");
5053
}
5154

5255

@@ -62,71 +65,108 @@ internal async Task<string> RunAllTestsAsHtmlOutputAsync()
6265

6366
output += $"<h3>Connectivity tests</h3>";
6467

65-
if (_httpClient != null)
66-
{
68+
string httpClientUrlToResolve = string.Empty;
6769

68-
string httpClientUrlToResolve = string.Empty;
69-
70-
output += $"<h4>HttpClient test</h4> <small>";
71-
output += HtmlBrandingHelper.GetBootstrapWhatItMeans("OutboundConnectivityHttpClient",
72-
$"<p>This test performs HTTP request via outbound connectivity of the application to hostname specified in <code>{Constants.TEST_HTTPCLIENT_RESOLVE_URL_VARIABLE}</code> configuration variable.</p>", false, false, "Test description");
73-
output += "</small>";
70+
output += $"<h4>HttpClient test</h4> <small>";
71+
output += HtmlBrandingHelper.GetBootstrapWhatItMeans("OutboundConnectivityHttpClient",
72+
$"<p>This test performs HTTP request via outbound connectivity of the application to hostname specified in <code>{Constants.TEST_HTTPCLIENT_RESOLVE_URL_VARIABLE}</code> configuration variable.</p>", false, false, "Test description");
73+
output += "</small>";
7474

7575

76-
_environmentVariablesDictionary.TryGetValue(Constants.TEST_HTTPCLIENT_RESOLVE_URL_VARIABLE, out httpClientUrlToResolve);
76+
_environmentVariablesDictionary.TryGetValue(Constants.TEST_HTTPCLIENT_RESOLVE_URL_VARIABLE, out httpClientUrlToResolve);
7777

78-
if (!string.IsNullOrWhiteSpace(httpClientUrlToResolve))
78+
if (!string.IsNullOrWhiteSpace(httpClientUrlToResolve) && _defaultHttpClient != null)
79+
{
80+
output += "<table class=\"table\">";
81+
string testResult = "";
82+
try
7983
{
80-
output += "<table class=\"table\">";
81-
string testResult = "";
82-
try
83-
{
84-
var content = await _httpClient.GetStringAsync(httpClientUrlToResolve);
85-
testResult = $"<span class=\"badge text-bg-success\">OK</span><br><code>{HttpUtility.HtmlEncode(content)}</code>";
86-
}
87-
catch (Exception e)
88-
{
89-
testResult = "<span class=\"badge text-bg-danger\">QUERY FAILED</span><br> " + e.Message +"<br>" + (e.InnerException != null ? e.InnerException.Message : "");
90-
}
91-
finally
92-
{
93-
output += HtmlBrandingHelper.GetStandardTableRow($"{httpClientUrlToResolve}", testResult);
94-
}
84+
var httpClientUrlToResolveUri = new Uri(httpClientUrlToResolve);
85+
var content = await _defaultHttpClient.GetAsync(httpClientUrlToResolveUri);
86+
content.EnsureSuccessStatusCode();
87+
string text = await content.Content.ReadAsStringAsync();
9588

96-
output += "</table>";
97-
}
9889

90+
testResult = $"<span class=\"badge text-bg-success\">OK</span>&nbsp; {(int)content.StatusCode} {content.StatusCode}<br><br>";
9991

100-
if (!string.IsNullOrWhiteSpace(Constants.MY_IP_ADDRESS_EXTERNAL_SERVICE_URL))
101-
{
102-
output += $"<h4>My outbound IP address</h4> <small>";
103-
output += HtmlBrandingHelper.GetBootstrapWhatItMeans("OutboundConnectivityExternalIp",
104-
$"<p>This test performs a HTTP request to external service, which returns a IP address of HTTP request.</p>", false, false, "Test description");
105-
output += "</small>";
10692

107-
output += "<table class=\"table\">";
108-
string testResult = "";
93+
testResult += $"<strong>Connection details</strong><br>" +
94+
$"Protocol: <code>HTTP {content.Version}</code><br>";
10995
try
11096
{
111-
var content = await _httpClient.GetStringAsync(Constants.MY_IP_ADDRESS_EXTERNAL_SERVICE_URL);
112-
testResult = $"<code>{HttpUtility.HtmlEncode(content)}</code>";
113-
}
114-
catch (Exception e)
115-
{
116-
testResult = "<span class=\"badge text-bg-danger\">QUERY FAILED</span><br> " + e.InnerException.Message;
97+
RemoteCertificateValidationCallback certCallback = (_, _, _, _) => true;
98+
using var client = new TcpClient(httpClientUrlToResolveUri.Host, httpClientUrlToResolveUri.Port);
99+
using var sslStream = new SslStream(client.GetStream(), true, certCallback);
100+
await sslStream.AuthenticateAsClientAsync(httpClientUrlToResolveUri.Host);
101+
var serverCertificate = sslStream.RemoteCertificate;
102+
var certificate = new X509Certificate2(serverCertificate);
103+
104+
if (certificate != null)
105+
{
106+
testResult += $"Security protocol: <code>{HtmlBrandingHelper.NiceTlsProtocol(sslStream.SslProtocol)}</code><br>" +
107+
$"Negotiated cipher: <code>{sslStream.NegotiatedCipherSuite}</code><br><br>";
108+
109+
testResult += $"<strong>Certificate</strong><br>" +
110+
$"Subject: <code>{certificate.Subject}</code><br>" +
111+
$"Issuer: <code>{certificate.Issuer}</code><br>" +
112+
$"Valid from: <code>{certificate.GetEffectiveDateString()}</code><br>" +
113+
$"Expires on: <code>{certificate.GetExpirationDateString()}</code><br>" +
114+
$"Thumbprint: <code>{certificate.Thumbprint}</code><br><br>"
115+
;
116+
}
117117
}
118-
finally
118+
catch (Exception ee)
119119
{
120-
output += HtmlBrandingHelper.GetStandardTableRow($"My IP address", testResult);
120+
testResult += $"<i>Connection is not secured</i><br><br>";
121+
121122
}
122123

123-
output += "</table>";
124+
testResult += $"<strong>Body:</strong><br>" +
125+
$"<code>{HttpUtility.HtmlEncode(HtmlBrandingHelper.NormalizeLength(text, 1000))}</code>";
124126
}
127+
catch (Exception e)
128+
{
129+
testResult = "<span class=\"badge text-bg-danger\">QUERY FAILED</span><br> " + e.Message + "<br>" + (e.InnerException != null ? e.InnerException.Message : "");
130+
}
131+
finally
132+
{
133+
output += HtmlBrandingHelper.GetStandardTableRow($"{httpClientUrlToResolve}", testResult);
134+
}
135+
125136

137+
output += "</table>";
138+
}
139+
else
140+
{
141+
output += $"<div class='callout callout-warning'>No valid <code>{Constants.TEST_HTTPCLIENT_RESOLVE_URL_VARIABLE}</code> variable defined, test has been skipped.</div>";
126142
}
127143

128144

145+
if (!string.IsNullOrWhiteSpace(Constants.MY_IP_ADDRESS_EXTERNAL_SERVICE_URL) && _defaultHttpClient != null)
146+
{
147+
output += $"<h4>My outbound IP address</h4> <small>";
148+
output += HtmlBrandingHelper.GetBootstrapWhatItMeans("OutboundConnectivityExternalIp",
149+
$"<p>This test performs a HTTP request to external service, which returns a IP address of HTTP request.</p>", false, false, "Test description");
150+
output += "</small>";
129151

152+
output += "<table class=\"table\">";
153+
string testResult = "";
154+
try
155+
{
156+
var content = await _defaultHttpClient.GetStringAsync(Constants.MY_IP_ADDRESS_EXTERNAL_SERVICE_URL);
157+
testResult = $"<code>{HttpUtility.HtmlEncode(content)}</code>";
158+
}
159+
catch (Exception e)
160+
{
161+
testResult = "<span class=\"badge text-bg-danger\">QUERY FAILED</span><br> " + e.InnerException.Message;
162+
}
163+
finally
164+
{
165+
output += HtmlBrandingHelper.GetStandardTableRow($"My IP address", testResult);
166+
}
167+
168+
output += "</table>";
169+
}
130170

131171
return output;
132172
}

0 commit comments

Comments
 (0)