Files
aliasvault/apps/server/Shared/AliasVault.Shared.Server/Utilities/SecretReader.cs
2025-09-03 14:59:14 +02:00

139 lines
5.7 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="SecretReader.cs" company="aliasvault">
// Copyright (c) aliasvault. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Shared.Server.Utilities;
using System;
using System.Globalization;
using System.IO;
/// <summary>
/// Utility class for reading secrets from environment variables or container secret files.
/// </summary>
public static class SecretReader
{
/// <summary>
/// Determines if the application is running in a container.
/// </summary>
/// <returns>True if running in a container, false otherwise.</returns>
public static bool IsRunningInContainer()
{
return Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
}
/// <summary>
/// Gets the JWT key from either the container secrets file or environment variable.
/// </summary>
/// <returns>The JWT key.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the JWT key cannot be found or is invalid.</exception>
public static string GetJwtKey()
{
if (IsRunningInContainer())
{
return ReadSecretFromFile("/secrets/jwt_key", "JWT key");
}
var jwtKey = Environment.GetEnvironmentVariable("JWT_KEY");
if (string.IsNullOrEmpty(jwtKey))
{
throw new KeyNotFoundException("JWT_KEY environment variable is not set.");
}
return jwtKey;
}
/// <summary>
/// Gets the admin password hash and generation timestamp.
/// </summary>
/// <returns>A tuple containing the password hash and the timestamp when it was generated.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the admin password hash cannot be found.</exception>
/// <exception cref="InvalidOperationException">Thrown when the admin password hash file has an invalid format.</exception>
public static (string PasswordHash, DateTime GeneratedAt) GetAdminPasswordHash()
{
if (IsRunningInContainer())
{
var secretsFilePath = "/secrets/admin_password_hash";
if (!File.Exists(secretsFilePath))
{
throw new KeyNotFoundException($"Admin password hash file not found at {secretsFilePath}. Container initialization may have failed.");
}
var secretContent = File.ReadAllText(secretsFilePath).Trim();
if (string.IsNullOrEmpty(secretContent))
{
throw new KeyNotFoundException($"Admin password hash file at {secretsFilePath} is empty.");
}
// Parse hash and timestamp separated by |
var parts = secretContent.Split('|');
if (parts.Length != 2)
{
throw new InvalidOperationException($"Invalid format in {secretsFilePath}. Expected format: hash|timestamp");
}
var passwordHash = parts[0];
if (!DateTime.TryParse(parts[1], CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var generatedAt))
{
throw new InvalidOperationException($"Invalid timestamp format in {secretsFilePath}: {parts[1]}");
}
return (passwordHash, generatedAt);
}
else
{
// Not in container - use environment variables
var passwordHash = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_HASH")
?? throw new KeyNotFoundException("ADMIN_PASSWORD_HASH environment variable is not set.");
var generatedAtStr = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_GENERATED")
?? throw new KeyNotFoundException("ADMIN_PASSWORD_GENERATED environment variable is not set.");
var generatedAt = DateTime.Parse(generatedAtStr, CultureInfo.InvariantCulture);
return (passwordHash, generatedAt);
}
}
/// <summary>
/// Gets the data protection certificate password.
/// </summary>
/// <returns>The data protection certificate password.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the certificate password cannot be found.</exception>
public static string GetDataProtectionCertPassword()
{
if (IsRunningInContainer())
{
return ReadSecretFromFile("/secrets/data_protection_cert_pass", "Certificate password");
}
return Environment.GetEnvironmentVariable("DATA_PROTECTION_CERT_PASS")
?? throw new KeyNotFoundException("DATA_PROTECTION_CERT_PASS is not set in configuration or environment variables.");
}
/// <summary>
/// Reads a secret from a file in the container secrets directory.
/// </summary>
/// <param name="filePath">The path to the secret file.</param>
/// <param name="secretName">The name of the secret for error messages.</param>
/// <returns>The secret value.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the secret file cannot be found or is invalid.</exception>
private static string ReadSecretFromFile(string filePath, string secretName)
{
if (!File.Exists(filePath))
{
throw new KeyNotFoundException($"{secretName} file not found at {filePath}. Container initialization may have failed.");
}
var secretValue = File.ReadAllText(filePath).Trim();
if (string.IsNullOrEmpty(secretValue))
{
throw new KeyNotFoundException($"{secretName} file at {filePath} is empty.");
}
return secretValue;
}
}