From 9a5f9f9332d75db3774d9fe42b907284255c402c Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Wed, 11 Aug 2021 12:39:46 +0000
Subject: [PATCH 01/23] Update dependencies from
https://github.com/dotnet/diagnostics build 20210810.1 (#707)
[main] Update dependencies from dotnet/diagnostics
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 96eadc3ccac..ad6fff7404a 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -4,13 +4,13 @@
https://github.com/dotnet/command-line-api166610c56ff732093f0145a2911d4f6c40b786da
-
+ https://github.com/dotnet/diagnostics
- 7cb03dbd4b93564f1d45c9f04ac8bf8961e01cc7
+ 5e4f7a31054550ed4dc66f2676035489706b090c
-
+ https://github.com/dotnet/diagnostics
- 7cb03dbd4b93564f1d45c9f04ac8bf8961e01cc7
+ 5e4f7a31054550ed4dc66f2676035489706b090c
diff --git a/eng/Versions.props b/eng/Versions.props
index ee009bcc23a..aa4b220a204 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,8 +31,8 @@
6.0.0-rc.1.21410.1
- 5.0.0-preview.21409.1
- 5.0.0-preview.21409.1
+ 5.0.0-preview.21410.1
+ 5.0.0-preview.21410.16.0.0-rc.1.21406.5
From 1ac28937baab41cbee97b42a21867d7219725200 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Wed, 11 Aug 2021 12:48:03 +0000
Subject: [PATCH 02/23] Update dependencies from
https://github.com/dotnet/runtime build 20210811.2 (#708)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index ad6fff7404a..a26177cecd3 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcore3535cfb61dd955a7fa6c31bdf72a3eba5353f1ce
-
+ https://github.com/dotnet/runtime
- 58efa4b79751a2dad08d9bf7ca67930f8160afe3
+ c0662e8129beaf93b8050d39a863cc6d16a0308c
diff --git a/eng/Versions.props b/eng/Versions.props
index aa4b220a204..9f6fad3e777 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21410.15.0.0-preview.21410.1
- 6.0.0-rc.1.21406.5
+ 6.0.0-rc.1.21411.21.0.240901
From cf94ef78c80aca5f19933add98150e14d5fd6d50 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Wed, 11 Aug 2021 12:48:11 +0000
Subject: [PATCH 03/23] Update dependencies from
https://github.com/dotnet/aspnetcore build 20210810.16 (#709)
[main] Update dependencies from dotnet/aspnetcore
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index a26177cecd3..e7b32b53341 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -26,9 +26,9 @@
https://github.com/dotnet/symstored8e2990b89c53632653d7d67f3481cc72773f25c
-
+ https://github.com/dotnet/aspnetcore
- 3535cfb61dd955a7fa6c31bdf72a3eba5353f1ce
+ a671f9652808921d6bbe74994c16065372bec6f6https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index 9f6fad3e777..78c159a8296 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -29,7 +29,7 @@
6.0.0-beta.21406.6
- 6.0.0-rc.1.21410.1
+ 6.0.0-rc.1.21410.165.0.0-preview.21410.15.0.0-preview.21410.1
From be7ded106c8c9e44c5a252e06911d0b2bfa73fe3 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Thu, 12 Aug 2021 12:43:05 +0000
Subject: [PATCH 04/23] Update dependencies from
https://github.com/dotnet/diagnostics build 20210811.1 (#711)
[main] Update dependencies from dotnet/diagnostics
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index e7b32b53341..c64ea96f28a 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -4,13 +4,13 @@
https://github.com/dotnet/command-line-api166610c56ff732093f0145a2911d4f6c40b786da
-
+ https://github.com/dotnet/diagnostics
- 5e4f7a31054550ed4dc66f2676035489706b090c
+ 626b7f8f23053672f989a8174336897fe1b57434
-
+ https://github.com/dotnet/diagnostics
- 5e4f7a31054550ed4dc66f2676035489706b090c
+ 626b7f8f23053672f989a8174336897fe1b57434
diff --git a/eng/Versions.props b/eng/Versions.props
index 78c159a8296..9e6c956fd96 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,8 +31,8 @@
6.0.0-rc.1.21410.16
- 5.0.0-preview.21410.1
- 5.0.0-preview.21410.1
+ 5.0.0-preview.21411.1
+ 5.0.0-preview.21411.16.0.0-rc.1.21411.2
From 24b4b13852df670d0c7dd9574fd5645ccaebac22 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Thu, 12 Aug 2021 12:55:07 +0000
Subject: [PATCH 05/23] Update dependencies from
https://github.com/dotnet/runtime build 20210811.5 (#713)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c64ea96f28a..73790218b23 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcorea671f9652808921d6bbe74994c16065372bec6f6
-
+ https://github.com/dotnet/runtime
- c0662e8129beaf93b8050d39a863cc6d16a0308c
+ 0ea8653e1f0ada5c7a15515430c6f16585911af4
diff --git a/eng/Versions.props b/eng/Versions.props
index 9e6c956fd96..ced245669ab 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21411.15.0.0-preview.21411.1
- 6.0.0-rc.1.21411.2
+ 6.0.0-rc.1.21411.51.0.240901
From 5bed1b40d232618bd49b64477c1a73a4edfeaede Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Thu, 12 Aug 2021 12:55:14 +0000
Subject: [PATCH 06/23] Update dependencies from
https://github.com/dotnet/aspnetcore build 20210811.15 (#714)
[main] Update dependencies from dotnet/aspnetcore
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 73790218b23..f8df3060b3a 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -26,9 +26,9 @@
https://github.com/dotnet/symstored8e2990b89c53632653d7d67f3481cc72773f25c
-
+ https://github.com/dotnet/aspnetcore
- a671f9652808921d6bbe74994c16065372bec6f6
+ 9c2b65a8f9ac334db5575160b2e07a35c25d0585https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index ced245669ab..ea95f09e8fd 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -29,7 +29,7 @@
6.0.0-beta.21406.6
- 6.0.0-rc.1.21410.16
+ 6.0.0-rc.1.21411.155.0.0-preview.21411.15.0.0-preview.21411.1
From d42c23473d0f73675b7cc28c7ff67fc7221e252f Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Thu, 12 Aug 2021 17:41:56 +0000
Subject: [PATCH 07/23] Update dependencies from
https://github.com/dotnet/arcade build 20210810.8 (#712)
[main] Update dependencies from dotnet/arcade
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 2 +-
global.json | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index f8df3060b3a..3e476aeda9a 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -14,13 +14,13 @@
-
+ https://github.com/dotnet/arcade
- 382667fff0b58c362855a42c3529ba294fd0514c
+ e10772e3594e46a031574c20a4145441737ac56d
-
+ https://github.com/dotnet/arcade
- 382667fff0b58c362855a42c3529ba294fd0514c
+ e10772e3594e46a031574c20a4145441737ac56dhttps://github.com/dotnet/symstore
diff --git a/eng/Versions.props b/eng/Versions.props
index ea95f09e8fd..f454e89d669 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -27,7 +27,7 @@
-->
- 6.0.0-beta.21406.6
+ 6.0.0-beta.21410.86.0.0-rc.1.21411.15
diff --git a/global.json b/global.json
index 38355eab28a..1d09eebba7e 100644
--- a/global.json
+++ b/global.json
@@ -16,6 +16,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "2.0.1",
- "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21406.6"
+ "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21410.8"
}
}
From b914277dccdeb6d20b32e3060ddcc0741edb3569 Mon Sep 17 00:00:00 2001
From: Patrick Fenelon
Date: Thu, 12 Aug 2021 23:04:22 -0700
Subject: [PATCH 08/23] Switch to Bearer auth (#463)
* Replace MonitorApiKey key/keyhash auth type with signed JWT implementation
* Add MonitorGenerateKeyRunner to test the generatekey command
Closes #463
Closes #589
---
documentation/api-key-format.md | 73 +++
documentation/authentication.md | 85 ++--
documentation/schema.json | 45 +-
eng/Versions.props | 3 +
.../AuthenticationOptions.cs | 18 +
...uration.cs => CorsConfigurationOptions.cs} | 2 +-
...tionOptions.cs => MonitorApiKeyOptions.cs} | 13 +-
.../OptionsDisplayStrings.Designer.cs | 33 +-
.../OptionsDisplayStrings.resx | 20 +-
.../Auth/AuthConstants.cs | 10 +-
...tics.Monitoring.ConfigurationSchema.csproj | 2 +-
.../Options/RootOptions.Logging.cs | 2 +-
.../Runners/DotNetRunner.cs | 12 +-
.../Runners/LoggingRunnerAdapter.cs | 58 ++-
.../TaskExtensions.cs | 23 +
.../AuthenticationTests.cs | 418 +++++++++++++++---
.../GenerateKeyTests.cs | 98 ++++
.../JsonSerializerOptionsFactory.cs | 52 +++
.../MetricsTests.cs | 8 +-
...ics.Monitoring.Tool.FunctionalTests.csproj | 10 +
.../Options/OptionsExtensions.cs | 243 +++++-----
.../ProcessTests.cs | 2 +-
.../Runners/MonitorCollectRunner.cs | 262 +++++++++++
.../Runners/MonitorGenerateKeyRunner.cs | 233 ++++++++++
.../Runners/MonitorRunner.cs | 225 ++--------
.../Runners/MonitorRunnerExtensions.cs | 21 +-
.../Runners/ScenarioRunner.cs | 4 +-
.../Auth/ApiKeyAuthenticationHandler.cs | 119 -----
...piKeyAuthenticationPostConfigureOptions.cs | 117 -----
.../{AuthOptions.cs => AuthConfiguration.cs} | 8 +-
.../Auth/AuthorizedUserRequirement.cs | 4 +-
.../dotnet-monitor/Auth/GeneratedApiKey.cs | 65 ---
.../dotnet-monitor/Auth/GeneratedJwtKey.cs | 59 +++
.../Auth/HashAlgorithmChecker.cs | 40 --
...{IAuthOptions.cs => IAuthConfiguration.cs} | 4 +-
.../Auth/JwtAlgorithmChecker.cs | 63 +++
.../Auth/JwtBearerChangeTokenSource.cs | 54 +++
.../Auth/JwtBearerPostConfigure.cs | 61 +++
.../Auth/LiveJwtBearerHandler.cs | 30 ++
...e.cs => MonitorApiKeyChangeTokenSource.cs} | 14 +-
...tions.cs => MonitorApiKeyConfiguration.cs} | 17 +-
... => MonitorApiKeyConfigurationObserver.cs} | 28 +-
.../Auth/MonitorApiKeyPostConfigure.cs | 113 +++++
.../Auth/RejectAllSecurityValidator.cs | 33 ++
.../Auth/UserAuthorizationHandler.cs | 17 +-
.../dotnet-monitor/CommonOptionsExtensions.cs | 128 ++++++
.../dotnet-monitor/ConfigurationJsonWriter.cs | 21 +-
src/Tools/dotnet-monitor/ConfigurationKeys.cs | 14 +-
.../DiagnosticsMonitorCommandHandler.cs | 29 +-
.../GenerateApiKeyCommandHandler.cs | 94 ++--
src/Tools/dotnet-monitor/LoggingExtensions.cs | 37 +-
src/Tools/dotnet-monitor/OutputFormat.cs | 24 +
src/Tools/dotnet-monitor/Program.cs | 21 +-
.../dotnet-monitor}/RootOptions.cs | 4 +-
.../ServiceCollectionExtensions.cs | 35 +-
src/Tools/dotnet-monitor/Startup.cs | 12 +-
src/Tools/dotnet-monitor/Strings.Designer.cs | 150 +++++--
src/Tools/dotnet-monitor/Strings.resx | 88 +++-
.../dotnet-monitor/dotnet-monitor.csproj | 2 +
59 files changed, 2460 insertions(+), 1020 deletions(-)
create mode 100644 documentation/api-key-format.md
create mode 100644 src/Microsoft.Diagnostics.Monitoring.Options/AuthenticationOptions.cs
rename src/Microsoft.Diagnostics.Monitoring.Options/{CorsConfiguration.cs => CorsConfigurationOptions.cs} (93%)
rename src/Microsoft.Diagnostics.Monitoring.Options/{ApiAuthenticationOptions.cs => MonitorApiKeyOptions.cs} (66%)
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/GenerateKeyTests.cs
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/JsonSerializerOptionsFactory.cs
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorCollectRunner.cs
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorGenerateKeyRunner.cs
delete mode 100644 src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs
delete mode 100644 src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationPostConfigureOptions.cs
rename src/Tools/dotnet-monitor/Auth/{AuthOptions.cs => AuthConfiguration.cs} (80%)
delete mode 100644 src/Tools/dotnet-monitor/Auth/GeneratedApiKey.cs
create mode 100644 src/Tools/dotnet-monitor/Auth/GeneratedJwtKey.cs
delete mode 100644 src/Tools/dotnet-monitor/Auth/HashAlgorithmChecker.cs
rename src/Tools/dotnet-monitor/Auth/{IAuthOptions.cs => IAuthConfiguration.cs} (84%)
create mode 100644 src/Tools/dotnet-monitor/Auth/JwtAlgorithmChecker.cs
create mode 100644 src/Tools/dotnet-monitor/Auth/JwtBearerChangeTokenSource.cs
create mode 100644 src/Tools/dotnet-monitor/Auth/JwtBearerPostConfigure.cs
create mode 100644 src/Tools/dotnet-monitor/Auth/LiveJwtBearerHandler.cs
rename src/Tools/dotnet-monitor/Auth/{ApiKeyAuthenticationOptionsChangeTokenSource.cs => MonitorApiKeyChangeTokenSource.cs} (68%)
rename src/Tools/dotnet-monitor/Auth/{ApiKeyAuthenticationOptions.cs => MonitorApiKeyConfiguration.cs} (51%)
rename src/Tools/dotnet-monitor/Auth/{ApiKeyAuthenticationOptionsObserver.cs => MonitorApiKeyConfigurationObserver.cs} (58%)
create mode 100644 src/Tools/dotnet-monitor/Auth/MonitorApiKeyPostConfigure.cs
create mode 100644 src/Tools/dotnet-monitor/Auth/RejectAllSecurityValidator.cs
create mode 100644 src/Tools/dotnet-monitor/CommonOptionsExtensions.cs
create mode 100644 src/Tools/dotnet-monitor/OutputFormat.cs
rename src/{Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options => Tools/dotnet-monitor}/RootOptions.cs (83%)
diff --git a/documentation/api-key-format.md b/documentation/api-key-format.md
new file mode 100644
index 00000000000..7b5db262bb9
--- /dev/null
+++ b/documentation/api-key-format.md
@@ -0,0 +1,73 @@
+# API Key Format
+API Keys or MonitorApiKeys used in `dotnet monitor` are JSON Web Tokens or JWTs as defined by [RFC 7519: JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519).
+> **Note:** Because the API Key is a `Bearer` token, it should be treated as a secret and always transmitted over `TLS` or anther protected protocol.
+
+It is possible to make your own API Keys for `dotnet monitor` by following the format as defined below. Although, it is recommended to use the `generatekey` command unless you have a specific reason to make your own key.
+
+## Token Format
+To use a JWT for authentication with `dotnet monitor`, the token must follow certain constraints. These constraints will be validated by `dotnet monitor` at configuration load time, authentication time, and authorization time.
+
+For this example, let's consider the API Key given on the [Authentication page](authentication.md). This token consists of 3 parts: Header, Payload, and Signature; explained in detail later. This is the entire portion passed as a `Bearer` type in the `Authorization` HTTP header:
+```yaml
+eyJhbGciOiJFUffffffffffffCI6IkpXVCJ9.eyJhdWQiOiJodffffffffffffGh1Yi5jb20vZG90bmV0L2RvdG5ldC1tb25pdG9yIiwiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2RvdG5ldC9kb3RuZXQtbW9uaXRvci9nZW5lcmF0ZWtleStNb25pdG9yQXBpS2V5Iiwic3ViIjoiYWU1NDczYjYtOGRhZC00OThkLWI5MTUtNTNiOWM2ODQwMDBlIn0.RZffffffffffff_yIyApvFKcxFpDJ65HJZek1_dt7jCTCMEEEffffffffffffR08OyhZZHs46PopwAsf_6fdTLKB1UGvLr95volwEwIFnHjdvMfTJ9ffffffffffffAU
+```
+>**Note:** While all values provided in this document are the correct length and format, the raw values have been edited to prevent this public example being used as a dotnet-monitor configuration.
+
+### Header
+The header (decoded from the token above) must contain at least 2 elements: `alg` (or [Algorithm](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1)), and `typ` (or [Type](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1)). `dotnet monitor` expects the `typ` to always be `JWT` for a JSON Web Token. `dotnet monitor` supports 6 `alg` values: `ES256`, `ES384`, `ES512`, `RS256`, `RS384`, and `RS512`.
+
+```json
+{
+ "alg": "ES384",
+ "typ": "JWT"
+}
+```
+>**Note:** The `alg` requirement is designed to enforce `dotnet monitor` to use public/private key signed tokens. This allows the key that is stored in configuration (as `Authentication__MonitorApiKey__PublicKey`) to only contain public key information and thus does not need to be kept secret.
+
+### Payload
+The payload (also decoded from the token above) must contain at least 2 elements: `aud` (or [Audience](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3)), and `sub` (or [Subject](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2)). `dotnet monitor` expects the `aud` to always be `https://github.com/dotnet/dotnet-monitor` which signals that the token is intended for dotnet-monitor. The `sub` field is any non-empty string defined in `Authentication__MonitorApiKey__Subject`, this is used to validate that the token provided is for the expected instance and is user-defined in configuration.
+
+When using the `generatekey` command, the `sub` field will be a randomly-generated `Guid` but the `sub` field may be any non-empty string that matches the configuration. The `iss` (or [Issuer](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1)) field will be set to `https://github.com/dotnet/dotnet-monitor/generatekey+MonitorApiKey` to specify the source of the token, however `dotnet monitor` will accept any `iss` field value, and does not need to be present.
+```json
+{
+ "aud": "https://github.com/dotnet/dotnet-monitor",
+ "iss": "https://github.com/dotnet/dotnet-monitor/generatekey+MonitorApiKey",
+ "sub": "ae5473b6-8dad-498d-b915-ffffffffffff"
+}
+```
+
+### Signature
+JSON web tokens may be cryptographically signed; `dotnet monitor` requires all tokens to be signed and supports `RSA PKCS1` and `ECDSA` signed tokens, these are tokens with `RS*` and `ES*` as `alg` values. `dotnet monitor` needs the public key portion of this cryptographic material in order to validate the token. See the [Providing a Public Key](#providing-a-public-key) section for information on how to encode a key.
+
+## Providing a Public Key
+
+The public key is provided to `dotnet monitor` as a JSON Web Key or JWK, as defined by [RFC 7517: JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517.html). This key must be serialized as JSON and then Base64 URL encoded into a single string.
+
+`dotnet monitor` imposes the following constraints on JWKs:
+- The key should **not** have private key data. The key is only used for signature verification, and thus private key parameters are not needed. A warning message will be shown if the private key data is included.
+- The `kty` (or [Key Type](https://www.rfc-editor.org/rfc/rfc7517.html#section-4.1)) must be `RSA` for a RSA public key or `EC` for an elliptic-curve public key.
+
+The key used for the token in this example is:
+
+```json
+{
+ "AdditionalData":{},
+ "Crv":"P-384",
+ "KeyOps":[],
+ "Kty":"EC",
+ "X":"HoffffffffffffuHyjH_57Yf4AkPLEhI5QOTnRugE192Xz_VqcffffffffffffOj",
+ "X5c":[],
+ "Y":"JyffffffffffffhzyV-VCMdUttelaY2a8WmileII4MzaYp9j6EffffffffffffFi",
+ "KeySize":384,
+ "HasPrivateKey":false,
+ "CryptoProviderFactory": {
+ "CryptoProviderCache":{},
+ "CacheSignatureProviders":true,
+ "SignatureProviderObjectPoolCacheSize":32
+ }
+}
+```
+The JWK above is then Base64 URL encoded into the following value which is passed to `dotnet monitor` as `Authentication__MonitorApiKey__PublicKey`
+```yaml
+eyffffffffffffFsRGF0YSI6e30sIkNydiI6IlAtMzg0IiwiS2V5T3BzIjpbXSwiS3R5IjoiRUMiLCJYIjoiTnhIRnhVZ19QM1dhVUZWVzk0U3dUY3FzVk5zNlFLYjZxc3AzNzVTRmJfQ3QyZHdpN0RWRl8tUTVheERtYlJuWSIsIlg1YyI6W10sIlkiOiJmMXBDdmNoUkVpTWEtc1h6SlZQaS02YmViMHdrZmxfdUZBN0Vka2dwcjF5N251Wmk2cy1NcHl5RzhKdVFSNWZOIiwiS2V5U2l6ZSI6Mzg0LCJIYXNQcml2YXRlS2V5IjpmYWxzZSwiQ3J5cHRvUHJvdmlkZXJGYWN0b3J5Ijp7IkNyeXB0b1Byb3ZpZGVyQ2FjaGUiOnt9LCJDYWNoZVNpZ25hdHVyZVByb3ZpZGVycyI6dHJ1ZSwiU2lnbmF0dXJlUHJvdmlkZXJPYmplY3RQb29sQ2FjaGffffffffffff19
+```
diff --git a/documentation/authentication.md b/documentation/authentication.md
index 8c8f309e8bf..2a1c62f7889 100644
--- a/documentation/authentication.md
+++ b/documentation/authentication.md
@@ -14,29 +14,38 @@ Windows authentication doesn't require explicit configuration and is enabled aut
> **NOTE:** Windows authentication will not be attempted if you are running `dotnet monitor` as an Administrator
## API Key Authentication
-An API Key is the recommended authentication mechanism for `dotnet monitor`. To enable it, you will need to generate a secret key, update the configuration of `dotnet monitor`, and then specify the API Key in the Authorization header on all requests to `dotnet monitor`.
+An API Key is the recommended authentication mechanism for `dotnet monitor`. API Keys are referred to as `MonitorApiKey` in configuration and source code but we will shorten the term to "API key" in this document. To enable it, you will need to generate a secret token, update the configuration of `dotnet monitor`, and then specify the secret token in the `Authorization` header on all requests to `dotnet monitor`.
> **NOTE:** API Key Authentication should only be used when [TLS is enabled](#) to protect the key while in transit. `dotnet monitor` will emit a warning if authentication is enabled over an insecure transport medium.
### Generating an API Key
-The API Key you use to secure `dotnet monitor` should be a 32-byte cryptographically random secret. You can generate a key either using `dotnet monitor` or via your shell. To generate an API Key with `dotnet monitor`, simply invoke the `generatekey` command:
+The API Key you use to secure `dotnet monitor` is a secret JWT token, cryptographically signed by a public/private key algorithm. You can generate a key either using `dotnet monitor` or via your shell. To generate an API Key with `dotnet monitor`, simply invoke the `generatekey` command:
```powershell
dotnet monitor generatekey
```
-The output from this command will display the API Key formatted as an authentication header along with its hash and associated hashing algorithm. You will need to store the `ApiKeyHash` and `ApiKeyHashType` in the configuration for `dotnet monitor` and use the authorization header value when making requests to the `dotnet monitor` HTTPS endpoint.
+The output from this command will display the API key (a bearer JWT token) formatted as an `Authorization` header along with its corresponding configuration for `dotnet monitor`. You will need to store the `Subject` and `PublicKey` in the configuration for `dotnet monitor` and use the `Authorization` header value when making requests to the `dotnet monitor` HTTPS endpoint.
```yaml
-Authorization: MonitorApiKey fffffffffffffffffffffffffffffffffffffffffff=
-ApiKeyHash: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-ApiKeyHashType: SHA256
+Generated ApiKey for dotnet-monitor; use the following header for authorization:
+
+Authorization: Bearer eyJhbGciOiJFUffffffffffffCI6IkpXVCJ9.eyJhdWQiOiJodffffffffffffGh1Yi5jb20vZG90bmV0L2RvdG5ldC1tb25pdG9yIiwiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2RvdG5ldC9kb3RuZXQtbW9uaXRvci9nZW5lcmF0ZWtleStNb25pdG9yQXBpS2V5Iiwic3ViIjoiYWU1NDczYjYtOGRhZC00OThkLWI5MTUtNTNiOWM2ODQwMDBlIn0.RZffffffffffff_yIyApvFKcxFpDJ65HJZek1_dt7jCTCMEEEffffffffffffR08OyhZZHs46PopwAsf_6fdTLKB1UGvLr95volwEwIFnHjdvMfTJ9ffffffffffffAU
+
+Settings in Text format:
+Subject: ae5473b6-8dad-498d-b915-ffffffffffff
+Public Key: eyffffffffffffFsRGF0YSI6e30sIkNydiI6IlAtMzg0IiwiS2V5T3BzIjpbXSwiS3R5IjoiRUMiLCJYIjoiTnhIRnhVZ19QM1dhVUZWVzk0U3dUY3FzVk5zNlFLYjZxc3AzNzVTRmJfQ3QyZHdpN0RWRl8tUTVheERtYlJuWSIsIlg1YyI6W10sIlkiOiJmMXBDdmNoUkVpTWEtc1h6SlZQaS02YmViMHdrZmxfdUZBN0Vka2dwcjF5N251Wmk2cy1NcHl5RzhKdVFSNWZOIiwiS2V5U2l6ZSI6Mzg0LCJIYXNQcml2YXRlS2V5IjpmYWxzZSwiQ3J5cHRvUHJvdmlkZXJGYWN0b3J5Ijp7IkNyeXB0b1Byb3ZpZGVyQ2FjaGUiOnt9LCJDYWNoZVNpZ25hdHVyZVByb3ZpZGVycyI6dHJ1ZSwiU2lnbmF0dXJlUHJvdmlkZXJPYmplY3RQb29sQ2FjaGffffffffffff19
```
+>**Note:** While all values provided in this document are the correct length and format, the raw values have been edited to provent this public example being used as a dotnet-monitor configuration.
-The API Key is hashed using the SHA256 algorithm when using the `generatekey` command, but other [secure hash implementations](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hashalgorithm.create?view=net-5.0#System_Security_Cryptography_HashAlgorithm_Create_System_String) (such as SHA384 and SHA512) are also supported; due to collision problems the SHA1 and MD5 algorithms are not permitted.
+The `generatekey` command supports 1 parameter `--output`/`-o` to specify the configuration format. By default, `dotnet monitor generatekey` will use the `--output json` format. Currently, the values in the list below are supported values for `--output`.
-> NOTE: The generated API Key should be secured at rest. We recommend using a tool such as a password manager to save it.
+- `Json` output format will provide a json blob in the correct format to merge with an `appsettings.json` file to specify configuration via json file.
+- `Text` output format write the individual parameters in an easily human-readable format.
+- `Cmd` output format in environment variables for a `cmd.exe` prompt.
+- `PowerShell` output format in environment variables for a `powershell` or `pwsh` prompt.
+- `Shell` output format in environment variables for a `bash` shell or another linux shell prompt.
### Configuring dotnet-monitor to use an API Key
@@ -48,29 +57,31 @@ If you're running on Windows, you can save these settings to `%USERPROFILE%\.dot
```json
{
- "ApiAuthentication": {
- "ApiKeyHash": "CB233C3BE9F650146CFCA81D7AA608E3A3865D7313016DFA02DAF82A2505C683",
- "ApiKeyHashType": "SHA256"
+ "Authentication": {
+ "MonitorApiKey": {
+ "Subject": "ae5473b6-8dad-498d-b915-ffffffffffff",
+ "PublicKey": "eyffffffffffffFsRGF0YSI6e30sIkNydiI6IlAtMzg0IiwiS2V5T3BzIjpbXSwiS3R5IjoiRUMiLCJYIjoiTnhIRnhVZ19QM1dhVUZWVzk0U3dUY3FzVk5zNlFLYjZxc3AzNzVTRmJfQ3QyZHdpN0RWRl8tUTVheERtYlJuWSIsIlg1YyI6W10sIlkiOiJmMXBDdmNoUkVpTWEtc1h6SlZQaS02YmViMHdrZmxfdUZBN0Vka2dwcjF5N251Wmk2cy1NcHl5RzhKdVFSNWZOIiwiS2V5U2l6ZSI6Mzg0LCJIYXNQcml2YXRlS2V5IjpmYWxzZSwiQ3J5cHRvUHJvdmlkZXJGYWN0b3J5Ijp7IkNyeXB0b1Byb3ZpZGVyQ2FjaGUiOnt9LCJDYWNoZVNpZ25hdHVyZVByb3ZpZGVycyI6dHJ1ZSwiU2lnbmF0dXJlUHJvdmlkZXJPYmplY3RQb29sQ2FjaGffffffffffff19"
+ }
}
}
```
-Alternatively, you can use environment variables to specify the configuration.
+Alternatively, you can use environment variables to specify the configuration. Use `dotnet monitor generatekey --output shell` to get the format below.
```sh
-DotnetMonitor_ApiAuthentication__ApiKeyHash="CB233C3BE9F650146CFCA81D7AA608E3A3865D7313016DFA02DAF82A2505C683"
-DotnetMonitor_ApiAuthentication__ApiKeyHashType="SHA256"
+export Authentication__MonitorApiKey__Subject="ae5473b6-8dad-498d-b915-ffffffffffff"
+export Authentication__MonitorApiKey__PublicKey="eyffffffffffffFsRGF0YSI6e30sIkNydiI6IlAtMzg0IiwiS2V5T3BzIjpbXSwiS3R5IjoiRUMiLCJYIjoiTnhIRnhVZ19QM1dhVUZWVzk0U3dUY3FzVk5zNlFLYjZxc3AzNzVTRmJfQ3QyZHdpN0RWRl8tUTVheERtYlJuWSIsIlg1YyI6W10sIlkiOiJmMXBDdmNoUkVpTWEtc1h6SlZQaS02YmViMHdrZmxfdUZBN0Vka2dwcjF5N251Wmk2cy1NcHl5RzhKdVFSNWZOIiwiS2V5U2l6ZSI6Mzg0LCJIYXNQcml2YXRlS2V5IjpmYWxzZSwiQ3J5cHRvUHJvdmlkZXJGYWN0b3J5Ijp7IkNyeXB0b1Byb3ZpZGVyQ2FjaGUiOnt9LCJDYWNoZVNpZ25hdHVyZVByb3ZpZGVycyI6dHJ1ZSwiU2lnbmF0dXJlUHJvdmlkZXJPYmplY3RQb29sQ2FjaGffffffffffff19"
```
-> **NOTE:** When you use environment variables to configure the API Key hash, you must restart `dotnet monitor` for the changes to take effect.
+> **NOTE:** When you use environment variables to configure the API Key, you must restart `dotnet monitor` for the changes to take effect.
### Configuring an API Key in a Kubernetes Cluster
If you're running in Kubernetes, we recommend creating secrets and mounting them into the `dotnet monitor` sidecar container via a deployment manifest. You can use this `kubectl` command to either create or rotate the API Key.
```sh
kubectl create secret generic apikey \
- --from-literal=ApiAuthentication__ApiKeyHash=$hash \
- --from-literal=ApiAuthentication__ApiKeyHashType=SHA256 \
+ --from-literal=Authentication__MonitorApiKey__Subject='ae5473b6-8dad-498d-b915-ffffffffffff' \
+ --from-literal=Authentication__MonitorApiKey__PublicKey='eyffffffffffff...19' \
--dry-run=client -o yaml \
| kubectl apply -f -
```
@@ -94,52 +105,30 @@ spec:
> **NOTE:** For a complete example of running dotnet-monitor in Kubernetes, see [Running in a Kubernetes Cluster](getting-started.md#running-in-a-kubernetes-cluster) in the Getting Started guide.
-### Generate an API Key with PowerShell
-```powershell
-$rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
-$secret = [byte[]]::new(32)
-$rng.GetBytes($secret)
-$API_KEY = [Convert]::ToBase64String($secret)
-"Authorization: MonitorApiKey $API_KEY"
-
-$secretStream = [System.IO.MemoryStream]::new($secret)
-$HASHED_KEY = (Get-FileHash -Algorithm SHA256 -InputStream $secretStream).Hash
-$rng.Dispose()
-
-Write-Output "ApiKeyHash: $HASHED_KEY"
-Write-Output "ApiKeyHashType: SHA256"
-```
-
-### Generate an API Key with a Linux shell
-
-```sh
-API_KEY=`openssl rand -base64 32`
-echo "Authorization: MonitorApiKey $API_KEY"
+### Generate API Keys manually
-HASHED_KEY=`$API_KEY | base64 -d | sha256sum`
-echo "ApiKeyHash: $HASHED_KEY"
-echo "ApiKeyHashType: SHA256"
-```
+API keys for `dotnet monitor` are standard JSON Web Tokens or JWTs and can be generated without the `generatekey` command on `dotnet monitor`. For more details see [Api Key Format](api-key-format.md).
## Authenticating requests
When using Windows Authentication, your browser will automatically handle the Windows authentication challenge. If you are using an API Key, you must specify it via the `Authorization` header.
```sh
-curl -H "Authorization: MonitorApiKey fffffffffffffffffffffffffffffffffffffffffff=" https://localhost:52323/processes
+curl -H "Authorization: Bearer eyJhbGciOiJFUffffffffffffCI6IkpXVCJ9.eyJhdWQiOiJodffffffffffffGh1Yi5jb20vZG90bmV0L2RvdG5ldC1tb25pdG9yIiwiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2RvdG5ldC9kb3RuZXQtbW9uaXRvci9nZW5lcmF0ZWtleStNb25pdG9yQXBpS2V5Iiwic3ViIjoiYWU1NDczYjYtOGRhZC00OThkLWI5MTUtNTNiOWM2ODQwMDBlIn0.RZffffffffffff_yIyApvFKcxFpDJ65HJZek1_dt7jCTCMEEEffffffffffffR08OyhZZHs46PopwAsf_6fdTLKB1UGvLr95volwEwIFnHjdvMfTJ9ffffffffffffAU" https://localhost:52323/processes
```
-If using PowerShell, be sure to use `curl.exe`, as `curl` is an alias for `Invoke-WebRequest` that does not accept the same parameters.
+If using PowerShell, you can use `Invoke-WebRequest` but it does not accept the same parameters.
```powershell
-curl.exe -H "Authorization: MonitorApiKey fffffffffffffffffffffffffffffffffffffffffff=" https://localhost:52323/processes
+ (Invoke-WebRequest -Uri https://localhost:52323/processes -Headers @{ 'Authorization' = 'Bearer eyJhbGciOiJFUffffffffffffCI6IkpXVCJ9.eyJhdWQiOiJodffffffffffffGh1Yi5jb20vZG90bmV0L2RvdG5ldC1tb25pdG9yIiwiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2RvdG5ldC9kb3RuZXQtbW9uaXRvci9nZW5lcmF0ZWtleStNb25pdG9yQXBpS2V5Iiwic3ViIjoiYWU1NDczYjYtOGRhZC00OThkLWI5MTUtNTNiOWM2ODQwMDBlIn0.RZffffffffffff_yIyApvFKcxFpDJ65HJZek1_dt7jCTCMEEEffffffffffffR08OyhZZHs46PopwAsf_6fdTLKB1UGvLr95volwEwIFnHjdvMfTJ9ffffffffffffAU' }).Content | ConvertFrom-Json
```
-To use Windows authentication with PowerShell, you can specify the `--negotiate` flag
+To use Windows authentication with PowerShell, you can specify the `-UseDefaultCredentials` flag for `Invoke-WebRequest` or `--negotiate` for `curl.exe`
```powershell
curl.exe --negotiate https://localhost:52323/processes -u $(whoami)
```
-
-
+```powershell
+(Invoke-WebRequest -Uri https://localhost:52323/processes -UseDefaultCredentials).Content | ConvertFrom-Json
+```
## Disabling Authentication
diff --git a/documentation/schema.json b/documentation/schema.json
index b7ce988a80a..2c1f961ea89 100644
--- a/documentation/schema.json
+++ b/documentation/schema.json
@@ -16,14 +16,14 @@
}
]
},
- "ApiAuthentication": {
+ "Authentication": {
"default": {},
"oneOf": [
{
"type": "null"
},
{
- "$ref": "#/definitions/ApiAuthenticationOptions"
+ "$ref": "#/definitions/AuthenticationOptions"
}
]
},
@@ -34,7 +34,7 @@
"type": "null"
},
{
- "$ref": "#/definitions/CorsConfiguration"
+ "$ref": "#/definitions/CorsConfigurationOptions"
}
]
},
@@ -335,28 +335,45 @@
}
}
},
- "ApiAuthenticationOptions": {
+ "AuthenticationOptions": {
"type": "object",
"additionalProperties": false,
"required": [
- "ApiKeyHash",
- "ApiKeyHashType"
+ "MonitorApiKey"
],
"properties": {
- "ApiKeyHash": {
+ "MonitorApiKey": {
+ "description": "The parameters used to validate MonitorApiKey JWT tokens.",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/MonitorApiKeyOptions"
+ }
+ ]
+ }
+ }
+ },
+ "MonitorApiKeyOptions": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "Subject",
+ "PublicKey"
+ ],
+ "properties": {
+ "Subject": {
"type": "string",
- "description": "API key in hashed form. Each byte should be two hexadecimal-based digits.",
- "minLength": 64,
- "pattern": "[0-9a-fA-F]+"
+ "description": "The value of the 'sub' or Subject field in the JWT (JSON Web Token).",
+ "minLength": 1
},
- "ApiKeyHashType": {
+ "PublicKey": {
"type": "string",
- "description": "Hash algorithm used to compute ApiKeyHash, typically 'SHA256'. 'SHA1' and 'MD5' are not allowed.",
- "minLength": 1
+ "description": "The public key used to sign the JWT (JSON Web Token) used for authentication. This field is a JSON Web Key serialized as JSON encoded with base64Url encoding. The JWK must have a kty field of RSA or EC and should not have the private key information.",
+ "minLength": 1,
+ "pattern": "[0-9a-zA-Z_-]+"
}
}
},
- "CorsConfiguration": {
+ "CorsConfigurationOptions": {
"type": "object",
"additionalProperties": false,
"required": [
diff --git a/eng/Versions.props b/eng/Versions.props
index f454e89d669..280ddcfadbc 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -48,6 +48,7 @@
12.6.0
+ 3.1.173.1.102.1.222.1.3
@@ -57,9 +58,11 @@
5.0.05.0.05.0.1
+ 6.11.11.2.32.0.0-beta1.20468.15.0.0
+ 6.11.14.5.15.0.04.7.2
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/AuthenticationOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/AuthenticationOptions.cs
new file mode 100644
index 00000000000..205e88dab21
--- /dev/null
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/AuthenticationOptions.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using System.ComponentModel.DataAnnotations;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ internal sealed class AuthenticationOptions
+ {
+ [Display(
+ ResourceType = typeof(OptionsDisplayStrings),
+ Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_AuthenticationOptions_MonitorApiKey))]
+ [Required]
+ public MonitorApiKeyOptions MonitorApiKey { get; set; }
+ }
+}
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/CorsConfiguration.cs b/src/Microsoft.Diagnostics.Monitoring.Options/CorsConfigurationOptions.cs
similarity index 93%
rename from src/Microsoft.Diagnostics.Monitoring.Options/CorsConfiguration.cs
rename to src/Microsoft.Diagnostics.Monitoring.Options/CorsConfigurationOptions.cs
index 2d551dac340..91bbfac1a11 100644
--- a/src/Microsoft.Diagnostics.Monitoring.Options/CorsConfiguration.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/CorsConfigurationOptions.cs
@@ -6,7 +6,7 @@
namespace Microsoft.Diagnostics.Monitoring.WebApi
{
- public class CorsConfiguration
+ public class CorsConfigurationOptions
{
[Display(
ResourceType = typeof(OptionsDisplayStrings),
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/ApiAuthenticationOptions.cs b/src/Microsoft.Diagnostics.Monitoring.Options/MonitorApiKeyOptions.cs
similarity index 66%
rename from src/Microsoft.Diagnostics.Monitoring.Options/ApiAuthenticationOptions.cs
rename to src/Microsoft.Diagnostics.Monitoring.Options/MonitorApiKeyOptions.cs
index a0bdc3cddfd..6e0149a0571 100644
--- a/src/Microsoft.Diagnostics.Monitoring.Options/ApiAuthenticationOptions.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/MonitorApiKeyOptions.cs
@@ -7,20 +7,19 @@
namespace Microsoft.Diagnostics.Tools.Monitor
{
- internal sealed class ApiAuthenticationOptions
+ internal sealed class MonitorApiKeyOptions
{
[Display(
ResourceType = typeof(OptionsDisplayStrings),
- Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHash))]
- [RegularExpression("[0-9a-fA-F]+")]
- [MinLength(64)]
+ Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_MonitorApiKeyOptions_Subject))]
[Required]
- public string ApiKeyHash { get; set; }
+ public string Subject { get; set; }
[Display(
ResourceType = typeof(OptionsDisplayStrings),
- Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHashType))]
+ Description = nameof(OptionsDisplayStrings.DisplayAttributeDescription_MonitorApiKeyOptions_PublicKey))]
+ [RegularExpression("[0-9a-zA-Z_-]+")]
[Required]
- public string ApiKeyHashType { get; set; }
+ public string PublicKey { get; set; }
}
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs
index 45f2b3659fc..8c810f1d307 100644
--- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.Designer.cs
@@ -61,20 +61,11 @@ internal OptionsDisplayStrings() {
}
///
- /// Looks up a localized string similar to API key in hashed form. Each byte should be two hexadecimal-based digits..
+ /// Looks up a localized string similar to The parameters used to validate MonitorApiKey JWT tokens..
///
- public static string DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHash {
+ public static string DisplayAttributeDescription_AuthenticationOptions_MonitorApiKey {
get {
- return ResourceManager.GetString("DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHash", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Hash algorithm used to compute ApiKeyHash, typically 'SHA256'. 'SHA1' and 'MD5' are not allowed..
- ///
- public static string DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHashType {
- get {
- return ResourceManager.GetString("DisplayAttributeDescription_ApiAuthenticationOptions_ApiKeyHashType", resourceCulture);
+ return ResourceManager.GetString("DisplayAttributeDescription_AuthenticationOptions_MonitorApiKey", resourceCulture);
}
}
@@ -467,6 +458,24 @@ public static string DisplayAttributeDescription_MetricsOptions_UpdateIntervalSe
}
}
+ ///
+ /// Looks up a localized string similar to The public key used to sign the JWT (JSON Web Token) used for authentication. This field is a JSON Web Key serialized as JSON encoded with base64Url encoding. The JWK must have a kty field of RSA or EC and should not have the private key information..
+ ///
+ public static string DisplayAttributeDescription_MonitorApiKeyOptions_PublicKey {
+ get {
+ return ResourceManager.GetString("DisplayAttributeDescription_MonitorApiKeyOptions_PublicKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The value of the 'sub' or Subject field in the JWT (JSON Web Token)..
+ ///
+ public static string DisplayAttributeDescription_MonitorApiKeyOptions_Subject {
+ get {
+ return ResourceManager.GetString("DisplayAttributeDescription_MonitorApiKeyOptions_Subject", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The criteria used to compare against the target process..
///
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx
index 5684a54647e..acba0a92790 100644
--- a/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/OptionsDisplayStrings.resx
@@ -117,14 +117,6 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- API key in hashed form. Each byte should be two hexadecimal-based digits.
- The description provided for the ApiKeyHash parameter on ApiAuthenticationOptions.
-
-
- Hash algorithm used to compute ApiKeyHash, typically 'SHA256'. 'SHA1' and 'MD5' are not allowed.
- The description provided for the ApiKeyHashType parameter on ApiAuthenticationOptions.
-
The account key used to access the Azure blob storage account.The description provided for the AccountKey parameter on AzureBlobEgressProviderOptions.
@@ -197,6 +189,10 @@
The minimum level of messages that are written to Console.Error.The description provided for the LogToStandardErrorThreshold parameter on ConsoleLoggerOptions.
+
+ The parameters used to validate MonitorApiKey JWT tokens.
+ The description provided for the MonitorApiKey parameter on AuthenticationOptions.
+
List of allowed CORS origins, separated by semicolons.The description provided for the AllowedOrigins parameter on CorsConfiguration.
@@ -297,6 +293,14 @@
How often metrics are collected.The description provided for the UpdateIntervalSeconds parameter on MetricsOptions.
+
+ The public key used to sign the JWT (JSON Web Token) used for authentication. This field is a JSON Web Key serialized as JSON encoded with base64Url encoding. The JWK must have a kty field of RSA or EC and should not have the private key information.
+ The description provided for the PublicKey parameter on MonitorApiKeyOptions.
+
+
+ The value of the 'sub' or Subject field in the JWT (JSON Web Token).
+ The description provided for the Subject parameter on MonitorApiKeyOptions.
+
The criteria used to compare against the target process.The description provided for the Key parameter on ProcessFilterDescriptor.
diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Auth/AuthConstants.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Auth/AuthConstants.cs
index fde16fec49b..8770c0000ee 100644
--- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Auth/AuthConstants.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Auth/AuthConstants.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+
namespace Microsoft.Diagnostics.Monitoring.WebApi
{
internal static class AuthConstants
@@ -10,6 +11,13 @@ internal static class AuthConstants
public const string NegotiateSchema = "Negotiate";
public const string NtlmSchema = "NTLM";
public const string KerberosSchema = "Kerberos";
- public const string ApiKeySchema = "MonitorApiKey";
+ public const string FederationAuthType = "AuthenticationTypes.Federation";
+ public const string ApiKeySchema = "Bearer";
+ public const string ApiKeyJwtType = "JWT";
+ public const string ApiKeyJwtInternalIssuer = "https://github.com/dotnet/dotnet-monitor/generatekey+MonitorApiKey";
+ public const string ApiKeyJwtAudience = "https://github.com/dotnet/dotnet-monitor";
+ public const string ClaimAudienceStr = "aud";
+ public const string ClaimIssuerStr = "iss";
+ public const string ClaimSubjectStr = "sub";
}
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.csproj
index 45aa14474e9..25f1234f32f 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.csproj
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Options/RootOptions.Logging.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Options/RootOptions.Logging.cs
index 2cd30f41b84..0856313ad1f 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Options/RootOptions.Logging.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema/Options/RootOptions.Logging.cs
@@ -6,7 +6,7 @@
namespace Microsoft.Diagnostics.Tools.Monitor
{
- partial class RootOptions
+ internal partial class RootOptions
{
public LoggingOptions Logging { get; set; }
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
index 3200d59a1f9..855806001d7 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
@@ -41,6 +41,11 @@ public sealed class DotNetRunner : IDisposable
///
public IDictionary Environment => _process.StartInfo.Environment;
+ ///
+ /// Gets a indicating if has been called and the process has been started.
+ ///
+ public bool HasStarted { get; private set; } = false;
+
///
/// Retrieves the exit code of the process.
///
@@ -59,7 +64,7 @@ public sealed class DotNetRunner : IDisposable
///
/// Determines if the process has exited.
///
- public bool HasExited => _process.HasExited;
+ public bool HasExited => HasStarted && _process.HasExited;
///
/// Gets the process ID of the running process.
@@ -133,6 +138,7 @@ public async Task StartAsync(CancellationToken token)
{
throw new InvalidOperationException($"Unable to start: {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
}
+ HasStarted = true;
if (WaitForDiagnosticPipe)
{
@@ -161,7 +167,7 @@ public async Task StartAsync(CancellationToken token)
///
public async Task WaitForExitAsync(CancellationToken token)
{
- if (!_process.HasExited)
+ if (HasStarted && !_process.HasExited)
{
TaskCompletionSource
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/OptionsExtensions.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/OptionsExtensions.cs
index 4bd9d6ed406..7c9aa7cb512 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/OptionsExtensions.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/OptionsExtensions.cs
@@ -5,63 +5,19 @@
using Microsoft.Diagnostics.Tools.Monitor;
using Microsoft.Diagnostics.Tools.Monitor.Egress.FileSystem;
using System;
-using System.Collections;
using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
using System.Security.Cryptography;
-using System.Text;
+using System.Text.Json;
+using Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.Diagnostics.Monitoring.WebApi;
namespace Microsoft.Diagnostics.Monitoring.TestCommon.Options
{
internal static class OptionsExtensions
{
- ///
- /// Generates an environment variable map of the options.
- ///
- ///
- /// Each key is the variable name; each value is the variable value.
- ///
- public static IDictionary ToEnvironmentConfiguration(this RootOptions options)
- {
- Dictionary variables = new(StringComparer.OrdinalIgnoreCase);
- MapObject(options, "DotNetMonitor_", variables);
- return variables;
- }
-
- ///
- /// Generates a key-per-file map of the options.
- ///
- ///
- /// Each key is the file name; each value is the file content.
- ///
- public static IDictionary ToKeyPerFileConfiguration(this RootOptions options)
- {
- Dictionary variables = new(StringComparer.OrdinalIgnoreCase);
- MapObject(options, string.Empty, variables);
- return variables;
- }
-
- ///
- /// Sets API Key authentication.
- ///
- public static RootOptions UseApiKey(this RootOptions options, string algorithmName, byte[] apiKey)
- {
- if (null == options.ApiAuthentication)
- {
- options.ApiAuthentication = new ApiAuthenticationOptions();
- }
-
- using var hashAlgorithm = HashAlgorithm.Create(algorithmName);
-
- byte[] hash = hashAlgorithm.ComputeHash(apiKey);
- options.ApiAuthentication.ApiKeyHash = ToHexString(hash);
- options.ApiAuthentication.ApiKeyHashType = algorithmName;
-
- return options;
- }
-
public static RootOptions AddFileSystemEgress(this RootOptions options, string name, string outputPath)
{
var egressProvider = new FileSystemEgressProviderOptions()
@@ -80,89 +36,152 @@ public static RootOptions AddFileSystemEgress(this RootOptions options, string n
return options;
}
- private static void MapDictionary(IDictionary dictionary, string prefix, IDictionary map)
+ ///
+ /// Sets API Key authentication. Use this overload for most operations, unless specifically testing Authentication or Authorization.
+ ///
+ public static RootOptions UseApiKey(this RootOptions options, string algorithmName, Guid subject, out string token)
{
- foreach (var key in dictionary.Keys)
- {
- object value = dictionary[key];
- if (null != value)
- {
- string keyString = Convert.ToString(key, CultureInfo.InvariantCulture);
- MapValue(
- value,
- FormattableString.Invariant($"{prefix}{keyString}"),
- map);
- }
- }
+ string subjectStr = subject.ToString("D");
+ Claim audClaim = new Claim(AuthConstants.ClaimAudienceStr, AuthConstants.ApiKeyJwtAudience);
+ Claim issClaim = new Claim(AuthConstants.ClaimIssuerStr, AuthConstants.ApiKeyJwtInternalIssuer);
+ Claim subClaim = new Claim(AuthConstants.ClaimSubjectStr, subjectStr);
+ JwtPayload newPayload = new JwtPayload(new Claim[] { audClaim, issClaim, subClaim });
+
+ return options.UseApiKey(algorithmName, subjectStr, newPayload, out token);
}
- private static void MapList(IList list, string prefix, IDictionary map)
+ public static RootOptions UseApiKey(this RootOptions options, string algorithmName, string subject, JwtPayload customPayload, out string token)
{
- for (int index = 0; index < list.Count; index++)
+ return options.UseApiKey(algorithmName, subject, customPayload, out token, out SecurityKey _);
+ }
+
+ public static RootOptions UseApiKey(this RootOptions options, string algorithmName, string subject, JwtPayload customPayload, out string token, out SecurityKey privateKey)
+ {
+ if (null == options.Authentication)
{
- object value = list[index];
- if (null != value)
- {
- MapValue(
- value,
- FormattableString.Invariant($"{prefix}{index}"),
- map);
- }
+ options.Authentication = new AuthenticationOptions();
+ }
+
+ if (null == options.Authentication.MonitorApiKey)
+ {
+ options.Authentication.MonitorApiKey = new MonitorApiKeyOptions();
+ }
+
+ SigningCredentials signingCreds;
+ JsonWebKey exportableJwk;
+ switch (algorithmName)
+ {
+ case SecurityAlgorithms.EcdsaSha256:
+ case SecurityAlgorithms.EcdsaSha256Signature:
+ case SecurityAlgorithms.EcdsaSha384:
+ case SecurityAlgorithms.EcdsaSha384Signature:
+ case SecurityAlgorithms.EcdsaSha512:
+ case SecurityAlgorithms.EcdsaSha512Signature:
+ ECDsa ecDsa = ECDsa.Create(GetEcCurveFromName(algorithmName));
+ ECDsaSecurityKey ecSecKey = new ECDsaSecurityKey(ecDsa);
+ signingCreds = new SigningCredentials(ecSecKey, algorithmName);
+ ECDsa pubEcDsa = ECDsa.Create(ecDsa.ExportParameters(false));
+ ECDsaSecurityKey pubEcSecKey = new ECDsaSecurityKey(pubEcDsa);
+ exportableJwk = JsonWebKeyConverter.ConvertFromECDsaSecurityKey(pubEcSecKey);
+ privateKey = ecSecKey;
+ break;
+
+ case SecurityAlgorithms.RsaSha256:
+ case SecurityAlgorithms.RsaSha256Signature:
+ case SecurityAlgorithms.RsaSha384:
+ case SecurityAlgorithms.RsaSha384Signature:
+ case SecurityAlgorithms.RsaSha512:
+ case SecurityAlgorithms.RsaSha512Signature:
+ RSA rsa = RSA.Create(GetRsaKeyLengthFromName(algorithmName));
+ RsaSecurityKey rsaSecKey = new RsaSecurityKey(rsa);
+ signingCreds = new SigningCredentials(rsaSecKey, algorithmName);
+ RSA pubRsa = RSA.Create(rsa.ExportParameters(false));
+ RsaSecurityKey pubRsaSecKey = new RsaSecurityKey(pubRsa);
+ exportableJwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(pubRsaSecKey);
+ privateKey = rsaSecKey;
+ break;
+
+ case SecurityAlgorithms.HmacSha256:
+ case SecurityAlgorithms.HmacSha384:
+ case SecurityAlgorithms.HmacSha512:
+ HMAC hmac = HMAC.Create(GetHmacAlgorithmFromName(algorithmName));
+ SymmetricSecurityKey hmacSecKey = new SymmetricSecurityKey(hmac.Key);
+ signingCreds = new SigningCredentials(hmacSecKey, algorithmName);
+ exportableJwk = JsonWebKeyConverter.ConvertFromSymmetricSecurityKey(hmacSecKey);
+ privateKey = hmacSecKey;
+ break;
+
+ default:
+ throw new ArgumentException($"Algorithm name '{algorithmName}' not supported", nameof(algorithmName));
}
+
+ JwtHeader newHeader = new JwtHeader(signingCreds, null, JwtConstants.HeaderType);
+ JwtSecurityToken newToken = new JwtSecurityToken(newHeader, customPayload);
+ JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
+ string resultToken = tokenHandler.WriteToken(newToken);
+
+ JsonSerializerOptions serializerOptions = JsonSerializerOptionsFactory.Create(JsonSerializerOptionsFactory.JsonIgnoreCondition.WhenWritingNull);
+ string publicKeyJson = JsonSerializer.Serialize(exportableJwk, serializerOptions);
+
+ string publicKeyEncoded = Base64UrlEncoder.Encode(publicKeyJson);
+
+ options.Authentication.MonitorApiKey.Subject = subject;
+ options.Authentication.MonitorApiKey.PublicKey = publicKeyEncoded;
+
+ token = resultToken;
+
+ return options;
}
- private static void MapObject(object obj, string prefix, IDictionary map)
+ private static string GetHmacAlgorithmFromName(string algorithmName)
{
- foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ switch (algorithmName)
{
- if (!property.GetIndexParameters().Any())
- {
- MapValue(
- property.GetValue(obj),
- FormattableString.Invariant($"{prefix}{property.Name}"),
- map);
- }
+ case SecurityAlgorithms.HmacSha256:
+ return typeof(HMACSHA256).FullName;
+ case SecurityAlgorithms.HmacSha384:
+ return typeof(HMACSHA384).FullName;
+ case SecurityAlgorithms.HmacSha512:
+ return typeof(HMACSHA512).FullName;
+ default:
+ throw new ArgumentException($"Algorithm name '{algorithmName}' not supported", nameof(algorithmName));
}
}
- private static void MapValue(object value, string valueName, IDictionary map)
+ private static int GetRsaKeyLengthFromName(string algorithmName)
{
- if (null != value)
+ switch (algorithmName)
{
- Type valueType = value.GetType();
- if (valueType.IsPrimitive || typeof(string) == valueType)
- {
- map.Add(
- valueName,
- Convert.ToString(value, CultureInfo.InvariantCulture));
- }
- else
- {
- string prefix = FormattableString.Invariant($"{valueName}__");
- if (value is IDictionary dictionary)
- {
- MapDictionary(dictionary, prefix, map);
- }
- else if (value is IList list)
- {
- MapList(list, prefix, map);
- }
- else
- {
- MapObject(value, prefix, map);
- }
- }
+ case SecurityAlgorithms.RsaSha256:
+ case SecurityAlgorithms.RsaSha256Signature:
+ return 2048;
+ case SecurityAlgorithms.RsaSha384:
+ case SecurityAlgorithms.RsaSha384Signature:
+ return 3072;
+ case SecurityAlgorithms.RsaSha512:
+ case SecurityAlgorithms.RsaSha512Signature:
+ return 4096;
+ default:
+ throw new ArgumentException($"Algorithm name '{algorithmName}' not supported", nameof(algorithmName));
}
}
- private static string ToHexString(byte[] data)
+ private static ECCurve GetEcCurveFromName(string algorithmName)
{
- StringBuilder builder = new(2 * data.Length);
- foreach (byte b in data)
+ switch (algorithmName)
{
- builder.Append(b.ToString("X2", CultureInfo.InvariantCulture));
+ case SecurityAlgorithms.EcdsaSha256:
+ case SecurityAlgorithms.EcdsaSha256Signature:
+ return ECCurve.NamedCurves.nistP256;
+ case SecurityAlgorithms.EcdsaSha384:
+ case SecurityAlgorithms.EcdsaSha384Signature:
+ return ECCurve.NamedCurves.nistP384;
+ case SecurityAlgorithms.EcdsaSha512:
+ case SecurityAlgorithms.EcdsaSha512Signature:
+ return ECCurve.NamedCurves.nistP521;
+ default:
+ throw new ArgumentException($"Algorithm name '{algorithmName}' not supported", nameof(algorithmName));
}
- return builder.ToString();
}
}
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ProcessTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ProcessTests.cs
index a8112d72982..5e6f1d76c25 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ProcessTests.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ProcessTests.cs
@@ -100,7 +100,7 @@ public async Task MultiProcessIdentificationTest(DiagnosticPortConnectionMode mo
out DiagnosticPortConnectionMode appConnectionMode,
out string diagnosticPortPath);
- await using MonitorRunner toolRunner = new(_outputHelper);
+ await using MonitorCollectRunner toolRunner = new(_outputHelper);
toolRunner.ConnectionMode = mode;
toolRunner.DiagnosticPortPath = diagnosticPortPath;
toolRunner.DisableAuthentication = true;
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorCollectRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorCollectRunner.cs
new file mode 100644
index 00000000000..bb72fe587d8
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorCollectRunner.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.TestCommon;
+using Microsoft.Diagnostics.Monitoring.TestCommon.Options;
+using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.Runners
+{
+ ///
+ /// Runner for the dotnet-monitor tool.
+ ///
+ internal sealed class MonitorCollectRunner : MonitorRunner
+ {
+ // Completion source containing the bound address of the default URL (e.g. provided by --urls argument)
+ private readonly TaskCompletionSource _defaultAddressSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Completion source containing the bound address of the metrics URL (e.g. provided by --metricUrls argument)
+ private readonly TaskCompletionSource _metricsAddressSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Completion source containing the string representing the base64 encoded MonitorApiKey for accessing the monitor (e.g. provided by --temp-apikey argument)
+ private readonly TaskCompletionSource _monitorApiKeySource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // Completion source containing a string which is fired when the monitor enters a ready idle state
+ private readonly TaskCompletionSource _readySource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private bool _isDiposed;
+
+ ///
+ /// Event callback for when a Private Key warning message is seen.
+ ///
+ public event Action WarnPrivateKey;
+
+ ///
+ /// The mode of the diagnostic port connection. Default is
+ /// (the tool is searching for apps that are in listen mode).
+ ///
+ ///
+ /// Set to if tool needs to establish the diagnostic port listener.
+ ///
+ public DiagnosticPortConnectionMode ConnectionMode { get; set; } = DiagnosticPortConnectionMode.Connect;
+
+ ///
+ /// Path of the diagnostic port to establish when is .
+ ///
+ public string DiagnosticPortPath { get; set; }
+
+ ///
+ /// Determines whether authentication is disabled when starting dotnet-monitor.
+ ///
+ public bool DisableAuthentication { get; set; }
+
+ ///
+ /// Determines whether HTTP egress is disabled when starting dotnet-monitor.
+ ///
+ public bool DisableHttpEgress { get; set; }
+
+ ///
+ /// Determines whether a temporary api key should be generated while starting dotnet-monitor.
+ ///
+ public bool UseTempApiKey { get; set; }
+
+ ///
+ /// Determines whether metrics are disabled via the command line when starting dotnet-monitor.
+ ///
+ public bool DisableMetricsViaCommandLine { get; set; }
+
+
+ public MonitorCollectRunner(ITestOutputHelper outputHelper)
+ : base(outputHelper)
+ {
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ lock (_lock)
+ {
+ if (_isDiposed)
+ {
+ return;
+ }
+ _isDiposed = true;
+ }
+
+ CancelCompletionSources(CancellationToken.None);
+
+ await base.DisposeAsync();
+ }
+
+ public async Task StartAsync(CancellationToken token)
+ {
+ List argsList = new();
+
+ const string command = "collect";
+
+ argsList.Add("--urls");
+ argsList.Add("http://127.0.0.1:0");
+
+ if (DisableMetricsViaCommandLine)
+ {
+ argsList.Add("--metrics:false");
+ }
+ else
+ {
+ argsList.Add("--metricUrls");
+ argsList.Add("http://127.0.0.1:0");
+ }
+
+ if (ConnectionMode == DiagnosticPortConnectionMode.Listen)
+ {
+ argsList.Add("--diagnostic-port");
+ if (string.IsNullOrEmpty(DiagnosticPortPath))
+ {
+ throw new ArgumentNullException(nameof(DiagnosticPortPath));
+ }
+ argsList.Add(DiagnosticPortPath);
+ }
+
+ if (DisableAuthentication)
+ {
+ argsList.Add("--no-auth");
+ }
+
+ if (DisableHttpEgress)
+ {
+ argsList.Add("--no-http-egress");
+ }
+
+ if (UseTempApiKey)
+ {
+ argsList.Add("--temp-apikey");
+ }
+
+ using IDisposable _ = token.Register(() => CancelCompletionSources(token));
+
+ await base.StartAsync(command, argsList.ToArray(), token);
+
+ Task runnerExitTask = RunnerExitedTask;
+ Task endingTask = await Task.WhenAny(_readySource.Task, runnerExitTask);
+ // Await ready and exited tasks in case process exits before it is ready.
+ if (runnerExitTask == endingTask)
+ {
+ throw new InvalidOperationException("Process exited before it was ready.");
+ }
+
+ await _readySource.Task;
+ }
+
+ protected override void StandardOutputCallback(string line)
+ {
+ ConsoleLogEvent logEvent = JsonSerializer.Deserialize(line);
+
+ switch (logEvent.Category)
+ {
+ case "Microsoft.Hosting.Lifetime":
+ HandleLifetimeEvent(logEvent);
+ break;
+ case "Microsoft.Diagnostics.Tools.Monitor.Startup":
+ HandleStartupEvent(logEvent);
+ break;
+ default:
+ HandleGenericLogEvent(logEvent);
+ break;
+ }
+ }
+
+ private void CancelCompletionSources(CancellationToken token)
+ {
+ _defaultAddressSource.TrySetCanceled(token);
+ _metricsAddressSource.TrySetCanceled(token);
+ _readySource.TrySetCanceled(token);
+ _monitorApiKeySource.TrySetCanceled(token);
+ }
+
+ public Task GetDefaultAddressAsync(CancellationToken token)
+ {
+ return _defaultAddressSource.GetAsync(token);
+ }
+
+ public Task GetMetricsAddressAsync(CancellationToken token)
+ {
+ return _metricsAddressSource.GetAsync(token);
+ }
+
+ public Task GetMonitorApiKey(CancellationToken token)
+ {
+ return _monitorApiKeySource.GetAsync(token);
+ }
+
+ private void HandleLifetimeEvent(ConsoleLogEvent logEvent)
+ {
+ // Lifetime events do not have unique EventIds, thus use the format
+ // string to differentiate the individual events.
+ if (logEvent.State.TryGetValue("{OriginalFormat}", out string format))
+ {
+ switch (format)
+ {
+ case "Application started. Press Ctrl+C to shut down.":
+ Assert.True(_readySource.TrySetResult(null));
+ break;
+ }
+ }
+ }
+
+ private void HandleStartupEvent(ConsoleLogEvent logEvent)
+ {
+ switch (logEvent.EventId)
+ {
+ case 16: // Bound default address: {address}
+ if (logEvent.State.TryGetValue("address", out string defaultAddress))
+ {
+ _outputHelper.WriteLine("Default Address: {0}", defaultAddress);
+ Assert.True(_defaultAddressSource.TrySetResult(defaultAddress));
+ }
+ break;
+ case 17: // Bound metrics address: {address}
+ if (logEvent.State.TryGetValue("address", out string metricsAddress))
+ {
+ _outputHelper.WriteLine("Metrics Address: {0}", metricsAddress);
+ Assert.True(_metricsAddressSource.TrySetResult(metricsAddress));
+ }
+ break;
+ case 23:
+ if (logEvent.State.TryGetValue("MonitorApiKey", out string monitorApiKey))
+ {
+ _outputHelper.WriteLine("MonitorApiKey: {0}", monitorApiKey);
+ Assert.True(_monitorApiKeySource.TrySetResult(monitorApiKey));
+ }
+ break;
+ }
+ }
+
+ private void HandleGenericLogEvent(ConsoleLogEvent logEvent)
+ {
+ switch (logEvent.EventId)
+ {
+ // 26: NotifyPrivateKey
+ // The configuration field {fieldName} contains private key information. The private key information is not required for dotnet-monitor to verify a token signature and it is strongly recomended to only provide the public key.
+ case 26:
+ if (logEvent.State.TryGetValue("fieldName", out string fieldName))
+ {
+ _outputHelper.WriteLine("Private Key data detected in field: {0}", fieldName);
+ WarnPrivateKey?.Invoke(fieldName);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorGenerateKeyRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorGenerateKeyRunner.cs
new file mode 100644
index 00000000000..e57d6050edc
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorGenerateKeyRunner.cs
@@ -0,0 +1,233 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.TestCommon;
+using Microsoft.Diagnostics.Monitoring.TestCommon.Options;
+using Microsoft.Diagnostics.Tools.Monitor;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.Runners
+{
+ ///
+ /// Runner for the dotnet-monitor tool. This runner is for the "generatekey" command.
+ ///
+ internal sealed class MonitorGenerateKeyRunner : MonitorRunner
+ {
+ // Completion source containing the bearer token emitted by the generatekey command
+ private readonly TaskCompletionSource _bearerTokenTaskSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly Regex _bearerTokenRegex =
+ new Regex("^Authorization: Bearer (?[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+)$", RegexOptions.Compiled);
+
+ // Completion source containing the format type emitted by the generatekey command
+ private readonly TaskCompletionSource _formatHeaderSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly Regex _formatHeaderRegex =
+ new Regex("^Settings in (?[a-zA-Z0-9-_]+) format:$", RegexOptions.Compiled);
+
+ // Completion source containing the Subject field emitted by the generatekey command
+ private readonly TaskCompletionSource _subjectSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly Dictionary _subjectRegexMap =
+ new Dictionary()
+ {
+ { OutputFormat.Json, new Regex("\\\"Subject\\\":\\s*\\\"(?[0-9a-zA-Z-_!@#\\$%\\^&\\*\\(\\)\\{\\}\\[\\]|\\,\\.;:/]+)\\\"", RegexOptions.Compiled) },
+ { OutputFormat.Text, new Regex("Subject:\\s*(?[0-9a-zA-Z-_!@#\\$%\\^&\\*\\(\\)\\{\\}\\[\\]|\\,\\.;:/]+)\\Z", RegexOptions.Compiled) },
+ { OutputFormat.Cmd, new Regex("set\\s*Authentication__MonitorApiKey__Subject=(?[0-9a-zA-Z-_!@#\\$%\\^&\\*\\(\\)\\{\\}\\[\\]|\\,\\.;:/]+)\\Z", RegexOptions.Compiled) },
+ { OutputFormat.PowerShell, new Regex("\\$env\\:Authentication__MonitorApiKey__Subject\\s*=\\s*\\\"(?[0-9a-zA-Z-_!@#\\$%\\^&\\*\\(\\)\\{\\}\\[\\]|\\,\\.;:/]+)\\\"", RegexOptions.Compiled) },
+ { OutputFormat.Shell, new Regex("export\\s*Authentication__MonitorApiKey__Subject=\\\"(?[0-9a-zA-Z-_!@#\\$%\\^&\\*\\(\\)\\{\\}\\[\\]|\\,\\.;:/]+)\\\"", RegexOptions.Compiled) },
+ };
+
+ // Completion source containing the PublicKey field emitted by the generatekey command
+ private readonly TaskCompletionSource _publicKeySource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly Dictionary _publicKeyRegexMap =
+ new Dictionary()
+ {
+ { OutputFormat.Json, new Regex("\\\"PublicKey\\\":\\s*\\\"(?[a-zA-Z0-9_-]{2,}?)\\\"", RegexOptions.Compiled) },
+ { OutputFormat.Text, new Regex("Public Key:\\s(?[a-zA-Z0-9_-]{2,}?)\\Z", RegexOptions.Compiled) },
+ { OutputFormat.Cmd, new Regex("set\\s*Authentication__MonitorApiKey__PublicKey=(?[a-zA-Z0-9_-]{2,}?)\\Z", RegexOptions.Compiled) },
+ { OutputFormat.PowerShell, new Regex("\\$env\\:Authentication__MonitorApiKey__PublicKey\\s*=\\s*\\\"(?[a-zA-Z0-9_-]{2,}?)\\\"", RegexOptions.Compiled) },
+ { OutputFormat.Shell, new Regex("export\\s*Authentication__MonitorApiKey__PublicKey=\\\"(?[a-zA-Z0-9_-]{2,}?)\\\"", RegexOptions.Compiled) },
+ };
+
+ // Completion source containing the full output in the specified format (this is everything after the _formatHeaderRegex line)
+ private readonly TaskCompletionSource _outputSource =
+ new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private StringBuilder _outputBuilder = new();
+
+ ///
+ /// Gets the expected default when no --output parameter is specified at the command line.
+ ///
+ private const OutputFormat DefaultOutputFormat = OutputFormat.Json;
+ ///
+ /// A bool indicating if has been called and the field should not be updated.
+ ///
+ private bool _executionStarted = false;
+ ///
+ /// A value indicating which format is being used. This should not be updated after is called.
+ ///
+ private OutputFormat? _selectedFormat = null;
+
+ ///
+ /// Gets the that was used to execute dotnet-monitor. If is set to then
+ /// the default is returned.
+ ///
+ /// Will be thrown when has not yet been called.
+ public OutputFormat FormatUsed
+ {
+ get
+ {
+ if (!_executionStarted)
+ {
+ throw new InvalidOperationException($"Can't get {nameof(FormatUsed)} until {nameof(StartAsync)} is called.");
+ }
+
+ return _selectedFormat ?? DefaultOutputFormat;
+ }
+ }
+
+ ///
+ /// Gets or sets the to be used while executing dotnet-monitor.
+ /// This can not be set after is called.
+ ///
+ public OutputFormat? Format
+ {
+ get
+ {
+ return _selectedFormat;
+ }
+ set
+ {
+ if (_executionStarted)
+ {
+ throw new InvalidOperationException($"Can't set {nameof(Format)} after {nameof(StartAsync)} is called.");
+ }
+
+ _selectedFormat = value;
+ }
+ }
+
+ public MonitorGenerateKeyRunner(ITestOutputHelper outputHelper)
+ : base(outputHelper)
+ {
+ }
+
+ public async Task StartAsync(CancellationToken token)
+ {
+ _executionStarted = true;
+
+ List argsList = new();
+
+ const string command = "generatekey";
+
+ if (null != Format)
+ {
+ argsList.Add("--output");
+ argsList.Add(Format.ToString());
+ }
+
+ await base.StartAsync(command, argsList.ToArray(), token);
+ }
+
+ public override async Task WaitForExitAsync(CancellationToken token)
+ {
+ await base.WaitForExitAsync(token).ConfigureAwait(false);
+
+ Assert.True(_outputSource.TrySetResult(_outputBuilder.ToString()));
+
+ if (FormatUsed == OutputFormat.Json)
+ {
+ RootOptions parsedOpts = JsonSerializer.Deserialize(_outputBuilder.ToString());
+
+ Assert.True(_subjectSource.TrySetResult(parsedOpts?.Authentication?.MonitorApiKey?.Subject));
+ Assert.True(_publicKeySource.TrySetResult(parsedOpts?.Authentication?.MonitorApiKey?.PublicKey));
+ }
+ }
+
+ protected override void StandardOutputCallback(string line)
+ {
+ if (_formatHeaderSource.Task.IsCompletedSuccessfully)
+ {
+ _outputBuilder.AppendLine(line);
+ }
+
+ Match tokenMatch = _bearerTokenRegex.Match(line);
+ if (tokenMatch.Success)
+ {
+ string tokenValue = tokenMatch.Groups["token"].Value;
+ _outputHelper.WriteLine($"Found Bearer Token: {tokenValue}");
+ Assert.True(_bearerTokenTaskSource.TrySetResult(tokenValue));
+ }
+
+ Match formatMatch = _formatHeaderRegex.Match(line);
+ if (formatMatch.Success)
+ {
+ string formatValue = formatMatch.Groups["format"].Value;
+ _outputHelper.WriteLine($"Output Format: {formatValue}");
+ Assert.True(_formatHeaderSource.TrySetResult(formatValue));
+ }
+
+ Match subjectMatch = _subjectRegexMap[FormatUsed].Match(line);
+ if (subjectMatch.Success)
+ {
+ string subjectValue = subjectMatch.Groups["subject"].Value;
+ _outputHelper.WriteLine($"Subject: {subjectValue}");
+
+ // for Json we will parse the whole blob and set the value that way
+ if (FormatUsed != OutputFormat.Json)
+ {
+ Assert.True(_subjectSource.TrySetResult(subjectValue));
+ }
+ }
+
+ Match publicKeyMatch = _publicKeyRegexMap[FormatUsed].Match(line);
+ if (publicKeyMatch.Success)
+ {
+ string publicKeyValue = publicKeyMatch.Groups["publickey"].Value;
+ _outputHelper.WriteLine($"Public Key: {publicKeyValue}");
+
+ // for Json we will parse the whole blob and set the value that way
+ if (FormatUsed != OutputFormat.Json)
+ {
+ Assert.True(_publicKeySource.TrySetResult(publicKeyValue));
+ }
+ }
+ }
+
+ public Task GetBearerToken(CancellationToken token)
+ {
+ return _bearerTokenTaskSource.GetAsync(token);
+ }
+
+ public Task GetFormat(CancellationToken token)
+ {
+ return _formatHeaderSource.GetAsync(token);
+ }
+
+ public Task GetOutput(CancellationToken token)
+ {
+ return _outputSource.GetAsync(token);
+ }
+
+ public Task GetSubject(CancellationToken token)
+ {
+ return _subjectSource.GetAsync(token);
+ }
+
+ public Task GetPublicKey(CancellationToken token)
+ {
+ return _publicKeySource.GetAsync(token);
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
index 634e3bd4164..bcb8fe61cc6 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
@@ -3,19 +3,15 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.Monitoring.TestCommon;
-using Microsoft.Diagnostics.Monitoring.TestCommon.Options;
using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
-using Microsoft.Diagnostics.Monitoring.WebApi;
using Microsoft.Diagnostics.Tools.Monitor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
-using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.Runners
@@ -23,34 +19,32 @@ namespace Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.Runners
///
/// Runner for the dotnet-monitor tool.
///
- internal sealed class MonitorRunner : IAsyncDisposable
+ internal class MonitorRunner : IAsyncDisposable
{
- private readonly LoggingRunnerAdapter _adapter;
-
- // Completion source containing the bound address of the default URL (e.g. provided by --urls argument)
- private readonly TaskCompletionSource _defaultAddressSource =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
-
- // Completion source containing the bound address of the metrics URL (e.g. provided by --metricUrls argument)
- private readonly TaskCompletionSource _metricsAddressSource =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
+ protected readonly object _lock = new();
- // Completion source containing the string representing the base64 encoded MonitorApiKey for accessing the monitor (e.g. provided by --temp-apikey argument)
- private readonly TaskCompletionSource _monitorApiKeySource =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
-
- private readonly ITestOutputHelper _outputHelper;
-
- private readonly TaskCompletionSource _readySource =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
+ protected readonly ITestOutputHelper _outputHelper;
private readonly DotNetRunner _runner = new();
+ private readonly LoggingRunnerAdapter _adapter;
+
private readonly string _runnerTmpPath =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
private bool _isDisposed;
+ ///
+ /// Sets configuration values via environment variables.
+ ///
+ public RootOptions ConfigurationFromEnvironment { get; } = new();
+
+ ///
+ /// Gets the task for the underlying 's
+ /// which is used to wait for process exit.
+ ///
+ protected Task RunnerExitedTask => _runner.ExitedTask;
+
///
/// The path of the currently executing assembly.
///
@@ -72,45 +66,6 @@ internal sealed class MonitorRunner : IAsyncDisposable
.Replace(Assembly.GetExecutingAssembly().GetName().Name, "dotnet-monitor")
.Replace(CurrentTargetFrameworkFolderName, "netcoreapp3.1");
- ///
- /// Sets configuration values via environment variables.
- ///
- public RootOptions ConfigurationFromEnvironment { get; } = new();
-
- ///
- /// The mode of the diagnostic port connection. Default is
- /// (the tool is searching for apps that are in listen mode).
- ///
- ///
- /// Set to if tool needs to establish the diagnostic port listener.
- ///
- public DiagnosticPortConnectionMode ConnectionMode { get; set; } = DiagnosticPortConnectionMode.Connect;
-
- ///
- /// Path of the diagnostic port to establish when is .
- ///
- public string DiagnosticPortPath { get; set; }
-
- ///
- /// Determines whether authentication is disabled when starting dotnet-monitor.
- ///
- public bool DisableAuthentication { get; set; }
-
- ///
- /// Determines whether HTTP egress is disabled when starting dotnet-monitor.
- ///
- public bool DisableHttpEgress { get; set; }
-
- ///
- /// Determines whether a temporary api key should be generated while starting dotnet-monitor.
- ///
- public bool UseTempApiKey { get; set; }
-
- ///
- /// Determines whether metrics are disabled via the command line when starting dotnet-monitor.
- ///
- public bool DisableMetricsViaCommandLine { get; set; }
-
private string SharedConfigDirectoryPath =>
Path.Combine(_runnerTmpPath, "SharedConfig");
@@ -136,9 +91,9 @@ public MonitorRunner(ITestOutputHelper outputHelper)
Directory.CreateDirectory(UserConfigDirectoryPath);
}
- public async ValueTask DisposeAsync()
+ public virtual async ValueTask DisposeAsync()
{
- lock (_adapter)
+ lock (_lock)
{
if (_isDisposed)
{
@@ -148,11 +103,8 @@ public async ValueTask DisposeAsync()
}
_adapter.ReceivedStandardOutputLine -= StandardOutputCallback;
-
await _adapter.DisposeAsync().ConfigureAwait(false);
- CancelCompletionSources(CancellationToken.None);
-
_runner.Dispose();
try
@@ -165,48 +117,18 @@ public async ValueTask DisposeAsync()
}
}
- public async Task StartAsync(CancellationToken token)
+ public virtual async Task StartAsync(string command, string[] args, CancellationToken token)
{
List argsList = new();
- argsList.Add("collect");
-
- argsList.Add("--urls");
- argsList.Add("http://127.0.0.1:0");
-
- if (DisableMetricsViaCommandLine)
- {
- argsList.Add("--metrics:false");
- }
- else
- {
- argsList.Add("--metricUrls");
- argsList.Add("http://127.0.0.1:0");
- }
-
- if (ConnectionMode == DiagnosticPortConnectionMode.Listen)
- {
- argsList.Add("--diagnostic-port");
- if (string.IsNullOrEmpty(DiagnosticPortPath))
- {
- throw new ArgumentNullException(nameof(DiagnosticPortPath));
- }
- argsList.Add(DiagnosticPortPath);
- }
-
- if (DisableAuthentication)
- {
- argsList.Add("--no-auth");
- }
-
- if (DisableHttpEgress)
+ if (!string.IsNullOrEmpty(command))
{
- argsList.Add("--no-http-egress");
+ argsList.Add(command);
}
- if (UseTempApiKey)
+ if (args != null)
{
- argsList.Add("--temp-apikey");
+ argsList.AddRange(args);
}
_runner.EntrypointAssemblyPath = DotNetMonitorPath;
@@ -227,7 +149,7 @@ public async Task StartAsync(CancellationToken token)
_adapter.Environment.Add("DotnetMonitorTestSettings__UserConfigDirectoryOverride", UserConfigDirectoryPath);
// Set configuration via environment variables
- var configurationViaEnvironment = ConfigurationFromEnvironment.ToEnvironmentConfiguration();
+ var configurationViaEnvironment = ConfigurationFromEnvironment.ToEnvironmentConfiguration(useDotnetMonitorPrefix: true);
if (configurationViaEnvironment.Count > 0)
{
// Set additional environment variables from configuration
@@ -240,54 +162,16 @@ public async Task StartAsync(CancellationToken token)
_outputHelper.WriteLine("User Settings Path: {0}", UserSettingsFilePath);
await _adapter.StartAsync(token);
-
- using IDisposable _ = token.Register(() => CancelCompletionSources(token));
-
- // Await ready and exited tasks in case process exits before it is ready.
- if (_runner.ExitedTask == await Task.WhenAny(_readySource.Task, _runner.ExitedTask))
- {
- throw new InvalidOperationException("Process exited before it was ready.");
- }
-
- // Await ready task to check if it faulted or cancelled.
- await _readySource.Task;
}
- private void CancelCompletionSources(CancellationToken token)
+ public virtual async Task WaitForExitAsync(CancellationToken token)
{
- _defaultAddressSource.TrySetCanceled(token);
- _metricsAddressSource.TrySetCanceled(token);
- _readySource.TrySetCanceled(token);
+ await RunnerExitedTask.WithCancellation(token).ConfigureAwait(false);
+ await _adapter.ReadToEnd(token).ConfigureAwait(false);
}
- private void StandardOutputCallback(string line)
+ protected virtual void StandardOutputCallback(string line)
{
- ConsoleLogEvent logEvent = JsonSerializer.Deserialize(line);
-
- switch (logEvent.Category)
- {
- case "Microsoft.Hosting.Lifetime":
- HandleLifetimeEvent(logEvent);
- break;
- case "Microsoft.Diagnostics.Tools.Monitor.Startup":
- HandleStartupEvent(logEvent);
- break;
- }
- }
-
- public Task GetDefaultAddressAsync(CancellationToken token)
- {
- return _defaultAddressSource.GetAsync(token);
- }
-
- public Task GetMetricsAddressAsync(CancellationToken token)
- {
- return _metricsAddressSource.GetAsync(token);
- }
-
- public Task GetMonitorApiKey(CancellationToken token)
- {
- return _monitorApiKeySource.GetAsync(token);
}
public void WriteKeyPerValueConfiguration(RootOptions options)
@@ -306,15 +190,7 @@ public async Task WriteUserSettingsAsync(RootOptions options, CancellationToken
{
using FileStream stream = new(UserSettingsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
- JsonSerializerOptions serializerOptions = new()
- {
-#if NET6_0_OR_GREATER
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
-#else
- IgnoreNullValues = true
-#endif
- };
-
+ JsonSerializerOptions serializerOptions = JsonSerializerOptionsFactory.Create(JsonSerializerOptionsFactory.JsonIgnoreCondition.WhenWritingNull);
await JsonSerializer.SerializeAsync(stream, options, serializerOptions).ConfigureAwait(false);
_outputHelper.WriteLine("Wrote user settings.");
@@ -325,48 +201,5 @@ public async Task WriteUserSettingsAsync(RootOptions options, TimeSpan timeout)
using CancellationTokenSource cancellation = new(timeout);
await WriteUserSettingsAsync(options, cancellation.Token).ConfigureAwait(false);
}
-
- private void HandleLifetimeEvent(ConsoleLogEvent logEvent)
- {
- // Lifetime events do not have unique EventIds, thus use the format
- // string to differentiate the individual events.
- if (logEvent.State.TryGetValue("{OriginalFormat}", out string format))
- {
- switch (format)
- {
- case "Application started. Press Ctrl+C to shut down.":
- Assert.True(_readySource.TrySetResult(null));
- break;
- }
- }
- }
-
- private void HandleStartupEvent(ConsoleLogEvent logEvent)
- {
- switch (logEvent.EventId)
- {
- case 16: // Bound default address: {address}
- if (logEvent.State.TryGetValue("address", out string defaultAddress))
- {
- _outputHelper.WriteLine("Default Address: {0}", defaultAddress);
- Assert.True(_defaultAddressSource.TrySetResult(defaultAddress));
- }
- break;
- case 17: // Bound metrics address: {address}
- if (logEvent.State.TryGetValue("address", out string metricsAddress))
- {
- _outputHelper.WriteLine("Metrics Address: {0}", metricsAddress);
- Assert.True(_metricsAddressSource.TrySetResult(metricsAddress));
- }
- break;
- case 23:
- if (logEvent.State.TryGetValue("MonitorApiKey", out string monitorApiKey))
- {
- _outputHelper.WriteLine("MonitorApiKey: {0}", monitorApiKey);
- Assert.True(_monitorApiKeySource.TrySetResult(monitorApiKey));
- }
- break;
- }
- }
}
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunnerExtensions.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunnerExtensions.cs
index e9b0dc90637..1f0303bc2c4 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunnerExtensions.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunnerExtensions.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.Monitoring.TestCommon;
+using Microsoft.Diagnostics.Monitoring.WebApi;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -11,12 +12,12 @@
namespace Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.Runners
{
- internal static class MonitorRunnerExtensions
+ internal static class MonitorCollectRunnerExtensions
{
///
/// Creates a over the default address of the .
///
- public static Task CreateHttpClientDefaultAddressAsync(this MonitorRunner runner, IHttpClientFactory factory)
+ public static Task CreateHttpClientDefaultAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory)
{
return runner.CreateHttpClientDefaultAddressAsync(factory, Extensions.Options.Options.DefaultName, TestTimeouts.HttpApi);
}
@@ -24,7 +25,7 @@ public static Task CreateHttpClientDefaultAddressAsync(this MonitorR
///
/// Creates a named over the default address of the .
///
- public static Task CreateHttpClientDefaultAddressAsync(this MonitorRunner runner, IHttpClientFactory factory, string name)
+ public static Task CreateHttpClientDefaultAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory, string name)
{
return runner.CreateHttpClientDefaultAddressAsync(factory, name, TestTimeouts.HttpApi);
}
@@ -32,7 +33,7 @@ public static Task CreateHttpClientDefaultAddressAsync(this MonitorR
///
/// Creates a over the default address of the .
///
- public static Task CreateHttpClientDefaultAddressAsync(this MonitorRunner runner, IHttpClientFactory factory, TimeSpan timeout)
+ public static Task CreateHttpClientDefaultAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory, TimeSpan timeout)
{
return runner.CreateHttpClientDefaultAddressAsync(factory, Extensions.Options.Options.DefaultName, timeout);
}
@@ -40,7 +41,7 @@ public static Task CreateHttpClientDefaultAddressAsync(this MonitorR
///
/// Creates a named over the default address of the .
///
- public static async Task CreateHttpClientDefaultAddressAsync(this MonitorRunner runner, IHttpClientFactory factory, string name, TimeSpan timeout)
+ public static async Task CreateHttpClientDefaultAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory, string name, TimeSpan timeout)
{
HttpClient client = factory.CreateClient(name);
@@ -50,7 +51,7 @@ public static async Task CreateHttpClientDefaultAddressAsync(this Mo
if (runner.UseTempApiKey)
{
string monitorApiKey = await runner.GetMonitorApiKey(cancellation.Token);
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(AuthenticationTests.ApiKeyScheme, monitorApiKey);
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(AuthConstants.ApiKeySchema, monitorApiKey);
}
return client;
@@ -59,7 +60,7 @@ public static async Task CreateHttpClientDefaultAddressAsync(this Mo
///
/// Creates a over the metrics address of the .
///
- public static Task CreateHttpClientMetricsAddressAsync(this MonitorRunner runner, IHttpClientFactory factory)
+ public static Task CreateHttpClientMetricsAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory)
{
return runner.CreateHttpClientMetricsAddressAsync(factory, TestTimeouts.HttpApi);
}
@@ -67,7 +68,7 @@ public static Task CreateHttpClientMetricsAddressAsync(this MonitorR
///
/// Creates a over the metrics address of the .
///
- public static async Task CreateHttpClientMetricsAddressAsync(this MonitorRunner runner, IHttpClientFactory factory, TimeSpan timeout)
+ public static async Task CreateHttpClientMetricsAddressAsync(this MonitorCollectRunner runner, IHttpClientFactory factory, TimeSpan timeout)
{
HttpClient client = factory.CreateClient();
@@ -77,12 +78,12 @@ public static async Task CreateHttpClientMetricsAddressAsync(this Mo
return client;
}
- public static Task StartAsync(this MonitorRunner runner)
+ public static Task StartAsync(this MonitorCollectRunner runner)
{
return runner.StartAsync(CommonTestTimeouts.StartProcess);
}
- public static async Task StartAsync(this MonitorRunner runner, TimeSpan timeout)
+ public static async Task StartAsync(this MonitorCollectRunner runner, TimeSpan timeout)
{
using CancellationTokenSource cancellation = new(timeout);
await runner.StartAsync(cancellation.Token);
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/ScenarioRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/ScenarioRunner.cs
index 81cbed82f2e..51ee104ad7f 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/ScenarioRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/ScenarioRunner.cs
@@ -26,7 +26,7 @@ public static async Task SingleTarget(
Func appValidate,
Func postAppValidate = null,
Action configureApp = null,
- Action configureTool = null,
+ Action configureTool = null,
bool disableHttpEgress = false)
{
DiagnosticPortHelper.Generate(
@@ -34,7 +34,7 @@ public static async Task SingleTarget(
out DiagnosticPortConnectionMode appConnectionMode,
out string diagnosticPortPath);
- await using MonitorRunner toolRunner = new(outputHelper);
+ await using MonitorCollectRunner toolRunner = new(outputHelper);
toolRunner.ConnectionMode = mode;
toolRunner.DiagnosticPortPath = diagnosticPortPath;
toolRunner.DisableAuthentication = true;
diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs b/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs
deleted file mode 100644
index 1c70122a28b..00000000000
--- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationHandler.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.Diagnostics.Monitoring.WebApi;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Microsoft.Extensions.Primitives;
-using Microsoft.Net.Http.Headers;
-using System;
-using System.Buffers.Text;
-using System.Diagnostics;
-using System.Linq;
-using System.Net.Http.Headers;
-using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Monitor
-{
- ///
- /// Authenticates against the ApiKey stored on the server.
- ///
- internal sealed class ApiKeyAuthenticationHandler : AuthenticationHandler
- {
- public const int ApiKeyByteMinLength = 32;
- public const int ApiKeyByteMaxLength = 2048;
- // This is the max length to efficiently encode an ApiKey of the length ApiKeyByteMaxLength.
- private readonly int ApiKeyBase64MaxLength = Base64.GetMaxEncodedToUtf8Length(ApiKeyByteMaxLength);
-
- public ApiKeyAuthenticationHandler(
- IOptionsMonitor options,
- ILoggerFactory loggerFactory,
- UrlEncoder encoder,
- ISystemClock clock)
- : base(options, loggerFactory, encoder, clock)
- {
- }
-
- protected override Task HandleAuthenticateAsync()
- {
- ApiKeyAuthenticationOptions options = OptionsMonitor.CurrentValue;
- if (options.ValidationErrors.Any())
- {
- Logger.ApiKeyValidationFailures(options.ValidationErrors);
-
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_ApiKeyNotConfigured));
- }
-
- //We are expecting a header such as Authorization:
- //If this is not present, we will return NoResult and move on to the next authentication handler.
- if (!Request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues values) ||
- !values.Any())
- {
- return Task.FromResult(AuthenticateResult.NoResult());
- }
-
- if (!AuthenticationHeaderValue.TryParse(values.First(), out AuthenticationHeaderValue authHeader))
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidAuthHeader));
- }
-
- if (!string.Equals(authHeader.Scheme, Scheme.Name, StringComparison.OrdinalIgnoreCase))
- {
- return Task.FromResult(AuthenticateResult.NoResult());
- }
-
- if (authHeader.Parameter == null)
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidApiKeyFormat));
- }
-
- if (authHeader.Parameter.Length > ApiKeyBase64MaxLength)
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidApiKeyFormat));
- }
-
- // Calculate the max length of the base64 encoded value,
- // the rules to decode this are really complex and allow white space in the
- // string which should be ignored, simply calculate the max it can be.
- int byteLen = Base64.GetMaxDecodedFromUtf8Length(authHeader.Parameter.Length);
-
- if (byteLen < ApiKeyByteMinLength)
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidApiKeyFormat));
- }
-
- // The user is passing a base 64-encoded version of the secret
- // We will be hash this and compare it to the secret in our configuration.
- byte[] buffer = new byte[byteLen];
- Span span = new Span(buffer);
- if (!Convert.TryFromBase64String(authHeader.Parameter, span, out int bytesWritten) || bytesWritten < ApiKeyByteMinLength || bytesWritten > ApiKeyByteMaxLength)
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidApiKeyFormat));
- }
-
- Debug.Assert(null != options.HashAlgorithm);
- using HashAlgorithm algorithm = HashAlgorithm.Create(options.HashAlgorithm);
- Debug.Assert(null != algorithm);
-
- byte[] hashedSecret = algorithm.ComputeHash(buffer, 0, bytesWritten);
-
- Debug.Assert(null != options.HashValue);
- if (hashedSecret.SequenceEqual(options.HashValue))
- {
- return Task.FromResult(AuthenticateResult.Success(
- new AuthenticationTicket(
- new ClaimsPrincipal(new[] { new ClaimsIdentity(AuthConstants.ApiKeySchema) }),
- AuthConstants.ApiKeySchema)));
- }
- else
- {
- return Task.FromResult(AuthenticateResult.Fail(Strings.ErrorMessage_InvalidApiKey));
- }
- }
- }
-}
diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationPostConfigureOptions.cs b/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationPostConfigureOptions.cs
deleted file mode 100644
index 682804e28fe..00000000000
--- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationPostConfigureOptions.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Extensions.Options;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Globalization;
-using System.Linq;
-using System.Security.Cryptography;
-
-namespace Microsoft.Diagnostics.Tools.Monitor
-{
- ///
- /// Sets options on ApiKeyAuthenticationOptions based on ApiAuthenticationOptions.
- ///
- internal sealed class ApiKeyAuthenticationPostConfigureOptions :
- IPostConfigureOptions
- {
- private readonly IOptionsMonitor _apiAuthOptions;
-
- public ApiKeyAuthenticationPostConfigureOptions(
- IOptionsMonitor apiAuthOptions)
- {
- _apiAuthOptions = apiAuthOptions;
- }
-
- public void PostConfigure(string name, ApiKeyAuthenticationOptions options)
- {
- ApiAuthenticationOptions sourceOptions = _apiAuthOptions.CurrentValue;
-
- IList errors = new List();
-
- Validator.TryValidateObject(
- sourceOptions,
- new ValidationContext(sourceOptions, null, null),
- errors,
- validateAllProperties: true);
-
- // Validate hash algorithm is allowed and is supported.
- if (!string.IsNullOrEmpty(sourceOptions.ApiKeyHashType))
- {
- if (!HashAlgorithmChecker.IsAllowedAlgorithm(sourceOptions.ApiKeyHashType))
- {
- errors.Add(
- new ValidationResult(
- string.Format(
- Strings.ErrorMessage_FieldNotAllowed,
- nameof(ApiAuthenticationOptions.ApiKeyHashType),
- sourceOptions.ApiKeyHashType),
- new string[] { nameof(ApiAuthenticationOptions.ApiKeyHashType) }));
- }
- else
- {
- using HashAlgorithm algorithm = HashAlgorithm.Create(sourceOptions.ApiKeyHashType);
- if (null == algorithm)
- {
- errors.Add(
- new ValidationResult(
- string.Format(
- Strings.ErrorMessage_FieldNotAllowed,
- nameof(ApiAuthenticationOptions.ApiKeyHashType),
- sourceOptions.ApiKeyHashType),
- new string[] { nameof(ApiAuthenticationOptions.ApiKeyHashType) }));
- }
- }
- }
-
- byte[] apiKeyHashBytes = null;
- if (!string.IsNullOrEmpty(sourceOptions.ApiKeyHash))
- {
- // ApiKeyHash is represented as a hex string. e.g. AABBCCDDEEFF
- if (sourceOptions.ApiKeyHash.Length % 2 == 0)
- {
- apiKeyHashBytes = new byte[sourceOptions.ApiKeyHash.Length / 2];
- for (int i = 0; i < sourceOptions.ApiKeyHash.Length; i += 2)
- {
- if (!byte.TryParse(sourceOptions.ApiKeyHash.AsSpan(i, 2), NumberStyles.HexNumber, provider: NumberFormatInfo.InvariantInfo, result: out byte resultByte))
- {
- errors.Add(
- new ValidationResult(
- string.Format(
- Strings.ErrorMessage_FieldNotHex,
- nameof(ApiAuthenticationOptions.ApiKeyHash)),
- new string[] { nameof(ApiAuthenticationOptions.ApiKeyHash) }));
- errors.Add(new ValidationResult($"The {nameof(ApiAuthenticationOptions.ApiKeyHash)} field could not be decoded as hex string.", new string[] { nameof(ApiAuthenticationOptions.ApiKeyHash) }));
- break;
- }
- apiKeyHashBytes[i / 2] = resultByte;
- }
- }
- else
- {
- errors.Add(
- new ValidationResult(
- string.Format(
- Strings.ErrorMessage_FieldOddLengh,
- nameof(ApiAuthenticationOptions.ApiKeyHash)),
- new string[] { nameof(ApiAuthenticationOptions.ApiKeyHash) }));
- }
- }
-
- options.ValidationErrors = errors;
- if (errors.Any())
- {
- options.HashAlgorithm = null;
- options.HashValue = null;
- }
- else
- {
- options.HashAlgorithm = sourceOptions.ApiKeyHashType;
- options.HashValue = apiKeyHashBytes;
- }
- }
- }
-}
diff --git a/src/Tools/dotnet-monitor/Auth/AuthOptions.cs b/src/Tools/dotnet-monitor/Auth/AuthConfiguration.cs
similarity index 80%
rename from src/Tools/dotnet-monitor/Auth/AuthOptions.cs
rename to src/Tools/dotnet-monitor/Auth/AuthConfiguration.cs
index 9e95395f131..287fea0a5ef 100644
--- a/src/Tools/dotnet-monitor/Auth/AuthOptions.cs
+++ b/src/Tools/dotnet-monitor/Auth/AuthConfiguration.cs
@@ -8,7 +8,7 @@
namespace Microsoft.Diagnostics.Tools.Monitor
{
- internal sealed class AuthOptions : IAuthOptions
+ internal sealed class AuthConfiguration : IAuthConfiguration
{
public bool EnableNegotiate => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && KeyAuthenticationMode != KeyAuthenticationMode.NoAuth;
@@ -17,15 +17,15 @@ internal sealed class AuthOptions : IAuthOptions
public bool EnableKeyAuth => (KeyAuthenticationMode == KeyAuthenticationMode.StoredKey) ||
(KeyAuthenticationMode == KeyAuthenticationMode.TemporaryKey);
- public GeneratedApiKey TemporaryKey { get; }
+ public GeneratedJwtKey TemporaryJwtKey { get; }
- public AuthOptions(KeyAuthenticationMode mode)
+ public AuthConfiguration(KeyAuthenticationMode mode)
{
KeyAuthenticationMode = mode;
if (mode == KeyAuthenticationMode.TemporaryKey)
{
- TemporaryKey = GeneratedApiKey.Create();
+ TemporaryJwtKey = GeneratedJwtKey.Create();
}
}
}
diff --git a/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs b/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs
index c6696c3a5dc..9bce3a5c763 100644
--- a/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs
+++ b/src/Tools/dotnet-monitor/Auth/AuthorizedUserRequirement.cs
@@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Authorization;
-using System;
-using System.Collections.Generic;
-using System.Text;
+using Microsoft.Extensions.Options;
namespace Microsoft.Diagnostics.Tools.Monitor
{
diff --git a/src/Tools/dotnet-monitor/Auth/GeneratedApiKey.cs b/src/Tools/dotnet-monitor/Auth/GeneratedApiKey.cs
deleted file mode 100644
index cc484f21850..00000000000
--- a/src/Tools/dotnet-monitor/Auth/GeneratedApiKey.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace Microsoft.Diagnostics.Tools.Monitor
-{
- internal sealed class GeneratedApiKey
- {
- public const int DefaultKeyLength = 32;
- public const string DefaultHashAlgorithm = "SHA256";
-
- public readonly string MonitorApiKey;
- public readonly string HashAlgorithm;
- public readonly string HashValue;
-
- private GeneratedApiKey(string monitorApiKey, string hashAlgorithm, string hashValue)
- {
- this.MonitorApiKey = monitorApiKey;
- this.HashAlgorithm = hashAlgorithm;
- this.HashValue = hashValue;
- }
-
- public static GeneratedApiKey Create()
- {
- return GeneratedApiKey.Create(DefaultKeyLength, DefaultHashAlgorithm);
- }
-
- public static GeneratedApiKey Create(int keyLength, string hashAlgorithm)
- {
- if (!HashAlgorithmChecker.IsAllowedAlgorithm(hashAlgorithm))
- {
- throw new ArgumentOutOfRangeException(nameof(hashAlgorithm));
- }
-
- if (keyLength < ApiKeyAuthenticationHandler.ApiKeyByteMinLength || keyLength > ApiKeyAuthenticationHandler.ApiKeyByteMaxLength)
- {
- throw new ArgumentOutOfRangeException(nameof(keyLength));
- }
-
- using RandomNumberGenerator rng = RandomNumberGenerator.Create();
- using HashAlgorithm hasher = System.Security.Cryptography.HashAlgorithm.Create(hashAlgorithm);
-
- byte[] secret = new byte[keyLength];
- rng.GetBytes(secret);
-
- byte[] hash = hasher.ComputeHash(secret);
- StringBuilder outHash = new StringBuilder(hash.Length * 2);
-
- foreach (byte b in hash)
- {
- outHash.AppendFormat("{0:X2}", b);
- }
-
- string apiKey = Convert.ToBase64String(secret);
-
- GeneratedApiKey result = new GeneratedApiKey(apiKey, hashAlgorithm, outHash.ToString());
- return result;
- }
- }
-}
diff --git a/src/Tools/dotnet-monitor/Auth/GeneratedJwtKey.cs b/src/Tools/dotnet-monitor/Auth/GeneratedJwtKey.cs
new file mode 100644
index 00000000000..cd902c50b59
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/GeneratedJwtKey.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ internal sealed class GeneratedJwtKey
+ {
+ public readonly string Token;
+ public readonly string Subject;
+ public readonly string PublicKey;
+
+ private GeneratedJwtKey(string token, string subject, string publicKey)
+ {
+ Token = token;
+ Subject = subject;
+ PublicKey = publicKey;
+ }
+
+ public static GeneratedJwtKey Create()
+ {
+ Guid subjectId = Guid.NewGuid();
+ string subjectStr = subjectId.ToString("D");
+
+ ECDsa dsa = ECDsa.Create();
+ dsa.GenerateKey(ECCurve.NamedCurves.nistP384);
+ ECDsaSecurityKey secKey = new ECDsaSecurityKey(dsa);
+ SigningCredentials signingCreds = new SigningCredentials(secKey, SecurityAlgorithms.EcdsaSha384);
+ JwtHeader newHeader = new JwtHeader(signingCreds, null, JwtConstants.HeaderType);
+
+ Claim audClaim = new Claim(AuthConstants.ClaimAudienceStr, AuthConstants.ApiKeyJwtAudience);
+ Claim issClaim = new Claim(AuthConstants.ClaimIssuerStr, AuthConstants.ApiKeyJwtInternalIssuer);
+ Claim subClaim = new Claim(AuthConstants.ClaimSubjectStr, subjectStr);
+ JwtPayload newPayload = new JwtPayload(new Claim[] { audClaim, issClaim, subClaim });
+
+ JwtSecurityToken newToken = new JwtSecurityToken(newHeader, newPayload);
+
+ ECDsa pubDsa = ECDsa.Create(dsa.ExportParameters(includePrivateParameters: false));
+ ECDsaSecurityKey pubSecKey = new ECDsaSecurityKey(pubDsa);
+ JsonWebKey jwk = JsonWebKeyConverter.ConvertFromECDsaSecurityKey(pubSecKey);
+ string publicKeyJson = JsonSerializer.Serialize(jwk, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
+ string publicKeyEncoded = Base64UrlEncoder.Encode(publicKeyJson);
+
+ JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
+ string output = tokenHandler.WriteToken(newToken);
+
+ return new GeneratedJwtKey(output, subjectStr, publicKeyEncoded);
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/HashAlgorithmChecker.cs b/src/Tools/dotnet-monitor/Auth/HashAlgorithmChecker.cs
deleted file mode 100644
index fc4de152b02..00000000000
--- a/src/Tools/dotnet-monitor/Auth/HashAlgorithmChecker.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Linq;
-
-namespace Microsoft.Diagnostics.Tools.Monitor
-{
- class HashAlgorithmChecker
- {
- private static readonly string[] DisallowedHashAlgorithms = new string[]
- {
- // ------------------ SHA1 ------------------
- "SHA",
- "SHA1",
- "System.Security.Cryptography.SHA1",
- "System.Security.Cryptography.SHA1Cng",
- "System.Security.Cryptography.HashAlgorithm",
- "http://www.w3.org/2000/09/xmldsig#sha1",
- // These give a KeyedHashAlgorith based on SHA1
- "System.Security.Cryptography.HMAC",
- "System.Security.Cryptography.KeyedHashAlgorithm",
- "HMACSHA1",
- "System.Security.Cryptography.HMACSHA1",
- "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
-
- // ------------------ MD5 ------------------
- "MD5",
- "System.Security.Cryptography.MD5",
- "System.Security.Cryptography.MD5Cng",
- "http://www.w3.org/2001/04/xmldsig-more#md5",
- // These give a KeyedHashAlgorith based on MD5
- "HMACMD5",
- "System.Security.Cryptography.HMACMD5",
- "http://www.w3.org/2001/04/xmldsig-more#hmac-md5",
- };
-
- public static bool IsAllowedAlgorithm(string hashAlgorithmName)
- {
- return !DisallowedHashAlgorithms.Contains(hashAlgorithmName, StringComparer.OrdinalIgnoreCase);
- }
- }
-}
diff --git a/src/Tools/dotnet-monitor/Auth/IAuthOptions.cs b/src/Tools/dotnet-monitor/Auth/IAuthConfiguration.cs
similarity index 84%
rename from src/Tools/dotnet-monitor/Auth/IAuthOptions.cs
rename to src/Tools/dotnet-monitor/Auth/IAuthConfiguration.cs
index f2ee4b49969..0ebfc8b2ae4 100644
--- a/src/Tools/dotnet-monitor/Auth/IAuthOptions.cs
+++ b/src/Tools/dotnet-monitor/Auth/IAuthConfiguration.cs
@@ -13,10 +13,10 @@ internal enum KeyAuthenticationMode
NoAuth,
}
- internal interface IAuthOptions
+ internal interface IAuthConfiguration
{
bool EnableNegotiate { get; }
KeyAuthenticationMode KeyAuthenticationMode { get; }
- GeneratedApiKey TemporaryKey { get; }
+ GeneratedJwtKey TemporaryJwtKey { get; }
}
}
diff --git a/src/Tools/dotnet-monitor/Auth/JwtAlgorithmChecker.cs b/src/Tools/dotnet-monitor/Auth/JwtAlgorithmChecker.cs
new file mode 100644
index 00000000000..9535df877df
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/JwtAlgorithmChecker.cs
@@ -0,0 +1,63 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+#if UNITTEST
+namespace Microsoft.Diagnostics.Monitoring.TestCommon.Options
+#else
+namespace Microsoft.Diagnostics.Tools.Monitor
+#endif
+{
+ internal static class JwtAlgorithmChecker
+ {
+ ///
+ /// This is the list of allowed algorithms for JWS that are public/private key based.
+ /// We also reject RSASSA-PSS because .net core does not have support for this algorithm.
+ ///
+ ///
+ /// We enforce this list to prevent storage of private key information in configuration.
+ /// Pulled from: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
+ ///
+ private static readonly string[] AllowedJwtAlgos = new string[]
+ {
+ // ECDSA using curves P-X and SHA-X
+ SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.EcdsaSha256Signature,
+ SecurityAlgorithms.EcdsaSha384, SecurityAlgorithms.EcdsaSha384Signature,
+ SecurityAlgorithms.EcdsaSha512, SecurityAlgorithms.EcdsaSha512Signature,
+
+ // RSASSA-PKCS1-v1_5 using SHA-x
+ SecurityAlgorithms.RsaSha256, SecurityAlgorithms.RsaSha256Signature,
+ SecurityAlgorithms.RsaSha384, SecurityAlgorithms.RsaSha384Signature,
+ SecurityAlgorithms.RsaSha512, SecurityAlgorithms.RsaSha512Signature,
+ };
+
+ ///
+ /// This is the list of JSON Web Key Key Types that we support. Specifically these are the values that are
+ /// valid for the 'kty' field.
+ ///
+ ///
+ /// Again we allow all the algorithms that are public/private to prevent private key information in configuration.
+ /// Pulled from: https://datatracker.ietf.org/doc/html/rfc7518#section-7.4.2
+ ///
+ private static readonly string[] AllowedJwkKeyTypes = new string[]
+ {
+ // Elliptic curve
+ JsonWebAlgorithmsKeyTypes.EllipticCurve,
+ // RSA
+ JsonWebAlgorithmsKeyTypes.RSA
+ };
+
+ public static IReadOnlyList GetAllowedJwsAlgorithmList()
+ {
+ return new List(AllowedJwtAlgos);
+ }
+
+ public static bool IsValidJwk(JsonWebKey key)
+ {
+ return
+ !string.IsNullOrEmpty(key.Kty)
+ && AllowedJwkKeyTypes.Contains(key.Kty, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/JwtBearerChangeTokenSource.cs b/src/Tools/dotnet-monitor/Auth/JwtBearerChangeTokenSource.cs
new file mode 100644
index 00000000000..eb29ba3acd5
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/JwtBearerChangeTokenSource.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using System;
+using System.Threading;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// Notifies that JwtBearerOptions changes when MonitorApiKeyConfiguration changes.
+ ///
+ internal sealed class JwtBearerChangeTokenSource :
+ IOptionsChangeTokenSource,
+ IDisposable
+ {
+ private readonly IOptionsMonitor _optionsMonitor;
+ private readonly IDisposable _changeRegistration;
+
+ private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
+
+ public JwtBearerChangeTokenSource(
+ IOptionsMonitor optionsMonitor)
+ {
+ _optionsMonitor = optionsMonitor;
+ _changeRegistration = _optionsMonitor.OnChange(OnReload);
+ }
+
+ ///
+ /// Returns Named config instance. expects
+ /// its configuration to be named after the AuthenticationScheme it's using, not .
+ ///
+ public string Name => JwtBearerDefaults.AuthenticationScheme;
+
+ public IChangeToken GetChangeToken()
+ {
+ return _reloadToken;
+ }
+
+ public void Dispose()
+ {
+ _changeRegistration.Dispose();
+ }
+
+ private void OnReload(MonitorApiKeyConfiguration options)
+ {
+ Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()).OnReload();
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/JwtBearerPostConfigure.cs b/src/Tools/dotnet-monitor/Auth/JwtBearerPostConfigure.cs
new file mode 100644
index 00000000000..ef8a3c1372a
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/JwtBearerPostConfigure.cs
@@ -0,0 +1,61 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// Configures based on configuration.
+ ///
+ internal sealed class JwtBearerPostConfigure :
+ IPostConfigureOptions
+ {
+ private readonly IOptionsMonitor _apiKeyConfig;
+
+ public JwtBearerPostConfigure(
+ IOptionsMonitor apiKeyConfig)
+ {
+ _apiKeyConfig = apiKeyConfig;
+ }
+
+ public void PostConfigure(string name, JwtBearerOptions options)
+ {
+ MonitorApiKeyConfiguration configSnapshot = _apiKeyConfig.CurrentValue;
+ if (configSnapshot.ValidationErrors.Any())
+ {
+ options.SecurityTokenValidators.Add(new RejectAllSecurityValidator());
+ return;
+ }
+
+ TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
+ {
+ // Signing Settings
+ RequireSignedTokens = true,
+ ValidAlgorithms = JwtAlgorithmChecker.GetAllowedJwsAlgorithmList(),
+
+ // Issuer Settings
+ ValidateIssuer = false,
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKeys = new SecurityKey[] { configSnapshot.PublicKey },
+ TryAllIssuerSigningKeys = true,
+
+ // Audience Settings
+ ValidateAudience = true,
+ ValidAudiences = new string[] { AuthConstants.ApiKeyJwtAudience },
+
+ // Other Settings
+ ValidateActor = false,
+ ValidateLifetime = false,
+ };
+ options.TokenValidationParameters = tokenValidationParameters;
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/LiveJwtBearerHandler.cs b/src/Tools/dotnet-monitor/Auth/LiveJwtBearerHandler.cs
new file mode 100644
index 00000000000..ee0ec666bc0
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/LiveJwtBearerHandler.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// This is a version of that will refresh the
+ /// stored JwtBearerOptions options when the settings change.
+ ///
+ internal sealed class LiveJwtBearerHandler : JwtBearerHandler
+ {
+ public LiveJwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
+ : base(options, logger, encoder, clock)
+ {
+ options.OnChange((JwtBearerOptions opts, string name) =>
+ {
+ // This is required to get AuthenticationHandler to reload options.
+ // Once InitializeAsync is called, the value of the JwtBearerOptions is stored in Options and updates are never taken.
+ base.InitializeAsync(Scheme, Context);
+ });
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsChangeTokenSource.cs b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyChangeTokenSource.cs
similarity index 68%
rename from src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsChangeTokenSource.cs
rename to src/Tools/dotnet-monitor/Auth/MonitorApiKeyChangeTokenSource.cs
index 3766d69ec41..c01a9cffac0 100644
--- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsChangeTokenSource.cs
+++ b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyChangeTokenSource.cs
@@ -11,19 +11,19 @@
namespace Microsoft.Diagnostics.Tools.Monitor
{
///
- /// Notifies that ApiKeyAuthenticationOptions changes when ApiAuthenticationOptions changes.
+ /// Notifies that changes when changes.
///
- internal sealed class ApiKeyAuthenticationOptionsChangeTokenSource :
- IOptionsChangeTokenSource,
+ internal sealed class MonitorApiKeyChangeTokenSource :
+ IOptionsChangeTokenSource,
IDisposable
{
- private readonly IOptionsMonitor _optionsMonitor;
+ private readonly IOptionsMonitor _optionsMonitor;
private readonly IDisposable _changeRegistration;
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
- public ApiKeyAuthenticationOptionsChangeTokenSource(
- IOptionsMonitor optionsMonitor)
+ public MonitorApiKeyChangeTokenSource(
+ IOptionsMonitor optionsMonitor)
{
_optionsMonitor = optionsMonitor;
_changeRegistration = _optionsMonitor.OnChange(OnReload);
@@ -41,7 +41,7 @@ public void Dispose()
_changeRegistration.Dispose();
}
- private void OnReload(ApiAuthenticationOptions options)
+ private void OnReload(MonitorApiKeyOptions options)
{
Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()).OnReload();
}
diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptions.cs b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfiguration.cs
similarity index 51%
rename from src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptions.cs
rename to src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfiguration.cs
index e91a4324d25..79a2583be67 100644
--- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptions.cs
+++ b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfiguration.cs
@@ -3,17 +3,22 @@
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Authentication;
+using Microsoft.IdentityModel.Tokens;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.Diagnostics.Tools.Monitor
{
- internal sealed class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
+ ///
+ /// Represents internal version of .
+ /// This object contains the validation state of the object and the decoded
+ /// Json Web Key.
+ ///
+ internal class MonitorApiKeyConfiguration : AuthenticationSchemeOptions
{
- public string HashAlgorithm { get; set; }
-
- public byte[] HashValue { get; set; }
-
+ public string Subject { get; set; }
+ public SecurityKey PublicKey { get; set; }
public IEnumerable ValidationErrors { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsObserver.cs b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfigurationObserver.cs
similarity index 58%
rename from src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsObserver.cs
rename to src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfigurationObserver.cs
index eccf994dbb7..b3dc7954453 100644
--- a/src/Tools/dotnet-monitor/Auth/ApiKeyAuthenticationOptionsObserver.cs
+++ b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyConfigurationObserver.cs
@@ -12,17 +12,17 @@ namespace Microsoft.Diagnostics.Tools.Monitor
///
/// Service that monitors API Key authentication options changes and logs issues with the specified options.
///
- internal class ApiKeyAuthenticationOptionsObserver :
+ internal class MonitorApiKeyConfigurationObserver :
IDisposable
{
- private readonly ILogger _logger;
- private readonly IOptionsMonitor _options;
+ private readonly ILogger _logger;
+ private readonly IOptionsMonitor _options;
private IDisposable _changeRegistration;
- public ApiKeyAuthenticationOptionsObserver(
- ILogger logger,
- IOptionsMonitor options
+ public MonitorApiKeyConfigurationObserver(
+ ILogger logger,
+ IOptionsMonitor options
)
{
_logger = logger;
@@ -31,10 +31,10 @@ IOptionsMonitor options
public void Initialize()
{
- _changeRegistration = _options.OnChange(OnApiKeyAuthenticationOptionsChanged);
+ _changeRegistration = _options.OnChange(OnMonitorApiKeyOptionsChanged);
// Write out current validation state of options when starting the tool.
- CheckApiKeyAuthenticationOptions(_options.CurrentValue);
+ CheckMonitorApiKeyOptions(_options.CurrentValue);
}
public void Dispose()
@@ -42,20 +42,22 @@ public void Dispose()
_changeRegistration?.Dispose();
}
- private void OnApiKeyAuthenticationOptionsChanged(ApiKeyAuthenticationOptions options)
+ private void OnMonitorApiKeyOptionsChanged(MonitorApiKeyConfiguration options)
{
- _logger.ApiKeyAuthenticationOptionsChanged();
-
- CheckApiKeyAuthenticationOptions(options);
+ CheckMonitorApiKeyOptions(options);
}
- private void CheckApiKeyAuthenticationOptions(ApiKeyAuthenticationOptions options)
+ private void CheckMonitorApiKeyOptions(MonitorApiKeyConfiguration options)
{
// ValidationErrors will be null if API key authentication is not enabled.
if (null != options.ValidationErrors && options.ValidationErrors.Any())
{
_logger.ApiKeyValidationFailures(options.ValidationErrors);
}
+ else
+ {
+ _logger.ApiKeyAuthenticationOptionsValidated();
+ }
}
}
}
diff --git a/src/Tools/dotnet-monitor/Auth/MonitorApiKeyPostConfigure.cs b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyPostConfigure.cs
new file mode 100644
index 00000000000..907cb3addb6
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/MonitorApiKeyPostConfigure.cs
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// Sets options on based on .
+ /// This class is responsible for decoding the value provided in and validating it.
+ ///
+ internal sealed class MonitorApiKeyPostConfigure :
+ IPostConfigureOptions
+ {
+ private readonly ILogger _logger;
+ private readonly IOptionsMonitor _apiKeyOptions;
+
+ public MonitorApiKeyPostConfigure(
+ ILogger logger,
+ IOptionsMonitor apiKeyOptions)
+ {
+ _logger = logger;
+ _apiKeyOptions = apiKeyOptions;
+ }
+
+ public void PostConfigure(string name, MonitorApiKeyConfiguration options)
+ {
+ MonitorApiKeyOptions sourceOptions = _apiKeyOptions.CurrentValue;
+
+ IList errors = new List();
+
+ Validator.TryValidateObject(
+ sourceOptions,
+ new ValidationContext(sourceOptions, null, null),
+ errors,
+ validateAllProperties: true);
+
+ string jwkJson = null;
+ try
+ {
+ jwkJson = Base64UrlEncoder.Decode(sourceOptions.PublicKey);
+ }
+ catch (Exception)
+ {
+ errors.Add(
+ new ValidationResult(
+ string.Format(
+ Strings.ErrorMessage_NotBase64,
+ nameof(MonitorApiKeyOptions.PublicKey),
+ sourceOptions.PublicKey),
+ new string[] { nameof(MonitorApiKeyOptions.PublicKey) }));
+ }
+
+ JsonWebKey jwk = null;
+ if (!string.IsNullOrEmpty(jwkJson))
+ {
+ try
+ {
+ jwk = JsonWebKey.Create(jwkJson);
+ }
+ // JsonWebKey will throw only throw ArgumentException or a derived class.
+ catch (ArgumentException ex)
+ {
+ errors.Add(
+ new ValidationResult(
+ string.Format(
+ Strings.ErrorMessage_InvalidJwk,
+ nameof(MonitorApiKeyOptions.PublicKey),
+ sourceOptions.PublicKey,
+ ex.Message),
+ new string[] { nameof(MonitorApiKeyOptions.PublicKey) }));
+ }
+ }
+
+ if (null != jwk)
+ {
+ if(!JwtAlgorithmChecker.IsValidJwk(jwk))
+ {
+ errors.Add(
+ new ValidationResult(
+ string.Format(
+ Strings.ErrorMessage_RejectedJwk,
+ nameof(MonitorApiKeyOptions.PublicKey)),
+ new string[] { nameof(MonitorApiKeyOptions.PublicKey) }));
+ }
+ // We will let the algorithm work with private key but we should produce a warning message
+ else if (jwk.HasPrivateKey)
+ {
+ _logger.NotifyPrivateKey(nameof(MonitorApiKeyOptions.PublicKey));
+ }
+ }
+
+ options.ValidationErrors = errors;
+ if (errors.Any())
+ {
+ options.Subject = string.Empty;
+ options.PublicKey = null;
+ }
+ else
+ {
+ options.Subject = sourceOptions.Subject;
+ options.PublicKey = jwk;
+ }
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/RejectAllSecurityValidator.cs b/src/Tools/dotnet-monitor/Auth/RejectAllSecurityValidator.cs
new file mode 100644
index 00000000000..8dd75cc6be6
--- /dev/null
+++ b/src/Tools/dotnet-monitor/Auth/RejectAllSecurityValidator.cs
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// This will reject all validations and is used when configuration is invalid and
+ /// all attempts to authenticate should be rejected.
+ ///
+ internal sealed class RejectAllSecurityValidator : ISecurityTokenValidator
+ {
+ public bool CanValidateToken => true;
+
+ public int MaximumTokenSizeInBytes
+ {
+ // We need to provide a maximum token size, so we pick the same as the default used by everything derived from TokenHandler
+ get => TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
+ set => throw new NotImplementedException();
+ }
+
+ public bool CanReadToken(string securityToken) => true;
+
+ public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
+ {
+ validatedToken = null;
+ throw new InvalidOperationException(Strings.ErrorMessage_ApiKeyNotConfigured);
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs b/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs
index 2d7184752d7..d0bd9dfd25a 100644
--- a/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs
+++ b/src/Tools/dotnet-monitor/Auth/UserAuthorizationHandler.cs
@@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Diagnostics.Monitoring.WebApi;
+using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -20,12 +21,22 @@ namespace Microsoft.Diagnostics.Tools.Monitor
///
internal sealed class UserAuthorizationHandler : AuthorizationHandler
{
+ private readonly IOptionsMonitor _apiKeyConfig;
+ public UserAuthorizationHandler(IOptionsMonitor apiKeyConfig)
+ {
+ _apiKeyConfig = apiKeyConfig;
+ }
+
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizedUserRequirement requirement)
{
- // If the schema type is ApiKey, we do not need further authorization.
- if (context.User.Identity.AuthenticationType == AuthConstants.ApiKeySchema)
+ if (context.User.Identity.AuthenticationType == AuthConstants.FederationAuthType)
{
- context.Succeed(requirement);
+ // If we get a FederationAuthType (Bearer from a Jwt Token) we need to check that the user has the specified subject claim.
+ MonitorApiKeyConfiguration configSnapshot = _apiKeyConfig.CurrentValue;
+ if (context.User.HasClaim(ClaimTypes.NameIdentifier, configSnapshot.Subject))
+ {
+ context.Succeed(requirement);
+ }
}
else if ((context.User.Identity.AuthenticationType == AuthConstants.NtlmSchema) ||
(context.User.Identity.AuthenticationType == AuthConstants.KerberosSchema) ||
diff --git a/src/Tools/dotnet-monitor/CommonOptionsExtensions.cs b/src/Tools/dotnet-monitor/CommonOptionsExtensions.cs
new file mode 100644
index 00000000000..801cd6e05c8
--- /dev/null
+++ b/src/Tools/dotnet-monitor/CommonOptionsExtensions.cs
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ internal static class CommonOptionsExtensions
+ {
+ ///
+ /// Generates an environment variable map of the options.
+ ///
+ ///
+ /// Each key is the variable name; each value is the variable value.
+ ///
+ public static IDictionary ToEnvironmentConfiguration(this RootOptions options, bool useDotnetMonitorPrefix = false)
+ {
+ Dictionary variables = new(StringComparer.OrdinalIgnoreCase);
+ MapObject(options, useDotnetMonitorPrefix ? "DotNetMonitor_" : string.Empty, variables);
+ return variables;
+ }
+
+ ///
+ /// Generates a key-per-file map of the options.
+ ///
+ ///
+ /// Each key is the file name; each value is the file content.
+ ///
+ public static IDictionary ToKeyPerFileConfiguration(this RootOptions options)
+ {
+ Dictionary variables = new(StringComparer.OrdinalIgnoreCase);
+ MapObject(options, string.Empty, variables);
+ return variables;
+ }
+
+ private static void MapDictionary(IDictionary dictionary, string prefix, IDictionary map)
+ {
+ foreach (var key in dictionary.Keys)
+ {
+ object value = dictionary[key];
+ if (null != value)
+ {
+ string keyString = Convert.ToString(key, CultureInfo.InvariantCulture);
+ MapValue(
+ value,
+ FormattableString.Invariant($"{prefix}{keyString}"),
+ map);
+ }
+ }
+ }
+
+ private static void MapList(IList list, string prefix, IDictionary map)
+ {
+ for (int index = 0; index < list.Count; index++)
+ {
+ object value = list[index];
+ if (null != value)
+ {
+ MapValue(
+ value,
+ FormattableString.Invariant($"{prefix}{index}"),
+ map);
+ }
+ }
+ }
+
+ private static void MapObject(object obj, string prefix, IDictionary map)
+ {
+ foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ if (!property.GetIndexParameters().Any())
+ {
+ MapValue(
+ property.GetValue(obj),
+ FormattableString.Invariant($"{prefix}{property.Name}"),
+ map);
+ }
+ }
+ }
+
+ private static void MapValue(object value, string valueName, IDictionary map)
+ {
+ if (null != value)
+ {
+ Type valueType = value.GetType();
+ if (valueType.IsPrimitive || typeof(string) == valueType || typeof(Guid) == valueType)
+ {
+ map.Add(
+ valueName,
+ Convert.ToString(value, CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ string prefix = FormattableString.Invariant($"{valueName}__");
+ if (value is IDictionary dictionary)
+ {
+ MapDictionary(dictionary, prefix, map);
+ }
+ else if (value is IList list)
+ {
+ MapList(list, prefix, map);
+ }
+ else
+ {
+ MapObject(value, prefix, map);
+ }
+ }
+ }
+ }
+
+ private static string ToHexString(byte[] data)
+ {
+ StringBuilder builder = new(2 * data.Length);
+ foreach (byte b in data)
+ {
+ builder.Append(b.ToString("X2", CultureInfo.InvariantCulture));
+ }
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs b/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs
index 1b4c1d6fd51..ea4bf17710f 100644
--- a/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs
+++ b/src/Tools/dotnet-monitor/ConfigurationJsonWriter.cs
@@ -68,21 +68,26 @@ public void Write(IConfiguration configuration, bool full)
if (full)
{
- ProcessChildSection(configuration, ConfigurationKeys.ApiAuthentication, includeChildSections: true);
+ ProcessChildSection(configuration, ConfigurationKeys.Authentication, includeChildSections: true);
ProcessChildSection(configuration, ConfigurationKeys.Egress, includeChildSections: true);
}
else
{
- //Do not emit ApiKeyHash
- IConfigurationSection authSection = ProcessChildSection(configuration, ConfigurationKeys.ApiAuthentication, includeChildSections: false);
- if (authSection != null)
+ IConfigurationSection auth = ProcessChildSection(configuration, ConfigurationKeys.Authentication, includeChildSections: false);
+ if (null != auth)
{
_writer.WriteStartObject();
- ProcessChildSection(authSection, nameof(ApiAuthenticationOptions.ApiKeyHash), includeChildSections: false, redact: true);
- ProcessChildSection(authSection, nameof(ApiAuthenticationOptions.ApiKeyHashType), includeChildSections: false, redact: false);
- _writer.WriteEndObject();
+ IConfigurationSection monitorApiKey = ProcessChildSection(auth, ConfigurationKeys.MonitorApiKey, includeChildSections: false);
+ if (null != monitorApiKey)
+ {
+ _writer.WriteStartObject();
+ ProcessChildSection(monitorApiKey, nameof(MonitorApiKeyOptions.Subject), includeChildSections: false, redact: false);
+ // The PublicKey should only ever contain the public key, however we expect that accidents may occur and we should
+ // redact this field in the event the JWK contains the private key information.
+ ProcessChildSection(monitorApiKey, nameof(MonitorApiKeyOptions.PublicKey), includeChildSections: false, redact: true);
+ }
}
-
+
IConfigurationSection egress = ProcessChildSection(configuration, ConfigurationKeys.Egress, includeChildSections: false);
if (egress != null)
{
diff --git a/src/Tools/dotnet-monitor/ConfigurationKeys.cs b/src/Tools/dotnet-monitor/ConfigurationKeys.cs
index c052e7b0297..b9dc2b4f13c 100644
--- a/src/Tools/dotnet-monitor/ConfigurationKeys.cs
+++ b/src/Tools/dotnet-monitor/ConfigurationKeys.cs
@@ -6,18 +6,20 @@ namespace Microsoft.Diagnostics.Tools.Monitor
{
internal static class ConfigurationKeys
{
- public const string ApiAuthentication = nameof(ApiAuthentication);
+ public const string Authentication = nameof(RootOptions.Authentication);
- public const string CorsConfiguration = nameof(CorsConfiguration);
+ public const string MonitorApiKey = nameof(AuthenticationOptions.MonitorApiKey);
- public const string DiagnosticPort = nameof(DiagnosticPort);
+ public const string CorsConfiguration = nameof(RootOptions.CorsConfiguration);
- public const string Egress = nameof(Egress);
+ public const string DiagnosticPort = nameof(RootOptions.DiagnosticPort);
- public const string Metrics = nameof(Metrics);
+ public const string Egress = nameof(RootOptions.Egress);
- public const string Storage = nameof(Storage);
+ public const string Metrics = nameof(RootOptions.Metrics);
+ public const string Storage = nameof(RootOptions.Storage);
+
public const string DefaultProcess = nameof(DefaultProcess);
public const string Logging = nameof(Logging);
diff --git a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
index 861ef53ce66..373fdfb2922 100644
--- a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
+++ b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
@@ -3,12 +3,14 @@
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Diagnostics.Monitoring;
using Microsoft.Diagnostics.Monitoring.WebApi;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -128,7 +130,7 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st
IHostBuilder hostBuilder = Host.CreateDefaultBuilder();
KeyAuthenticationMode authMode = noAuth ? KeyAuthenticationMode.NoAuth : tempApiKey ? KeyAuthenticationMode.TemporaryKey : KeyAuthenticationMode.StoredKey;
- AuthOptions authenticationOptions = new AuthOptions(authMode);
+ AuthConfiguration authenticationOptions = new AuthConfiguration(authMode);
EgressOutputConfiguration egressConfiguration = new EgressOutputConfiguration(httpEgressEnabled: !noHttpEgress);
@@ -176,7 +178,7 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st
{
//TODO Many of these service additions should be done through extension methods
- services.AddSingleton(authenticationOptions);
+ services.AddSingleton(authenticationOptions);
services.AddSingleton(egressConfiguration);
@@ -185,21 +187,13 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st
// to observe other options in the future, at which point it might be good to
// refactor the options observers for each into separate implementations and are
// orchestrated by this single service.
- services.AddSingleton();
+ services.AddSingleton();
List authSchemas = null;
if (authenticationOptions.EnableKeyAuth)
{
- services.ConfigureApiKeyConfiguration(context.Configuration);
-
- //Add support for Authentication and Authorization.
- AuthenticationBuilder authBuilder = services.AddAuthentication(options =>
- {
- options.DefaultAuthenticateScheme = AuthConstants.ApiKeySchema;
- options.DefaultChallengeScheme = AuthConstants.ApiKeySchema;
- })
- .AddScheme(AuthConstants.ApiKeySchema, _ => { });
-
+ AuthenticationBuilder authBuilder = services.ConfigureMonitorApiKeyAuthentication(context.Configuration);
+
authSchemas = new List { AuthConstants.ApiKeySchema };
if (authenticationOptions.EnableNegotiate)
@@ -222,7 +216,6 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st
builder.AddRequirements(new AuthorizedUserRequirement());
builder.RequireAuthenticatedUser();
builder.AddAuthenticationSchemes(authSchemas.ToArray());
-
});
}
else
@@ -299,14 +292,14 @@ public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, st
return hostBuilder;
}
- private static void ConfigureTempApiHashKey(IConfigurationBuilder builder, AuthOptions authenticationOptions)
+ private static void ConfigureTempApiHashKey(IConfigurationBuilder builder, AuthConfiguration authenticationOptions)
{
- if (authenticationOptions.TemporaryKey != null)
+ if (authenticationOptions.TemporaryJwtKey != null)
{
builder.AddInMemoryCollection(new Dictionary
{
- { ConfigurationPath.Combine(ConfigurationKeys.ApiAuthentication, nameof(ApiAuthenticationOptions.ApiKeyHashType)), authenticationOptions.TemporaryKey.HashAlgorithm },
- { ConfigurationPath.Combine(ConfigurationKeys.ApiAuthentication, nameof(ApiAuthenticationOptions.ApiKeyHash)), authenticationOptions.TemporaryKey.HashValue },
+ { ConfigurationPath.Combine(ConfigurationKeys.Authentication, ConfigurationKeys.MonitorApiKey, nameof(MonitorApiKeyOptions.Subject)), authenticationOptions.TemporaryJwtKey.Subject },
+ { ConfigurationPath.Combine(ConfigurationKeys.Authentication, ConfigurationKeys.MonitorApiKey, nameof(MonitorApiKeyOptions.PublicKey)), authenticationOptions.TemporaryJwtKey.PublicKey },
});
}
}
diff --git a/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs b/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs
index d3a00f97566..2670ca9c182 100644
--- a/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs
+++ b/src/Tools/dotnet-monitor/GenerateApiKeyCommandHandler.cs
@@ -2,10 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using Microsoft.Net.Http.Headers;
using System;
+using System.Collections.Generic;
using System.CommandLine;
-using System.CommandLine.IO;
using System.Globalization;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
@@ -18,39 +23,76 @@ namespace Microsoft.Diagnostics.Tools.Monitor
///
internal sealed class GenerateApiKeyCommandHandler
{
- public Task GenerateApiKey(CancellationToken token, int keyLength, string hashAlgorithm, IConsole console)
+ public Task GenerateApiKey(CancellationToken token, OutputFormat output, IConsole console)
{
- if (!HashAlgorithmChecker.IsAllowedAlgorithm(hashAlgorithm))
+ GeneratedJwtKey newJwt = GeneratedJwtKey.Create();
+
+ StringBuilder outputBldr = new StringBuilder();
+
+ outputBldr.AppendLine(Strings.Message_GenerateApiKey);
+ outputBldr.AppendLine();
+ outputBldr.AppendLine(string.Format(Strings.Message_GeneratedAuthorizationHeader, HeaderNames.Authorization, AuthConstants.ApiKeySchema, newJwt.Token));
+ outputBldr.AppendLine();
+
+ RootOptions opts = new()
{
- console.Error.WriteLine(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.ErrorMessage_ParameterNotAllowed,
- nameof(hashAlgorithm),
- hashAlgorithm));
- return Task.FromResult(1);
- }
+ Authentication = new AuthenticationOptions()
+ {
+ MonitorApiKey = new MonitorApiKeyOptions()
+ {
+ Subject = newJwt.Subject,
+ PublicKey = newJwt.PublicKey,
+ }
+ }
+ };
- if (keyLength < ApiKeyAuthenticationHandler.ApiKeyByteMinLength || keyLength > ApiKeyAuthenticationHandler.ApiKeyByteMaxLength)
+ outputBldr.AppendFormat(CultureInfo.CurrentCulture, Strings.Message_SettingsDump, output);
+ outputBldr.AppendLine();
+ switch (output)
{
- console.Error.WriteLine(
- string.Format(
- CultureInfo.CurrentCulture,
- Strings.ErrorMessage_ParameterNotAllowedByteRange,
- nameof(hashAlgorithm),
- hashAlgorithm,
- ApiKeyAuthenticationHandler.ApiKeyByteMinLength,
- ApiKeyAuthenticationHandler.ApiKeyByteMaxLength));
- return Task.FromResult(1);
+ case OutputFormat.Json:
+ string optsJson = JsonSerializer.Serialize(opts, new JsonSerializerOptions()
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ WriteIndented = true
+ });
+ outputBldr.AppendLine(optsJson);
+ break;
+ case OutputFormat.Text:
+ outputBldr.AppendLine(string.Format(Strings.Message_GeneratekeySubject, newJwt.Subject));
+ outputBldr.AppendLine(string.Format(Strings.Message_GeneratekeyPublicKey, newJwt.PublicKey));
+ break;
+ case OutputFormat.Cmd:
+ case OutputFormat.PowerShell:
+ case OutputFormat.Shell:
+ IDictionary optList = opts.ToEnvironmentConfiguration();
+ foreach ((string name, string value) in optList)
+ {
+ outputBldr.AppendFormat(CultureInfo.InvariantCulture, GetFormatString(output), name, value);
+ outputBldr.AppendLine();
+ }
+ break;
}
- GeneratedApiKey newKey = GeneratedApiKey.Create(keyLength, hashAlgorithm);
-
- console.Out.WriteLine(FormattableString.Invariant($"Authorization: {Monitoring.WebApi.AuthConstants.ApiKeySchema} {newKey.MonitorApiKey}"));
- console.Out.WriteLine(FormattableString.Invariant($"ApiKeyHash: {newKey.HashValue}"));
- console.Out.WriteLine(FormattableString.Invariant($"ApiKeyHashType: {newKey.HashAlgorithm}"));
+ outputBldr.AppendLine();
+ console.Out.Write(outputBldr.ToString());
return Task.FromResult(0);
}
+
+ private string GetFormatString(OutputFormat output)
+ {
+ switch (output)
+ {
+ case OutputFormat.Cmd:
+ return "set {0}={1}";
+ case OutputFormat.PowerShell:
+ return "$env:{0}=\"{1}\"";
+ case OutputFormat.Shell:
+ return "export {0}=\"{1}\"";
+ default:
+ throw new InvalidOperationException(string.Format(Strings.ErrorMessage_UnknownFormat, output));
+ }
+ }
}
}
diff --git a/src/Tools/dotnet-monitor/LoggingExtensions.cs b/src/Tools/dotnet-monitor/LoggingExtensions.cs
index 8d4f8e9e859..afeb178f59c 100644
--- a/src/Tools/dotnet-monitor/LoggingExtensions.cs
+++ b/src/Tools/dotnet-monitor/LoggingExtensions.cs
@@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Text.Json;
namespace Microsoft.Diagnostics.Tools.Monitor
{
@@ -115,11 +116,7 @@ internal static class LoggingExtensions
logLevel: LogLevel.Warning,
formatString: Strings.LogFormatString_ApiKeyValidationFailure);
- private static readonly Action _apiKeyAuthenticationOptionsChanged =
- LoggerMessage.Define(
- eventId: new EventId(22, "ApiKeyAuthenticationOptionsChanged"),
- logLevel: LogLevel.Information,
- formatString: Strings.LogFormatString_ApiKeyAuthenticationOptionsChanged);
+ // 22:ApiKeyAuthenticationOptionsChanged
private static readonly Action _logTempKey =
LoggerMessage.Define(
@@ -133,10 +130,23 @@ internal static class LoggingExtensions
logLevel: LogLevel.Warning,
formatString: Strings.LogFormatString_DuplicateEgressProviderIgnored);
+ private static readonly Action _apiKeyAuthenticationOptionsValidated =
+ LoggerMessage.Define(
+ eventId: new EventId(25, "ApiKeyAuthenticationOptionsValidated"),
+ logLevel: LogLevel.Information,
+ formatString: Strings.LogFormatString_ApiKeyAuthenticationOptionsValidated);
+
+ private static readonly Action _notifyPrivateKey =
+ LoggerMessage.Define(
+ eventId: new EventId(26, "NotifyPrivateKey"),
+ logLevel: LogLevel.Warning,
+ formatString: Strings.LogFormatString_NotifyPrivateKey);
+
public static void EgressProviderInvalidOptions(this ILogger logger, string providerName)
{
_egressProviderInvalidOptions(logger, providerName, null);
}
+
public static void EgressCopyActionStreamToEgressStream(this ILogger logger, int bufferSize)
{
_egressCopyActionStreamToEgressStream(logger, bufferSize, null);
@@ -207,15 +217,10 @@ public static void ApiKeyValidationFailures(this ILogger logger, IEnumerable
name: "generatekey",
description: Strings.HelpDescription_CommandGenerateKey)
{
- CommandHandler.Create(new GenerateApiKeyCommandHandler().GenerateApiKey),
- HashAlgorithm(), KeyLength()
+ CommandHandler.Create(new GenerateApiKeyCommandHandler().GenerateApiKey),
+ Output()
};
private static Command CollectCommand() =>
@@ -124,20 +124,12 @@ private static Option TempApiKey() =>
Argument = new Argument(name: "tempApiKey", getDefaultValue: () => false)
};
- private static Option HashAlgorithm() =>
+ private static Option Output() =>
new Option(
- aliases: new[] { "-h", "--hash-algorithm" },
- description: Strings.HelpDescription_HashAlgorithm)
+ aliases: new[] { "-o", "--output" },
+ description: Strings.HelpDescription_OutputFormat)
{
- Argument = new Argument(name: "hashAlgorithm", getDefaultValue: () => GeneratedApiKey.DefaultHashAlgorithm)
- };
-
- private static Option KeyLength() =>
- new Option(
- aliases: new[] { "-l", "--key-length" },
- description: Strings.HelpDescription_KeyLength)
- {
- Argument = new Argument(name: "keyLength", getDefaultValue: () => GeneratedApiKey.DefaultKeyLength)
+ Argument = new Argument(name: "output", getDefaultValue: () => OutputFormat.Json)
};
private static Option ConfigLevel() =>
@@ -170,6 +162,7 @@ public static Task Main(string[] args)
return parser.InvokeAsync(args);
}
}
+
internal enum ConfigDisplayLevel
{
Redacted,
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/RootOptions.cs b/src/Tools/dotnet-monitor/RootOptions.cs
similarity index 83%
rename from src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/RootOptions.cs
rename to src/Tools/dotnet-monitor/RootOptions.cs
index e0b8482e346..5a062aca2b1 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Options/RootOptions.cs
+++ b/src/Tools/dotnet-monitor/RootOptions.cs
@@ -8,9 +8,9 @@ namespace Microsoft.Diagnostics.Tools.Monitor
{
internal partial class RootOptions
{
- public ApiAuthenticationOptions ApiAuthentication { get; set; }
+ public AuthenticationOptions Authentication { get; set; }
- public CorsConfiguration CorsConfiguration { get; set; }
+ public CorsConfigurationOptions CorsConfiguration { get; set; }
public DiagnosticPortOptions DiagnosticPort { get; set; }
diff --git a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
index d44058a28ab..eb43d122d1d 100644
--- a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
+++ b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Diagnostics.Monitoring.WebApi;
using Microsoft.Diagnostics.Tools.Monitor.Egress;
using Microsoft.Diagnostics.Tools.Monitor.Egress.AzureBlob;
@@ -22,13 +24,34 @@ public static IServiceCollection ConfigureMetrics(this IServiceCollection servic
return ConfigureOptions(services, configuration, ConfigurationKeys.Metrics);
}
- public static IServiceCollection ConfigureApiKeyConfiguration(this IServiceCollection services, IConfiguration configuration)
+ public static IServiceCollection ConfigureMonitorApiKeyOptions(this IServiceCollection services, IConfiguration configuration)
{
- return ConfigureOptions(services, configuration, ConfigurationKeys.ApiAuthentication)
- // Loads and validates ApiAuthenticationOptions into ApiKeyAuthenticationOptions
- .AddSingleton, ApiKeyAuthenticationPostConfigureOptions>()
- // Notifies that ApiKeyAuthenticationOptions is changed when ApiAuthenticationOptions is changed.
- .AddSingleton, ApiKeyAuthenticationOptionsChangeTokenSource>();
+ ConfigureOptions(services, configuration, ConfigurationKeys.MonitorApiKey);
+
+ // Loads and validates MonitorApiKeyOptions into MonitorApiKeyConfiguration
+ services.AddSingleton, MonitorApiKeyPostConfigure>();
+ // Notifies that MonitorApiKeyConfiguration is changed when MonitorApiKeyOptions is changed.
+ services.AddSingleton, MonitorApiKeyChangeTokenSource>();
+
+ return services;
+ }
+
+ public static AuthenticationBuilder ConfigureMonitorApiKeyAuthentication(this IServiceCollection services, IConfiguration configuration)
+ {
+ IConfigurationSection authSection = configuration.GetSection(ConfigurationKeys.Authentication);
+ services.ConfigureMonitorApiKeyOptions(authSection);
+
+ // Notifies that the JwtBearerOptions change when MonitorApiKeyConfiguration gets changed.
+ services.AddSingleton, JwtBearerChangeTokenSource>();
+ // Adds the JwtBearerOptions configuration source, which will provide the updated JwtBearerOptions when MonitorApiKeyConfiguration updates
+ services.TryAddEnumerable(ServiceDescriptor.Singleton, JwtBearerPostConfigure>());
+
+ // AddJwtBearer will consume the JwtBearerOptions generated by ConfigureMonitorApiKeyConfiguration
+ AuthenticationBuilder builder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
+ // We need to provide a slightly modified JwtBearerHandler to do auth, the basic JwtBearerHandler does not accept option changes after initialization
+ builder.AddScheme(JwtBearerDefaults.AuthenticationScheme, null, _ => { });
+
+ return builder;
}
public static IServiceCollection ConfigureStorage(this IServiceCollection services, IConfiguration configuration)
diff --git a/src/Tools/dotnet-monitor/Startup.cs b/src/Tools/dotnet-monitor/Startup.cs
index 0f2435bda1d..f29c5d1bfb9 100644
--- a/src/Tools/dotnet-monitor/Startup.cs
+++ b/src/Tools/dotnet-monitor/Startup.cs
@@ -99,9 +99,9 @@ public void Configure(
IApplicationBuilder app,
IHostApplicationLifetime lifetime,
IWebHostEnvironment env,
- IAuthOptions options,
+ IAuthConfiguration options,
AddressListenResults listenResults,
- ApiKeyAuthenticationOptionsObserver optionsObserver,
+ MonitorApiKeyConfigurationObserver optionsObserver,
ILogger logger)
{
// These errors are populated before Startup.Configure is called because
@@ -141,7 +141,7 @@ public void Configure(
{
if (options.KeyAuthenticationMode == KeyAuthenticationMode.TemporaryKey)
{
- logger.LogTempKey(options.TemporaryKey.MonitorApiKey);
+ logger.LogTempKey(options.TemporaryJwtKey.Token);
}
//Auth is enabled and we are binding on http. Make sure we log a warning.
@@ -181,8 +181,8 @@ public void Configure(
app.UseAuthentication();
app.UseAuthorization();
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- Configuration.Bind(nameof(CorsConfiguration), corsConfiguration);
+ CorsConfigurationOptions corsConfiguration = new CorsConfigurationOptions();
+ Configuration.Bind(ConfigurationKeys.CorsConfiguration, corsConfiguration);
if (!string.IsNullOrEmpty(corsConfiguration.AllowedOrigins))
{
app.UseCors(builder => builder.WithOrigins(corsConfiguration.GetOrigins()).AllowAnyHeader().AllowAnyMethod());
@@ -217,7 +217,7 @@ private static void LogBoundAddresses(IFeatureCollection features, AddressListen
}
}
- private static void LogElevatedPermissions(IAuthOptions options, ILogger logger)
+ private static void LogElevatedPermissions(IAuthConfiguration options, ILogger logger)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
diff --git a/src/Tools/dotnet-monitor/Strings.Designer.cs b/src/Tools/dotnet-monitor/Strings.Designer.cs
index 0a6a6ae2160..26eae97fee2 100644
--- a/src/Tools/dotnet-monitor/Strings.Designer.cs
+++ b/src/Tools/dotnet-monitor/Strings.Designer.cs
@@ -150,15 +150,6 @@ internal static string ErrorMessage_EgressUnableToCreateIntermediateFile {
}
}
- ///
- /// Looks up a localized string similar to The {0} field value '{1}' is not allowed..
- ///
- internal static string ErrorMessage_FieldNotAllowed {
- get {
- return ResourceManager.GetString("ErrorMessage_FieldNotAllowed", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to The {0} field could not be decoded as hex string..
///
@@ -204,6 +195,24 @@ internal static string ErrorMessage_InvalidAuthHeader {
}
}
+ ///
+ /// Looks up a localized string similar to The configuration parameter {0} must be contain a valid jwk, the value '{1}' could not be parsed as a Json Web Key. The expected format is a Json Web Key written as JSON which is base64Url encoded. {2}.
+ ///
+ internal static string ErrorMessage_InvalidJwk {
+ get {
+ return ResourceManager.GetString("ErrorMessage_InvalidJwk", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The configuration parameter {0} must be base64Url encoded, the value '{1}' could not be parsed as a base64Url-encoded string..
+ ///
+ internal static string ErrorMessage_NotBase64 {
+ get {
+ return ResourceManager.GetString("ErrorMessage_NotBase64", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The {0} parameter value '{1}' is not allowed..
///
@@ -222,6 +231,24 @@ internal static string ErrorMessage_ParameterNotAllowedByteRange {
}
}
+ ///
+ /// Looks up a localized string similar to The configuration parameter {0} must be contain a jwk that is valid for use with dotnet-monitor. The provided Json Web Key must be have a key-type of EC or RSA and must-not have private key information because this this key is only used for signature verification..
+ ///
+ internal static string ErrorMessage_RejectedJwk {
+ get {
+ return ResourceManager.GetString("ErrorMessage_RejectedJwk", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The {0} field or the {1} field is required..
+ ///
+ internal static string ErrorMessage_TwoFieldsMissing {
+ get {
+ return ResourceManager.GetString("ErrorMessage_TwoFieldsMissing", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unable to bind any urls..
///
@@ -240,6 +267,15 @@ internal static string ErrorMessage_UnhandledConnectionMode {
}
}
+ ///
+ /// Looks up a localized string similar to Unknown output format type: {0}.
+ ///
+ internal static string ErrorMessage_UnknownFormat {
+ get {
+ return ResourceManager.GetString("ErrorMessage_UnknownFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Monitor logs and metrics in a .NET application send the results to a chosen destination..
///
@@ -276,24 +312,6 @@ internal static string HelpDescription_CommandShow {
}
}
- ///
- /// Looks up a localized string similar to The string representing the hash algorithm used to compute ApiKeyHash store in configuration, typically SHA256..
- ///
- internal static string HelpDescription_HashAlgorithm {
- get {
- return ResourceManager.GetString("HelpDescription_HashAlgorithm", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to The length of the MonitorApiKey in bytes..
- ///
- internal static string HelpDescription_KeyLength {
- get {
- return ResourceManager.GetString("HelpDescription_KeyLength", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to The fully qualified path and filename of the diagnostic port to which runtime instances can connect..
///
@@ -367,11 +385,29 @@ internal static string HelpDescription_OptionUrls {
}
///
- /// Looks up a localized string similar to {apiAuthenticationConfigKey} settings have changed..
+ /// Looks up a localized string similar to The format to display the output in. Valid values are Json, Text, and EnvVar..
///
- internal static string LogFormatString_ApiKeyAuthenticationOptionsChanged {
+ internal static string HelpDescription_OutputFormat {
get {
- return ResourceManager.GetString("LogFormatString_ApiKeyAuthenticationOptionsChanged", resourceCulture);
+ return ResourceManager.GetString("HelpDescription_OutputFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {apiAuthenticationConfigKey} settings have changed. The new settings have passed validation..
+ ///
+ internal static string LogFormatString_ApiKeyAuthenticationOptionsValidated {
+ get {
+ return ResourceManager.GetString("LogFormatString_ApiKeyAuthenticationOptionsValidated", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The {configName} have changed, new values: Subject={subject}, PublicKey={publicKey}.
+ ///
+ internal static string LogFormatString_ApiKeyOptionsChanged {
+ get {
+ return ResourceManager.GetString("LogFormatString_ApiKeyOptionsChanged", resourceCulture);
}
}
@@ -501,6 +537,15 @@ internal static string LogFormatString_NoAuthentication {
}
}
+ ///
+ /// Looks up a localized string similar to The configuration field {fieldName} contains private key information. The private key information is not required for dotnet-monitor to verify a token signature and it is strongly recomended to only provide the public key..
+ ///
+ internal static string LogFormatString_NotifyPrivateKey {
+ get {
+ return ResourceManager.GetString("LogFormatString_NotifyPrivateKey", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to {failure}.
///
@@ -528,6 +573,51 @@ internal static string LogFormatString_UnableToListenToAddress {
}
}
+ ///
+ /// Looks up a localized string similar to Generated ApiKey for dotnet-monitor; use the following header for authorization:.
+ ///
+ internal static string Message_GenerateApiKey {
+ get {
+ return ResourceManager.GetString("Message_GenerateApiKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0}: {1} {2}.
+ ///
+ internal static string Message_GeneratedAuthorizationHeader {
+ get {
+ return ResourceManager.GetString("Message_GeneratedAuthorizationHeader", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Public Key: {0}.
+ ///
+ internal static string Message_GeneratekeyPublicKey {
+ get {
+ return ResourceManager.GetString("Message_GeneratekeyPublicKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Subject: {0}.
+ ///
+ internal static string Message_GeneratekeySubject {
+ get {
+ return ResourceManager.GetString("Message_GeneratekeySubject", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Settings in {0} format:.
+ ///
+ internal static string Message_SettingsDump {
+ get {
+ return ResourceManager.GetString("Message_SettingsDump", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to :NOT PRESENT:.
///
diff --git a/src/Tools/dotnet-monitor/Strings.resx b/src/Tools/dotnet-monitor/Strings.resx
index 56a946f8101..a6dde0b6ecb 100644
--- a/src/Tools/dotnet-monitor/Strings.resx
+++ b/src/Tools/dotnet-monitor/Strings.resx
@@ -166,13 +166,6 @@
Gets the format string for file system egress provider failure due to inability to create an intermediate file.
1 Format Parameter:
0. intermediateFileDirectory: The directory where the intermediate file was attempted to be created.
-
-
- The {0} field value '{1}' is not allowed.
- Gets the format string for rejecting a field value.
-2 Format Parameters:
-0. fieldName: The name of the field that failed validation
-1. fieldValue: The value in the field that was rejectedThe {0} field could not be decoded as hex string.
@@ -198,6 +191,21 @@
Invalid authentication header.Gets a string similar to "Invalid authentication header.".
+
+ The configuration parameter {0} must be contain a valid jwk, the value '{1}' could not be parsed as a Json Web Key. The expected format is a Json Web Key written as JSON which is base64Url encoded. {2}
+ Gets the format string for rejecting a config param because it is not a json-encoded jwk.
+3 Format Parameters:
+0. configName: The variable name for the provided string.
+1. value: The value of the string that could not be parsed.
+2. errorMessage: Additional error message text.
+
+
+ The configuration parameter {0} must be base64Url encoded, the value '{1}' could not be parsed as a base64Url-encoded string.
+ Gets the format string for rejecting a string because it is not base64url encoded.
+2 Format Parameters:
+0. stringName: The variable name for the provided string.
+1. stringValue: The value of the string that could not be parsed.
+
The {0} parameter value '{1}' is not allowed.Gets the format string for rejecting a parameter value.
@@ -213,6 +221,19 @@
1. parameterValue: The value in the parameter that was rejected
2. minBytes: The minimum number of bytes acceptable
3. maxBytes: The maximum number of bytes acceptable
+
+
+ The configuration parameter {0} must be contain a jwk that is valid for use with dotnet-monitor. The provided Json Web Key must be have a key-type of EC or RSA and must-not have private key information because this this key is only used for signature verification.
+ Gets the format string for rejecting a config param because it is not a jwk type that is accepted.
+1 Format Parameter:
+0. configName: The variable name for the provided string.
+
+
+ The {0} field or the {1} field is required.
+ Gets the format string for rejecting validation due to 2 missing fields where at least one is required.
+2 Format Parameters:
+0. fieldNameOne: The name of the first field that is missing
+1. fieldNameTwo: The name of the second field that is missingUnable to bind any urls.
@@ -223,6 +244,12 @@
Gets the format string for egress provider failure due to missing provider.
1 Format Parameter:
0. connectionMode: The provided DiagnosticPortConnectionMode that could not be parsed.
+
+
+ Unknown output format type: {0}
+ Gets the format string for an unknown output format.
+1 Format Parameter:
+0. formatType: The value of the unknown OutputFormat type.Monitor logs and metrics in a .NET application send the results to a chosen destination.
@@ -240,14 +267,6 @@
Shows configuration, as if dotnet-monitor collect was executed with these parameters.Gets the string to display in help that explains what the 'show' command does.
-
- The string representing the hash algorithm used to compute ApiKeyHash store in configuration, typically SHA256.
- Gets the string to display in help that explains what the '--hash-algorithm' option does.
-
-
- The length of the MonitorApiKey in bytes.
- Gets the string to display in help that explains what the '--key-length' option does.
-
The fully qualified path and filename of the diagnostic port to which runtime instances can connect.Gets the string to display in help that explains what the '--diagnostic-port' option does.
@@ -280,11 +299,23 @@
Bindings for the REST api.Gets the string to display in help that explains what the '--urls' option does.
-
- {apiAuthenticationConfigKey} settings have changed.
+
+ The format to display the output in. Valid values are Json, Text, and EnvVar.
+ Gets the string to display in help that explains what the '--output' option does.
+
+
+ {apiAuthenticationConfigKey} settings have changed. The new settings have passed validation.Gets the format string that is printed in the 22:ApiKeyAuthenticationOptionsChanged event.
1 Format Parameter:
1. apiAuthenticationConfigKey: The string key of ApiAuthentication object in configuration.
+
+
+ The {configName} have changed, new values: Subject={subject}, PublicKey={publicKey}
+ Gets the format string that is printed in the 21:ApiKeyValidationFailure event.
+3 Format Parameters:
+1. configName: The string key of MonitorApiKey object in configuration.
+2. subject: The value of the Subject property from the MonitorApiKeyOptions object.
+3. publicKey: The value of the PublicKey property from the MonitorApiKeyOptions object.{apiAuthenticationConfigKey} settings are invalid: {validationFailure}
@@ -376,6 +407,9 @@
Gets the format string that is printed in the 13:NoAuthentication event.
0 Format Parameters
+
+ The configuration field {fieldName} contains private key information. The private key information is not required for dotnet-monitor to verify a token signature and it is strongly recomended to only provide the public key.
+
{failure}Gets the format string that is printed in the 18:OptionsValidationFailure event.
@@ -393,6 +427,26 @@
1 Format Parameter:
1. url: The URL that could not have a listener attached to it
+
+ Generated ApiKey for dotnet-monitor; use the following header for authorization:
+ Gets a header string for the output of GenerateApiKeyCommand.
+
+
+ {0}: {1} {2}
+ Gets the string for writing the http Authorization header.
+
+
+ Public Key: {0}
+ Gets the string for displaying the PublicKey configuration value
+
+
+ Subject: {0}
+ Gets the string for displaying the Subject configuration value
+
+
+ Settings in {0} format:
+ Gets a header string for the configuration output of GenerateApiKeyCommand.
+
:NOT PRESENT:Gets a string similar to ":NOT PRESENT:".
diff --git a/src/Tools/dotnet-monitor/dotnet-monitor.csproj b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
index 04fe3e9aa47..74dda206f54 100644
--- a/src/Tools/dotnet-monitor/dotnet-monitor.csproj
+++ b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
@@ -22,10 +22,12 @@
+
+
From d251001ff9b192461a41c4753a2bc69b8a50d167 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Fri, 13 Aug 2021 13:00:52 +0000
Subject: [PATCH 09/23] Update dependencies from
https://github.com/dotnet/runtime build 20210813.5 (#717)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 3e476aeda9a..a14ad2a4890 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcore9c2b65a8f9ac334db5575160b2e07a35c25d0585
-
+ https://github.com/dotnet/runtime
- 0ea8653e1f0ada5c7a15515430c6f16585911af4
+ f66b142980b2b0df738158457458e003944dc7f6
diff --git a/eng/Versions.props b/eng/Versions.props
index 280ddcfadbc..c3537fbc6b6 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21411.15.0.0-preview.21411.1
- 6.0.0-rc.1.21411.5
+ 6.0.0-rc.1.21413.51.0.240901
From 6fed2b429b88652c795dddea2ffc4caf21dc4d08 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Fri, 13 Aug 2021 13:01:00 +0000
Subject: [PATCH 10/23] Update dependencies from
https://github.com/dotnet/aspnetcore build 20210812.9 (#718)
[main] Update dependencies from dotnet/aspnetcore
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index a14ad2a4890..eda33fb07c0 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -26,9 +26,9 @@
https://github.com/dotnet/symstored8e2990b89c53632653d7d67f3481cc72773f25c
-
+ https://github.com/dotnet/aspnetcore
- 9c2b65a8f9ac334db5575160b2e07a35c25d0585
+ 24ea7ef4b753e1d1d56d13ffa4791af242bcefd0https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index c3537fbc6b6..747ac120226 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -29,7 +29,7 @@
6.0.0-beta.21410.8
- 6.0.0-rc.1.21411.15
+ 6.0.0-rc.1.21412.95.0.0-preview.21411.15.0.0-preview.21411.1
From a364f7e68b67f3ba987aed77e54c991fb6fee793 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Fri, 13 Aug 2021 17:33:04 +0000
Subject: [PATCH 11/23] Update dependencies from
https://github.com/dotnet/diagnostics build 20210812.1 (#716)
[main] Update dependencies from dotnet/diagnostics
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index eda33fb07c0..ec37c4f52bd 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -4,13 +4,13 @@
https://github.com/dotnet/command-line-api166610c56ff732093f0145a2911d4f6c40b786da
-
+ https://github.com/dotnet/diagnostics
- 626b7f8f23053672f989a8174336897fe1b57434
+ 7ffbade45f5eba01ee700fd43f5d7e78f2ca6d5a
-
+ https://github.com/dotnet/diagnostics
- 626b7f8f23053672f989a8174336897fe1b57434
+ 7ffbade45f5eba01ee700fd43f5d7e78f2ca6d5a
diff --git a/eng/Versions.props b/eng/Versions.props
index 747ac120226..100f5f8f3b4 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,8 +31,8 @@
6.0.0-rc.1.21412.9
- 5.0.0-preview.21411.1
- 5.0.0-preview.21411.1
+ 5.0.0-preview.21412.1
+ 5.0.0-preview.21412.16.0.0-rc.1.21413.5
From ed78b4842d7b5bb794330e9a26bc1936421b202e Mon Sep 17 00:00:00 2001
From: Patrick Fenelon
Date: Fri, 13 Aug 2021 11:40:08 -0700
Subject: [PATCH 12/23] Add detailed message to output in TestSchemaBaseline
(#715)
* Add detailed message to output in TestSchemaBaseline. This shows context when the baseline differs from the generated schema.
---
.../SchemaGenerationTests.cs | 31 +++++++++++++------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
index 5d5073c19ca..32f06cbf40d 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
@@ -5,6 +5,7 @@
using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -77,31 +78,43 @@ private async Task GenerateSchema()
return new FileStream(tempSchema, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.DeleteOnClose);
}
- private async Task CompareLines(TextReader first, TextReader second)
+ private async Task CompareLines(TextReader baseline, TextReader generated)
{
- IList firstLines = await ReadAllLines(first);
- IList secondLines = await ReadAllLines(second);
+ IList baselineLines = await ReadAllLines(baseline);
+ IList generatedLines = await ReadAllLines(generated);
- for (int i = 0; i < Math.Min(firstLines.Count, secondLines.Count); i++)
+ for (int i = 0; i < Math.Min(baselineLines.Count, generatedLines.Count); i++)
{
- if (!string.Equals(firstLines[i], secondLines[i], StringComparison.Ordinal))
+ if (!string.Equals(baselineLines[i], generatedLines[i], StringComparison.Ordinal))
{
_outputHelper.WriteLine($"Differs from baseline on line {i + 1}:");
- _outputHelper.WriteLine(firstLines[i]);
- _outputHelper.WriteLine(secondLines[i]);
+ PrintSection(_outputHelper, nameof(baseline), baselineLines, i);
+ PrintSection(_outputHelper, nameof(generated), generatedLines, i);
return false;
}
}
- if (firstLines.Count != secondLines.Count)
+ if (baselineLines.Count != generatedLines.Count)
{
- _outputHelper.WriteLine($"Count differs from baseline: {firstLines.Count} {secondLines.Count}");
+ _outputHelper.WriteLine($"Count differs from baseline: {baselineLines.Count} {generatedLines.Count}");
return false;
}
return true;
}
+ private static void PrintSection(ITestOutputHelper outputHelper, string header, IList lines, int lineHighlighted, int contextQty = 7)
+ {
+ outputHelper.WriteLine($"-----{header}-----");
+ int startLine = Math.Max(0, lineHighlighted - contextQty);
+ int endLine = Math.Min(lines.Count, lineHighlighted + contextQty);
+ int formatQty = (endLine + 1).ToString("D").Length; // Get the length of the biggest number (add 1 for the 1-based index)
+ for (int i = startLine; i <= endLine; i++)
+ {
+ outputHelper.WriteLine("{0}:{1}{2}", (i+1).ToString("D" + formatQty.ToString(CultureInfo.InvariantCulture)), (i == lineHighlighted) ? " >" : " ", lines[i]);
+ }
+ }
+
private async Task> ReadAllLines(TextReader reader)
{
var lines = new List();
From d9280b3b7db5b0cffb94b0c17906ca08b1813bd0 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Sat, 14 Aug 2021 12:42:09 +0000
Subject: [PATCH 13/23] Update dependencies from
https://github.com/dotnet/arcade build 20210812.1 (#721)
[main] Update dependencies from dotnet/arcade
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 2 +-
global.json | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index ec37c4f52bd..2f2b76efb9e 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -14,13 +14,13 @@
-
+ https://github.com/dotnet/arcade
- e10772e3594e46a031574c20a4145441737ac56d
+ 58ac7035021bd7277ef7582338afd25403fc9aea
-
+ https://github.com/dotnet/arcade
- e10772e3594e46a031574c20a4145441737ac56d
+ 58ac7035021bd7277ef7582338afd25403fc9aeahttps://github.com/dotnet/symstore
diff --git a/eng/Versions.props b/eng/Versions.props
index 100f5f8f3b4..410904a1c6e 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -27,7 +27,7 @@
-->
- 6.0.0-beta.21410.8
+ 6.0.0-beta.21412.16.0.0-rc.1.21412.9
diff --git a/global.json b/global.json
index 1d09eebba7e..4571c406869 100644
--- a/global.json
+++ b/global.json
@@ -16,6 +16,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "2.0.1",
- "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21410.8"
+ "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21412.1"
}
}
From b358d9fbe03bf622e90ef80d5c755cc6b52de2bb Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Sat, 14 Aug 2021 12:58:23 +0000
Subject: [PATCH 14/23] Update dependencies from
https://github.com/dotnet/runtime build 20210814.1 (#722)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 2f2b76efb9e..e81039b3548 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcore24ea7ef4b753e1d1d56d13ffa4791af242bcefd0
-
+ https://github.com/dotnet/runtime
- f66b142980b2b0df738158457458e003944dc7f6
+ 1f26d24c0ffba88504d66736d5000e7f750f0f58
diff --git a/eng/Versions.props b/eng/Versions.props
index 410904a1c6e..8a0fa20d0be 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21412.15.0.0-preview.21412.1
- 6.0.0-rc.1.21413.5
+ 6.0.0-rc.1.21414.11.0.240901
From 203216d4be5ee3ef09303ba3940199d0e3557f51 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Sun, 15 Aug 2021 12:51:05 +0000
Subject: [PATCH 15/23] Update dependencies from
https://github.com/dotnet/runtime build 20210814.4 (#724)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index e81039b3548..c4dd058d5f8 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcore24ea7ef4b753e1d1d56d13ffa4791af242bcefd0
-
+ https://github.com/dotnet/runtime
- 1f26d24c0ffba88504d66736d5000e7f750f0f58
+ 6c39236e9f8353758b5b841c3ce171051dc71579
diff --git a/eng/Versions.props b/eng/Versions.props
index 8a0fa20d0be..d79da6c27bb 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21412.15.0.0-preview.21412.1
- 6.0.0-rc.1.21414.1
+ 6.0.0-rc.1.21414.41.0.240901
From 1430b931d9321e07b8bef77088901fcbdbef0eca Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Mon, 16 Aug 2021 12:45:43 +0000
Subject: [PATCH 16/23] Update dependencies from
https://github.com/dotnet/runtime build 20210815.6 (#725)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c4dd058d5f8..9ef1763c21c 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcore24ea7ef4b753e1d1d56d13ffa4791af242bcefd0
-
+ https://github.com/dotnet/runtime
- 6c39236e9f8353758b5b841c3ce171051dc71579
+ fde6b37e985605d862c070256de7c97e2a3f3342
diff --git a/eng/Versions.props b/eng/Versions.props
index d79da6c27bb..bb13f33184c 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21412.15.0.0-preview.21412.1
- 6.0.0-rc.1.21414.4
+ 6.0.0-rc.1.21415.61.0.240901
From 6f5de0c7e97f22cfd11edf58ecca35659e25b744 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Tue, 17 Aug 2021 00:48:41 +0000
Subject: [PATCH 17/23] Update dependencies from
https://github.com/dotnet/aspnetcore build 20210813.12 (#723)
[main] Update dependencies from dotnet/aspnetcore
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 9ef1763c21c..c94c9a460e4 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -26,9 +26,9 @@
https://github.com/dotnet/symstored8e2990b89c53632653d7d67f3481cc72773f25c
-
+ https://github.com/dotnet/aspnetcore
- 24ea7ef4b753e1d1d56d13ffa4791af242bcefd0
+ bcfbd5cc47dde7f2be50a24721f24a020dc77356https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index bb13f33184c..93912c61eb8 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -29,7 +29,7 @@
6.0.0-beta.21412.1
- 6.0.0-rc.1.21412.9
+ 6.0.0-rc.1.21413.125.0.0-preview.21412.15.0.0-preview.21412.1
From caa59a5ebde8f0cc3eb82f306a902c6f2b4ba78d Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Tue, 17 Aug 2021 12:42:20 +0000
Subject: [PATCH 18/23] Update dependencies from
https://github.com/dotnet/diagnostics build 20210813.1 (#727)
[main] Update dependencies from dotnet/diagnostics
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c94c9a460e4..038f4486a35 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -4,13 +4,13 @@
https://github.com/dotnet/command-line-api166610c56ff732093f0145a2911d4f6c40b786da
-
+ https://github.com/dotnet/diagnostics
- 7ffbade45f5eba01ee700fd43f5d7e78f2ca6d5a
+ 9ed8c2fce33fe4136d77e019e319510ba77cc4c6
-
+ https://github.com/dotnet/diagnostics
- 7ffbade45f5eba01ee700fd43f5d7e78f2ca6d5a
+ 9ed8c2fce33fe4136d77e019e319510ba77cc4c6
diff --git a/eng/Versions.props b/eng/Versions.props
index 93912c61eb8..8fd0402a77e 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,8 +31,8 @@
6.0.0-rc.1.21413.12
- 5.0.0-preview.21412.1
- 5.0.0-preview.21412.1
+ 5.0.0-preview.21413.1
+ 5.0.0-preview.21413.16.0.0-rc.1.21415.6
From 7ed86ad55e7806c2bc6eeaf920fcbc8c1dd5fadb Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Tue, 17 Aug 2021 12:52:42 +0000
Subject: [PATCH 19/23] Update dependencies from
https://github.com/dotnet/arcade build 20210813.4 (#728)
[main] Update dependencies from dotnet/arcade
---
eng/Version.Details.xml | 8 ++++----
eng/Versions.props | 2 +-
global.json | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 038f4486a35..b2f596e03ed 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -14,13 +14,13 @@
-
+ https://github.com/dotnet/arcade
- 58ac7035021bd7277ef7582338afd25403fc9aea
+ 9b7027ba718462aa6410cef61a8be5a4283e7528
-
+ https://github.com/dotnet/arcade
- 58ac7035021bd7277ef7582338afd25403fc9aea
+ 9b7027ba718462aa6410cef61a8be5a4283e7528https://github.com/dotnet/symstore
diff --git a/eng/Versions.props b/eng/Versions.props
index 8fd0402a77e..90c9b4129a3 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -27,7 +27,7 @@
-->
- 6.0.0-beta.21412.1
+ 6.0.0-beta.21413.46.0.0-rc.1.21413.12
diff --git a/global.json b/global.json
index 4571c406869..afdac051c9c 100644
--- a/global.json
+++ b/global.json
@@ -16,6 +16,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "2.0.1",
- "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21412.1"
+ "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21413.4"
}
}
From 384898ab45cefccbcb0976dd014da3ca48520ab3 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Tue, 17 Aug 2021 13:04:10 +0000
Subject: [PATCH 20/23] Update dependencies from
https://github.com/dotnet/runtime build 20210817.1 (#729)
[main] Update dependencies from dotnet/runtime
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index b2f596e03ed..e0bc438eb95 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -30,9 +30,9 @@
https://github.com/dotnet/aspnetcorebcfbd5cc47dde7f2be50a24721f24a020dc77356
-
+ https://github.com/dotnet/runtime
- fde6b37e985605d862c070256de7c97e2a3f3342
+ 14b34eb02bc8969b77c0d3a1e39fb38f450625cf
diff --git a/eng/Versions.props b/eng/Versions.props
index 90c9b4129a3..943ba02d69b 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -34,7 +34,7 @@
5.0.0-preview.21413.15.0.0-preview.21413.1
- 6.0.0-rc.1.21415.6
+ 6.0.0-rc.1.21417.11.0.240901
From 860cfcd20ec597d5c37bd567c1344a29073219d1 Mon Sep 17 00:00:00 2001
From: "dotnet-maestro[bot]"
<42748379+dotnet-maestro[bot]@users.noreply.github.com>
Date: Tue, 17 Aug 2021 15:56:26 +0000
Subject: [PATCH 21/23] Update dependencies from
https://github.com/dotnet/aspnetcore build 20210817.1 (#730)
[main] Update dependencies from dotnet/aspnetcore
---
eng/Version.Details.xml | 4 ++--
eng/Versions.props | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index e0bc438eb95..7a9846368f5 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -26,9 +26,9 @@
https://github.com/dotnet/symstored8e2990b89c53632653d7d67f3481cc72773f25c
-
+ https://github.com/dotnet/aspnetcore
- bcfbd5cc47dde7f2be50a24721f24a020dc77356
+ 0ca2ed9af69e7e334b8e3c1de1d015017f138988https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index 943ba02d69b..327bebfade5 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -29,7 +29,7 @@
6.0.0-beta.21413.4
- 6.0.0-rc.1.21413.12
+ 6.0.0-rc.1.21417.15.0.0-preview.21413.15.0.0-preview.21413.1
From 89a0e84e34201e20552829361325c32a7c277611 Mon Sep 17 00:00:00 2001
From: Justin Anderson
Date: Tue, 17 Aug 2021 11:52:43 -0700
Subject: [PATCH 22/23] Small changes to the WithCancellation extension method.
(#719)
---
.../TaskExtensions.cs | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TaskExtensions.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TaskExtensions.cs
index a928aef0a8c..9ba05e008e3 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TaskExtensions.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TaskExtensions.cs
@@ -44,7 +44,7 @@ public static async Task SafeAwait(this Task task, ITestOutputHelper outputHelpe
public static async Task WithCancellation(this Task task, CancellationToken token)
{
- CancellationTokenSource localTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
+ using CancellationTokenSource localTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
try
{
@@ -56,11 +56,8 @@ public static async Task WithCancellation(this Task task, CancellationToken toke
}
finally
{
- // If the token provided wasn't cancelled, cancel our own token
- if (!token.IsCancellationRequested)
- {
- localTokenSource.Cancel();
- }
+ // Cancel to make sure Task.Delay token registration is removed.
+ localTokenSource.Cancel();
}
}
}
From 6145472dd4b6579f952544c4c891987ec752d460 Mon Sep 17 00:00:00 2001
From: Justin Anderson
Date: Tue, 17 Aug 2021 14:30:58 -0700
Subject: [PATCH 23/23] Move *EndpointInfo implementations to dotnet-monitor.
(#726)
Move *EndpointInfo implementations to dotnet-monitor.
Add dotnet-monitor unit test assembly and move *EndpointInfo tests to it.
Consolidate calculating entry point assembly path into single utility method.
---
dotnet-monitor.sln | 7 ++
...soft.Diagnostics.Monitoring.Options.csproj | 2 +-
...osoft.Diagnostics.Monitoring.WebApi.csproj | 1 +
.../SchemaGenerationTests.cs | 3 +-
.../OpenApiGeneratorTests.cs | 3 +-
.../AssemblyHelper.cs | 31 +++++++++
.../DotNetHost.cs | 15 +++++
.../DotNetHost.cs.template | 4 ++
.../GenerateDotNetHost.targets | 3 +
.../Runners/AppRunner.cs | 13 ++--
.../Runners/DotNetRunner.cs | 11 +++-
.../TargetFrameworkMoniker.cs | 64 +++++++++++++++++++
.../Runners/MonitorRunner.cs | 21 ++----
.../EndpointInfoSourceTests.cs | 30 +++++----
...agnostics.Monitoring.Tool.UnitTests.csproj | 13 ++++
.../EndpointInfo/ClientEndpointInfoSource.cs | 3 +-
.../EndpointInfo/EndpointInfo.cs | 3 +-
.../EndpointInfo/ServerEndpointInfoSource.cs | 3 +-
18 files changed, 185 insertions(+), 45 deletions(-)
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/AssemblyHelper.cs
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TargetFrameworkMoniker.cs
rename src/Tests/{Microsoft.Diagnostics.Monitoring.WebApi.UnitTests => Microsoft.Diagnostics.Monitoring.Tool.UnitTests}/EndpointInfoSourceTests.cs (93%)
create mode 100644 src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests.csproj
rename src/{Microsoft.Diagnostics.Monitoring.WebApi => Tools/dotnet-monitor}/EndpointInfo/ClientEndpointInfoSource.cs (95%)
rename src/{Microsoft.Diagnostics.Monitoring.WebApi => Tools/dotnet-monitor}/EndpointInfo/EndpointInfo.cs (97%)
rename src/{Microsoft.Diagnostics.Monitoring.WebApi => Tools/dotnet-monitor}/EndpointInfo/ServerEndpointInfoSource.cs (99%)
diff --git a/dotnet-monitor.sln b/dotnet-monitor.sln
index e7e8bd71012..f6849d81e37 100644
--- a/dotnet-monitor.sln
+++ b/dotnet-monitor.sln
@@ -36,6 +36,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monit
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Options", "src\Microsoft.Diagnostics.Monitoring.Options\Microsoft.Diagnostics.Monitoring.Options.csproj", "{173F959B-231B-45D1-8328-9460D4C5BC71}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Diagnostics.Monitoring.Tool.UnitTests", "src\Tests\Microsoft.Diagnostics.Monitoring.Tool.UnitTests\Microsoft.Diagnostics.Monitoring.Tool.UnitTests.csproj", "{C5D14E28-DACA-4884-B513-9246B788BC22}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,6 +88,10 @@ Global
{173F959B-231B-45D1-8328-9460D4C5BC71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{173F959B-231B-45D1-8328-9460D4C5BC71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{173F959B-231B-45D1-8328-9460D4C5BC71}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5D14E28-DACA-4884-B513-9246B788BC22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5D14E28-DACA-4884-B513-9246B788BC22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5D14E28-DACA-4884-B513-9246B788BC22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5D14E28-DACA-4884-B513-9246B788BC22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -105,6 +111,7 @@ Global
{422ABBF6-6236-4042-AACA-09531DBDFBAA} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{3AD0A40B-C569-4712-9764-7A788B9CD811} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{173F959B-231B-45D1-8328-9460D4C5BC71} = {19FAB78C-3351-4911-8F0C-8C6056401740}
+ {C5D14E28-DACA-4884-B513-9246B788BC22} = {C7568468-1C79-4944-8136-18812A7F9EA7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
diff --git a/src/Microsoft.Diagnostics.Monitoring.Options/Microsoft.Diagnostics.Monitoring.Options.csproj b/src/Microsoft.Diagnostics.Monitoring.Options/Microsoft.Diagnostics.Monitoring.Options.csproj
index a369689f274..6b640ff0314 100644
--- a/src/Microsoft.Diagnostics.Monitoring.Options/Microsoft.Diagnostics.Monitoring.Options.csproj
+++ b/src/Microsoft.Diagnostics.Monitoring.Options/Microsoft.Diagnostics.Monitoring.Options.csproj
@@ -1,4 +1,4 @@
-
+netstandard2.0
diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Microsoft.Diagnostics.Monitoring.WebApi.csproj b/src/Microsoft.Diagnostics.Monitoring.WebApi/Microsoft.Diagnostics.Monitoring.WebApi.csproj
index d370f21f973..391f981ec3d 100644
--- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Microsoft.Diagnostics.Monitoring.WebApi.csproj
+++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Microsoft.Diagnostics.Monitoring.WebApi.csproj
@@ -42,6 +42,7 @@
+
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
index 32f06cbf40d..7540213983c 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.ConfigurationSchema.UnitTests/SchemaGenerationTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Diagnostics.Monitoring.TestCommon;
using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
using System;
using System.Collections.Generic;
@@ -31,7 +32,7 @@ public class SchemaGenerationTests
private const string SchemaGeneratorName = "Microsoft.Diagnostics.Monitoring.ConfigurationSchema";
private static readonly string SchemaGeneratorPath =
- CurrentExecutingAssemblyPath.Replace(Assembly.GetExecutingAssembly().GetName().Name, SchemaGeneratorName);
+ AssemblyHelper.GetAssemblyArtifactBinPath(Assembly.GetExecutingAssembly(), SchemaGeneratorName);
public SchemaGenerationTests(ITestOutputHelper outputHelper)
{
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.OpenApiGen.UnitTests/OpenApiGeneratorTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.OpenApiGen.UnitTests/OpenApiGeneratorTests.cs
index 53354591f41..6b329f727c3 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.OpenApiGen.UnitTests/OpenApiGeneratorTests.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.OpenApiGen.UnitTests/OpenApiGeneratorTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.Diagnostics.Monitoring.TestCommon;
using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
@@ -32,7 +33,7 @@ public class OpenApiGeneratorTests
Path.Combine(Path.GetDirectoryName(CurrentExecutingAssemblyPath), OpenApiBaselineName);
private static readonly string OpenApiGenPath =
- CurrentExecutingAssemblyPath.Replace(Assembly.GetExecutingAssembly().GetName().Name, OpenApiGenName);
+ AssemblyHelper.GetAssemblyArtifactBinPath(Assembly.GetExecutingAssembly(), OpenApiGenName);
private readonly ITestOutputHelper _outputHelper;
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/AssemblyHelper.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/AssemblyHelper.cs
new file mode 100644
index 00000000000..c279d938f9a
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/AssemblyHelper.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+
+namespace Microsoft.Diagnostics.Monitoring.TestCommon
+{
+ public static class AssemblyHelper
+ {
+ public static string GetAssemblyArtifactBinPath(
+ Assembly testAssembly,
+ string assemblyName,
+ TargetFrameworkMoniker tfm = TargetFrameworkMoniker.Current)
+ {
+ string assemblyPath = testAssembly.Location
+ .Replace(testAssembly.GetName().Name, assemblyName);
+
+ if (tfm != TargetFrameworkMoniker.Current)
+ {
+ string currentFolderName = DotNetHost.BuiltTargetFrameworkMoniker.ToFolderName();
+ string targetFolderName = tfm.ToFolderName();
+
+ assemblyPath = assemblyPath.Replace(currentFolderName, targetFolderName);
+ }
+
+ return assemblyPath;
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs
index 075ae74b3cb..1940bb9c9d2 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs
@@ -20,5 +20,20 @@ public partial class DotNetHost
public static string HostExePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
@"..\..\..\..\..\.dotnet\dotnet.exe" :
"../../../../../.dotnet/dotnet";
+
+ public static TargetFrameworkMoniker BuiltTargetFrameworkMoniker
+ {
+ get
+ {
+ // Update with specific TFM when building this assembly for a new target framework.
+#if NETCOREAPP3_1
+ return TargetFrameworkMoniker.NetCoreApp31;
+#elif NET5_0
+ return TargetFrameworkMoniker.Net50;
+#elif NET6_0
+ return TargetFrameworkMoniker.Net60;
+#endif
+ }
+ }
}
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs.template b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs.template
index 7567dc0123f..3266e0dd1f7 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs.template
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/DotNetHost.cs.template
@@ -10,5 +10,9 @@ namespace Microsoft.Diagnostics.Monitoring.TestCommon
{
public static readonly string CurrentAspNetCoreVersionString = "$MicrosoftAspNetCoreAppVersion$";
public static readonly string CurrentNetCoreVersionString = "$MicrosoftNetCoreAppVersion$";
+
+ public static readonly string NetCore31VersionString = "$MicrosoftNetCoreApp31Version$";
+ public static readonly string NetCore50VersionString = "$MicrosoftNetCoreApp50Version$";
+ public static readonly string NetCore60VersionString = "$MicrosoftNetCoreApp60Version$";
}
}
\ No newline at end of file
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/GenerateDotNetHost.targets b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/GenerateDotNetHost.targets
index 675e35cc6e5..1f727be2d92 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/GenerateDotNetHost.targets
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/GenerateDotNetHost.targets
@@ -21,6 +21,9 @@
$([System.IO.File]::ReadAllText('DotNetHost.cs.template'))$(TemplateContent.Replace('$MicrosoftNetCoreAppVersion$', '$(NetCoreAppVersion)'))$(TransformedContent.Replace('$MicrosoftAspNetCoreAppVersion$', '$(AspNetCoreAppVersion)'))
+ $(TransformedContent.Replace('$MicrosoftNetCoreApp31Version$', '$(MicrosoftNETCoreApp31Version)'))
+ $(TransformedContent.Replace('$MicrosoftNetCoreApp50Version$', '$(MicrosoftNETCoreApp50Version)'))
+ $(TransformedContent.Replace('$MicrosoftNetCoreApp60Version$', '$(MicrosoftNETCoreApp60Version)'))
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/AppRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/AppRunner.cs
index 49dcd2a8c14..279ab12f631 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/AppRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/AppRunner.cs
@@ -35,17 +35,10 @@ public sealed class AppRunner : IAsyncDisposable
private bool _isDiposed;
- ///
- /// The path of the current test assembly.
- ///
- private string CurrentTestAssembly => _testAssembly.Location;
-
///
/// The path to the application.
///
- private string AppPath =>
- CurrentTestAssembly
- .Replace(_testAssembly.GetName().Name, "Microsoft.Diagnostics.Monitoring.UnitTestApp");
+ private string AppPath => AssemblyHelper.GetAssemblyArtifactBinPath(_testAssembly, "Microsoft.Diagnostics.Monitoring.UnitTestApp");
///
/// The mode of the diagnostic port connection. Default is
@@ -74,13 +67,15 @@ public sealed class AppRunner : IAsyncDisposable
public int AppId { get; }
- public AppRunner(ITestOutputHelper outputHelper, Assembly testAssembly, int appId = 1)
+ public AppRunner(ITestOutputHelper outputHelper, Assembly testAssembly, int appId = 1, TargetFrameworkMoniker tfm = TargetFrameworkMoniker.Current)
{
AppId = appId;
_testAssembly = testAssembly;
_outputHelper = new PrefixedOutputHelper(outputHelper, FormattableString.Invariant($"[App{appId}] "));
+ _runner.TargetFramework = tfm;
+
_adapter = new LoggingRunnerAdapter(_outputHelper, _runner);
_adapter.ReceivedStandardOutputLine += StandardOutputCallback;
}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
index 855806001d7..2bb8e562c7a 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/Runners/DotNetRunner.cs
@@ -70,7 +70,7 @@ public sealed class DotNetRunner : IDisposable
/// Gets the process ID of the running process.
///
public int ProcessId => _process.Id;
-
+
///
/// Gets a that reads stderr.
///
@@ -86,6 +86,11 @@ public sealed class DotNetRunner : IDisposable
///
public StreamReader StandardOutput => _process.StandardOutput;
+ ///
+ /// Get or set the target framework on which the application should run.
+ ///
+ public TargetFrameworkMoniker TargetFramework { get; set; } = TargetFrameworkMoniker.Current;
+
///
/// Determines if should wait for the diagnostic pipe to be available.
///
@@ -123,10 +128,10 @@ public async Task StartAsync(CancellationToken token)
switch (FrameworkReference)
{
case DotNetFrameworkReference.Microsoft_AspNetCore_App:
- frameworkVersion = DotNetHost.CurrentAspNetCoreVersionString;
+ frameworkVersion = TargetFramework.GetAspNetCoreFrameworkVersion();
break;
case DotNetFrameworkReference.Microsoft_NetCore_App:
- frameworkVersion = DotNetHost.CurrentNetCoreVersionString;
+ frameworkVersion = TargetFramework.GetNetCoreAppFrameworkVersion();
break;
default:
throw new InvalidOperationException($"Unsupported framework reference: {FrameworkReference}");
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TargetFrameworkMoniker.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TargetFrameworkMoniker.cs
new file mode 100644
index 00000000000..54f7ca93c59
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.TestCommon/TargetFrameworkMoniker.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Diagnostics.Monitoring.TestCommon
+{
+ public enum TargetFrameworkMoniker
+ {
+ Current,
+ NetCoreApp31,
+ Net50,
+ Net60
+ }
+
+ public static class TargetFrameworkMonikerExtensions
+ {
+ public static string GetAspNetCoreFrameworkVersion(this TargetFrameworkMoniker moniker)
+ {
+ switch (moniker)
+ {
+ case TargetFrameworkMoniker.Current:
+ return DotNetHost.CurrentAspNetCoreVersionString;
+ }
+ throw CreateUnsupportedException(moniker);
+ }
+
+ public static string GetNetCoreAppFrameworkVersion(this TargetFrameworkMoniker moniker)
+ {
+ switch (moniker)
+ {
+ case TargetFrameworkMoniker.Current:
+ return DotNetHost.CurrentNetCoreVersionString;
+ case TargetFrameworkMoniker.NetCoreApp31:
+ return DotNetHost.NetCore31VersionString;
+ case TargetFrameworkMoniker.Net50:
+ return DotNetHost.NetCore50VersionString;
+ case TargetFrameworkMoniker.Net60:
+ return DotNetHost.NetCore60VersionString;
+ }
+ throw CreateUnsupportedException(moniker);
+ }
+
+ public static string ToFolderName(this TargetFrameworkMoniker moniker)
+ {
+ switch (moniker)
+ {
+ case TargetFrameworkMoniker.Net50:
+ return "net5.0";
+ case TargetFrameworkMoniker.NetCoreApp31:
+ return "netcoreapp3.1";
+ case TargetFrameworkMoniker.Net60:
+ return "net6.0";
+ }
+ throw CreateUnsupportedException(moniker);
+ }
+
+ private static ArgumentException CreateUnsupportedException(TargetFrameworkMoniker moniker)
+ {
+ return new ArgumentException($"Unsupported target framework moniker: {moniker:G}");
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
index bcb8fe61cc6..564ddf19bdf 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
@@ -46,25 +46,14 @@ internal class MonitorRunner : IAsyncDisposable
protected Task RunnerExitedTask => _runner.ExitedTask;
///
- /// The path of the currently executing assembly.
- ///
- private static string CurrentExecutingAssemblyPath =>
- Assembly.GetExecutingAssembly().Location;
-
- ///
- /// The target framework name of the currently executing assembly.
- ///
- private static string CurrentTargetFrameworkFolderName =>
- new FileInfo(CurrentExecutingAssemblyPath).Directory.Name;
-
- ///
- /// The path to dotnet-monitor. It is currently only build for the
+ /// The path to dotnet-monitor. It is currently only built for the
/// netcoreapp3.1 target framework.
///
private static string DotNetMonitorPath =>
- CurrentExecutingAssemblyPath
- .Replace(Assembly.GetExecutingAssembly().GetName().Name, "dotnet-monitor")
- .Replace(CurrentTargetFrameworkFolderName, "netcoreapp3.1");
+ AssemblyHelper.GetAssemblyArtifactBinPath(
+ Assembly.GetExecutingAssembly(),
+ "dotnet-monitor",
+ TargetFrameworkMoniker.NetCoreApp31);
private string SharedConfigDirectoryPath =>
Path.Combine(_runnerTmpPath, "SharedConfig");
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.WebApi.UnitTests/EndpointInfoSourceTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EndpointInfoSourceTests.cs
similarity index 93%
rename from src/Tests/Microsoft.Diagnostics.Monitoring.WebApi.UnitTests/EndpointInfoSourceTests.cs
rename to src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EndpointInfoSourceTests.cs
index ea82bc88d65..ff08632e164 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.WebApi.UnitTests/EndpointInfoSourceTests.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EndpointInfoSourceTests.cs
@@ -4,6 +4,8 @@
using Microsoft.Diagnostics.Monitoring.TestCommon;
using Microsoft.Diagnostics.Monitoring.TestCommon.Runners;
+using Microsoft.Diagnostics.Monitoring.WebApi;
+using Microsoft.Diagnostics.Tools.Monitor;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,9 +15,8 @@
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.Diagnostics.Monitoring.WebApi.UnitTests
+namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests
{
-#if NET5_0_OR_GREATER
public class EndpointInfoSourceTests
{
private static readonly TimeSpan DefaultNegativeVerificationTimeout = TimeSpan.FromSeconds(2);
@@ -104,8 +105,9 @@ public async Task ServerSourceThrowsWhenMultipleStartTest()
/// Tests that the server endpoint info source can properly enumerate endpoint infos when a single
/// target connects to it and "disconnects" from it.
///
- [Fact]
- public async Task ServerSourceAddRemoveSingleConnectionTest()
+ [Theory]
+ [MemberData(nameof(GetTfmsSupportingPortListener))]
+ public async Task ServerSourceAddRemoveSingleConnectionTest(TargetFrameworkMoniker appTfm)
{
await using var source = CreateServerSource(out string transportName);
source.Start();
@@ -113,7 +115,7 @@ public async Task ServerSourceAddRemoveSingleConnectionTest()
var endpointInfos = await GetEndpointInfoAsync(source);
Assert.Empty(endpointInfos);
- AppRunner runner = CreateAppRunner(transportName);
+ AppRunner runner = CreateAppRunner(transportName, appTfm);
Task newEndpointInfoTask = source.WaitForNewEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);
@@ -143,8 +145,9 @@ await runner.ExecuteAsync(async () =>
/// Tests that the server endpoint info source can properly enumerate endpoint infos when multiple
/// targets connect to it and "disconnect" from it.
///
- [Fact]
- public async Task ServerSourceAddRemoveMultipleConnectionTest()
+ [Theory]
+ [MemberData(nameof(GetTfmsSupportingPortListener))]
+ public async Task ServerSourceAddRemoveMultipleConnectionTest(TargetFrameworkMoniker appTfm)
{
await using var source = CreateServerSource(out string transportName);
source.Start();
@@ -159,7 +162,7 @@ public async Task ServerSourceAddRemoveMultipleConnectionTest()
// Start all app instances
for (int i = 0; i < appCount; i++)
{
- runners[i] = CreateAppRunner(transportName, appId: i + 1);
+ runners[i] = CreateAppRunner(transportName, appTfm, appId: i + 1);
newEndpointInfoTasks[i] = source.WaitForNewEndpointInfoAsync(runners[i], CommonTestTimeouts.StartProcess);
}
@@ -199,6 +202,12 @@ await runners.ExecuteAsync(async () =>
Assert.Empty(endpointInfos);
}
+ public static IEnumerable