Skip to content

Commit 3fdbcec

Browse files
Hub joining: implement additional joining token for authenticated/non authenticated
1 parent e6d8e74 commit 3fdbcec

File tree

12 files changed

+139
-33
lines changed

12 files changed

+139
-33
lines changed

src/Certify.Client/ManagementServerClient.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ public class ManagementServerClient : IManagementServerClient
2828
private string _hubUri = "";
2929

3030
private ManagedInstanceInfo _instanceInfo;
31-
private ClientSecret _clientSecret;
31+
private string _joiningToken = default;
3232

33-
public ManagementServerClient(string hubUri, ClientSecret clientSecret, ManagedInstanceInfo instanceInfo)
33+
public ManagementServerClient(string hubUri, ManagedInstanceInfo instanceInfo)
3434
{
3535
_hubUri = $"{hubUri}";
3636
_instanceInfo = instanceInfo;
37-
_clientSecret = clientSecret;
37+
}
38+
39+
public void SetJoiningToken(string joiningToken)
40+
{
41+
_joiningToken = joiningToken;
3842
}
3943

4044
private void Log(string msg)
@@ -76,6 +80,7 @@ public async Task ConnectAsync()
7680
};
7781

7882
opts.UseStatefulReconnect = true;
83+
opts.AccessTokenProvider = () => Task.FromResult(_joiningToken ?? "");
7984

8085
})
8186
.WithAutomaticReconnect()
@@ -91,6 +96,7 @@ public async Task ConnectAsync()
9196

9297
_connection.Closed += async (error) =>
9398
{
99+
// if we are disconnected, wait a random amount of time and try to reconnect
94100
await Task.Delay(new Random().Next(0, 5) * 1000);
95101
await _connection.StartAsync();
96102
};

src/Certify.Core/Certify.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<NoWarn>1701;1702;CA1068</NoWarn>
4545
</PropertyGroup>
4646
<ItemGroup>
47+
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.7.0" />
4748
<PackageReference Include="Polly" Version="8.5.2" />
4849
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
4950
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />

src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Text.Json;
@@ -11,6 +11,7 @@
1111
using Certify.Models.Hub;
1212
using Certify.Shared;
1313
using Certify.Shared.Core.Utils;
14+
using Microsoft.IdentityModel.JsonWebTokens;
1415

