case 4711 - JWT signing key generated by the server; AYANOVA_JWT_SECRET removed
The LAN/IIS and Linux templates shipped a placeholder secret printed in the manual, and installs that kept it signed logins with a public key; any setting also invited short or reused values (security review F1). The setting is now ignored (one warning at boot if still present): the server generates a key once (48 random bytes) into jwt-signing.key in the data folder (beside the app if there is none; owner-only on Linux) and reuses it, so sign-ins also survive a restart where the server used to make a new key each boot (standalone, Linux desktop). If the file cannot be used, a temporary key is used for that run. Templates and dev launch configs drop the setting; manual updated (key page rewritten, setting steps removed from install pages); changelog line. Checked locally: token forged with the old placeholder 401; key file created and reused after restart (old token 200); auth and 2FA tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -228,27 +228,41 @@ namespace AyaNova
|
||||
#endregion
|
||||
|
||||
#region JWT AUTHENTICATION
|
||||
//get the key if specified
|
||||
var secretKey = ServerBootConfig.AYANOVA_JWT_SECRET;
|
||||
//case 4711: the signing key is no longer a setting (placeholders and weak values could be used to forge tokens);
|
||||
//it is generated once and kept in a file so sign-ins survive a restart. Deleting the file signs everyone out.
|
||||
if (ServerBootConfig.AYANOVA_JWT_SECRET_SETTING_PRESENT)
|
||||
_newLog.LogWarning("AYANOVA_JWT_SECRET is no longer used and can be removed from the configuration: AyaNova generates and keeps its own sign-in key");
|
||||
|
||||
//If no key specified make a unique one
|
||||
//This means the jwt creds won't survive a server reboot
|
||||
//so in that case users need to specify an AyaNova_JWT_SECRET environment variable
|
||||
if (string.IsNullOrWhiteSpace(secretKey))
|
||||
ServerBootConfig.AYANOVA_JWT_SECRET = LoadOrCreateJwtSigningKey(ServerBootConfig.JWT_SIGNING_KEY_FILE_PATH);
|
||||
|
||||
string LoadOrCreateJwtSigningKey(string path)
|
||||
{
|
||||
_newLog.LogWarning("AYANOVA_JWT_SECRET configuration setting is missing; AyaNova will randomly generate one. Any Users who were logged in when the server restarted will need to login to get a fresh auth token. See manual 'AYANOVA_JWT_SECRET' page for details.");
|
||||
secretKey = Util.Hasher.GenerateSalt();
|
||||
}
|
||||
//WAS "UNLICENSED5G*QQJ8#bQ7$Xr_@sXfHq4"
|
||||
|
||||
|
||||
//If secretKey is less than 32 characters, pad it
|
||||
if (secretKey.Length < 32)
|
||||
{
|
||||
secretKey = secretKey.PadRight(32, '-');
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(path))
|
||||
{
|
||||
var existing = System.IO.File.ReadAllText(path).Trim();
|
||||
if (existing.Length >= 32)
|
||||
return existing;
|
||||
_newLog.LogWarning($"Sign-in key file {path} is not valid; generating a new one (users will need to log in again)");
|
||||
}
|
||||
var newKey = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(48));
|
||||
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path));
|
||||
var options = new System.IO.FileStreamOptions { Mode = System.IO.FileMode.Create, Access = System.IO.FileAccess.Write };
|
||||
if (!OperatingSystem.IsWindows())
|
||||
options.UnixCreateMode = System.IO.UnixFileMode.UserRead | System.IO.UnixFileMode.UserWrite;
|
||||
using (var writer = new System.IO.StreamWriter(path, System.Text.Encoding.ASCII, options))
|
||||
writer.Write(newKey);
|
||||
_newLog.LogInformation($"Generated a new sign-in key in {path}");
|
||||
return newKey;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_newLog.LogWarning(ex, $"Could not read or create the sign-in key file {path}; using a temporary key for this run (users will need to log in again after a restart)");
|
||||
return Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(48));
|
||||
}
|
||||
}
|
||||
|
||||
ServerBootConfig.AYANOVA_JWT_SECRET = secretKey;
|
||||
var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.AYANOVA_JWT_SECRET));
|
||||
|
||||
_newLog.LogDebug("Authorization");
|
||||
|
||||
@@ -59,7 +59,12 @@ namespace AyaNova.Util
|
||||
internal static long AYANOVA_DEFAULT_TRANSLATION_ID { get; set; } //internal setting set at boot by TranslationBiz::ValidateTranslations
|
||||
|
||||
//API
|
||||
//case 4711: the JWT signing key, set at boot from JWT_SIGNING_KEY_FILE_PATH (generated once); no longer a setting
|
||||
internal static string AYANOVA_JWT_SECRET { get; set; }
|
||||
//case 4711: true if the old AYANOVA_JWT_SECRET setting is still present, so boot can say it is ignored
|
||||
internal static bool AYANOVA_JWT_SECRET_SETTING_PRESENT { get; set; }
|
||||
//case 4711: where the generated JWT signing key is kept (not a setting, derived from AYANOVA_DATA_PATH, else beside the app)
|
||||
internal static string JWT_SIGNING_KEY_FILE_PATH { get; set; }
|
||||
internal static string AYANOVA_USE_URLS { get; set; }
|
||||
internal static int AYANOVA_REPORT_RENDERING_TIMEOUT { get; set; }
|
||||
|
||||
@@ -174,7 +179,8 @@ namespace AyaNova.Util
|
||||
? "http://*:7575"
|
||||
: AYANOVA_USE_URLS;
|
||||
|
||||
AYANOVA_JWT_SECRET = config.GetValue<string>("AYANOVA_JWT_SECRET");
|
||||
//case 4711: no longer used, only noted so boot can log that it is ignored
|
||||
AYANOVA_JWT_SECRET_SETTING_PRESENT = !string.IsNullOrWhiteSpace(config.GetValue<string>("AYANOVA_JWT_SECRET"));
|
||||
|
||||
//backdoor back door password superuser reset
|
||||
AYANOVA_SET_SUPERUSER_PW = config.GetValue<string>("AYANOVA_SET_SUPERUSER_PW");
|
||||
@@ -270,6 +276,10 @@ namespace AyaNova.Util
|
||||
(string.IsNullOrWhiteSpace(DataFolderPath))
|
||||
? string.Empty
|
||||
: Path.Combine(DataFolderPath, "report-browser");
|
||||
JWT_SIGNING_KEY_FILE_PATH =
|
||||
(string.IsNullOrWhiteSpace(DataFolderPath))
|
||||
? Path.Combine(AppContext.BaseDirectory, "jwt-signing.key")
|
||||
: Path.Combine(DataFolderPath, "jwt-signing.key");
|
||||
|
||||
#endregion server BASICS
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user