1516
namespace Certify.Management
1617
{
@@ -21,6 +22,7 @@ public partial class CertifyManager
2122
private bool _isHubConnectionErrorLogged = false;
2223
private ClientSecret _mgmtHubJoiningSecret;
2324
private const string _mgmtHubJoiningCredId = "_ManagementHubJoiningKey";
25+
private string _mgmtHubJoiningToken = default!;
2426

2527
public async Task<ActionResult> CheckManagementHubConnectionStatus()
2628
{
@@ -40,6 +42,8 @@ public async Task<ActionResult> JoinManagementHub(string url, ClientSecret clien
4042

4143
if (check.IsSuccess)
4244
{
45+
_mgmtHubJoiningToken = check.Result.JoiningToken;
46+
4347
_serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig();
4448
var hubEndpoint = check.Result.HubEndpoint;
4549

@@ -119,32 +123,84 @@ public void SetDirectManagementClient(IManagementServerClient client)
119123
_managementServerClient = client;
120124
}
121125

126+
private JsonWebTokenHandler _joiningTokenHandler = new JsonWebTokenHandler();
122127
private async Task EnsureMgmtHubConnection()
123128
{
129+
// check we have a current non-expired joining token
130+
if (!string.IsNullOrWhiteSpace(_mgmtHubJoiningToken))
131+
{
132+
// check jwt has not expired
133+
134+
var validation = await _joiningTokenHandler.ValidateTokenAsync(_mgmtHubJoiningToken, new Microsoft.IdentityModel.Tokens.TokenValidationParameters
135+
{
136+
ValidateLifetime = true,
137+
ValidateAudience = false,
138+
ValidateIssuer = false,
139+
ValidateIssuerSigningKey = false
140+
});
141+
142+
if (!validation.IsValid)
143+
{
144+
// token has expired, will need a new one
145+
_mgmtHubJoiningToken = null;
146+
}
147+
}
148+
124149
// connect/reconnect to management hub if enabled
125150
if (_managementServerClient == null || !_managementServerClient.IsConnected())
126151
{
127152
var mgmtHubUri = string.Empty;
153+
var api = string.Empty;
154+
var endpoint = string.Empty;
155+
var defaultEnpoint = "api/internal/managementhub";
128156

129157
// construct hub api url and status hub api endpoint
130158
if (Environment.GetEnvironmentVariable("CERTIFY_MANAGEMENT_HUB") != null)
131159
{
132-
var api = Environment.GetEnvironmentVariable("CERTIFY_MANAGEMENT_HUB");
160+
api = Environment.GetEnvironmentVariable("CERTIFY_MANAGEMENT_HUB");
133161

134-
if (api.EndsWith("api/internal/managementhub"))
162+
if (api.EndsWith(defaultEnpoint))
135163
{
136164
mgmtHubUri = api;
165+
166+
endpoint = defaultEnpoint;
167+
api = api.Replace(defaultEnpoint, "");
137168
}
138169
else
139170
{
140-
var endpoint = Environment.GetEnvironmentVariable("CERTIFY_MANAGEMENT_HUB_ENDPOINT") ?? "api/internal/managementhub";
171+
endpoint = Environment.GetEnvironmentVariable("CERTIFY_MANAGEMENT_HUB_ENDPOINT") ?? defaultEnpoint;
141172
mgmtHubUri = $"{api.Trim('/')}/{endpoint.Trim('/')}";
142173
}
143174
}
144175
else
145176
{
146-
mgmtHubUri = $"{_serverConfig.ManagementServerHubAPI.Trim('/')}/{_serverConfig.ManagementServerHubEndpoint.Trim('/')}";
177+
api = _serverConfig.ManagementServerHubAPI.Trim('/');
178+
endpoint = _serverConfig.ManagementServerHubEndpoint.Trim('/');
179+
mgmtHubUri = $"{api}/{endpoint}";
180+
}
181+
182+
if (string.IsNullOrWhiteSpace(_mgmtHubJoiningToken))
183+
{
184+
if (_mgmtHubJoiningSecret == null)
185+
{
186+
var secret = await _credentialsManager.GetUnlockedCredential(_mgmtHubJoiningCredId);
187+
if (secret != null)
188+
{
189+
_mgmtHubJoiningSecret = JsonSerializer.Deserialize<ClientSecret>(secret, JsonOptions.DefaultJsonSerializerOptions);
190+
}
191+
}
147192

193+
// acquire new token
194+
var check = await CheckManagementHubCredentials(api, _mgmtHubJoiningSecret);
195+
if (check.IsSuccess)
196+
{
197+
_mgmtHubJoiningToken = check.Result.JoiningToken;
198+
}
199+
else
200+
{
201+
_serviceLog.Error($"Failed to acquire new hub joining token using current joining key: {check.Message}");
202+
return;
203+
}
148204
}
149205

150206
if (!string.IsNullOrWhiteSpace(mgmtHubUri))
@@ -192,16 +248,8 @@ private async Task StartManagementHubConnection(string hubUri)
192248
_managementServerClient.OnConnectionReconnecting -= _managementServerClient_OnConnectionReconnecting;
193249
}
194250

195-
if (_mgmtHubJoiningSecret == null)
196-
{
197-
var secret = await _credentialsManager.GetUnlockedCredential(_mgmtHubJoiningCredId);
198-
if (secret != null)
199-
{
200-
_mgmtHubJoiningSecret = JsonSerializer.Deserialize<ClientSecret>(secret, JsonOptions.DefaultJsonSerializerOptions);
201-
}
202-
}
203-
204-
_managementServerClient = new ManagementServerClient(hubUri, _mgmtHubJoiningSecret, instanceInfo);
251+
_managementServerClient = new ManagementServerClient(hubUri, instanceInfo);
252+
_managementServerClient.SetJoiningToken(_mgmtHubJoiningToken);
205253

206254
try
207255
{

src/Certify.Models/Hub/HubInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ public class HubInfo
77
public string HubEndpoint { get; set; } = default!;
88

99
public string Message { get; set; } = default!;
10+
11+
/// <summary>
12+
/// if set, provides the authenticated caller with a JWT joining token for use in subsequent hub communication
13+
/// </summary>
14+
public string JoiningToken { get; set; } = default!;
1015
}
1116

1217
public class HubHealth

src/Certify.Models/Hub/ManagedInstanceInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class ManagedInstanceInfo
1616
public DateTimeOffset LastReported { get; set; }
1717

1818
public string ConnectionStatus { get; set; } = string.Empty;
19+
public bool IsAuthenticated { get; set; }
1920
}
2021

2122
public class ConnectionStatus

src/Certify.Models/Providers/IManagementServerClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ public interface IManagementServerClient
1212
event Func<InstanceCommandRequest, Task<InstanceCommandResult>> OnGetCommandResult;
1313
event Func<ManagedInstanceItems> OnGetInstanceItems;
1414

15+
void SetJoiningToken(string joiningToken);
16+
1517
Task ConnectAsync();
1618
Task Disconnect();
1719
bool IsConnected();
1820
void SendInstanceInfo(Guid commandId, bool isCommandResponse = true);
1921
void SendNotificationToManagementHub(string msgCommandType, object updateMsg);
2022
}
21-
}
23+
}

src/Certify.Models/Shared/Json.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace Certify.Shared
44
{
55
public class JsonOptions
66
{
7-
public static JsonSerializerOptions DefaultJsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };
7+
private static readonly JsonSerializerOptions _defaultJsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };
8+
9+
public static JsonSerializerOptions DefaultJsonSerializerOptions => _defaultJsonSerializerOptions;
810
}
911
}

src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
</PropertyGroup>
1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.AspNet.SignalR.Client" Version="2.4.3" />
12-
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.3.0" />
1312
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.3" />
1413
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
1514
<PackageReference Include="Polly" Version="8.5.2" />

src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/SystemController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public SystemController(ILogger<SystemController> logger, ICertifyInternalApiCli
3232
_logger = logger;
3333
_client = client;
3434
_mgmtAPI = mgmtApi;
35+
3536
}
3637

3738
/// <summary>
@@ -117,6 +118,11 @@ public async Task<IActionResult> CheckJoining()
117118
hubInfo.HubEndpoint = "api/internal/managementhub";
118119
hubInfo.Message = "Joining OK";
119120

121+
var _config = HttpContext.RequestServices.GetRequiredService<IConfiguration>();
122+
var jwtService = new Hub.Api.Services.JwtService(_config);
123+
124+
hubInfo.JoiningToken = jwtService.GenerateSecurityToken($"clientId:{clientId}");
125+
120126
return new OkObjectResult(hubInfo);
121127
}
122128
else

src/Certify.Server/Certify.Server.Hub.Api/Services/JwtService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Security.Claims;
1+
using System.Security.Claims;
22
using System.Security.Cryptography;
33
using System.Text;
44
using Microsoft.IdentityModel.JsonWebTokens;
@@ -26,7 +26,7 @@ public JwtService(IConfiguration config)
2626
{
2727
_secret = config.GetSection("JwtSettings").GetSection("secret").Value ?? "";
2828
_issuer = config.GetSection("JwtSettings").GetSection("issuer").Value ?? "";
29-
_expDate = config.GetSection("JwtSettings").GetSection("expirationInDays").Value ?? DateTimeOffset.Now.AddDays(1).ToString();
29+
_expDate = config.GetSection("JwtSettings").GetSection("expirationInDays").Value ?? "1";
3030
}
3131

3232
/// <summary>
@@ -61,7 +61,7 @@ public string GenerateSecurityToken(string identifier, double? expiryMinutes)
6161
new Claim(ClaimTypes.Sid, identifier)
6262
}),
6363
Issuer = _issuer,
64-
Expires = expiryMinutes != null ? DateTime.UtcNow.AddHours((double)expiryMinutes) : DateTime.UtcNow.AddDays(double.Parse(_expDate)), //token expiry could be role specific - e.g. 1 yr vs 1 month
64+
Expires = expiryMinutes != null ? DateTime.UtcNow.AddMinutes((double)expiryMinutes) : DateTime.UtcNow.AddDays(double.Parse(_expDate)), //token expiry could be role specific - e.g. 1 yr vs 1 month
6565
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
6666
};
6767

0 commit comments

Comments
 (0)