|
| 1 | +using System.Security.Cryptography; |
| 2 | +using Accanto.Infrastructure; |
| 3 | +using Accanto.Infrastructure.Persistence; |
| 4 | +using Accanto.Infrastructure.Security; |
| 5 | +using Microsoft.EntityFrameworkCore; |
| 6 | +using Microsoft.Extensions.Configuration; |
| 7 | +using Microsoft.Extensions.DependencyInjection; |
| 8 | + |
| 9 | +namespace Accanto.Cli; |
| 10 | + |
| 11 | +/// <summary> |
| 12 | +/// CLI di amministrazione Accanto. Comandi disponibili: |
| 13 | +/// |
| 14 | +/// accanto generate-key |
| 15 | +/// Stampa una nuova chiave AES-256 in base64. |
| 16 | +/// |
| 17 | +/// accanto rotate-keys |
| 18 | +/// Riscrive tutti i campi cifrati e tutti i file dei documenti usando la chiave |
| 19 | +/// "attiva" (Encryption:ActiveKeyId). Le vecchie chiavi devono restare configurate |
| 20 | +/// in Encryption:Keys (oppure Encryption:MasterKey per il formato legacy v1) |
| 21 | +/// finche' la rotazione non e' completa. |
| 22 | +/// </summary> |
| 23 | +public static class Program |
| 24 | +{ |
| 25 | + public static async Task<int> Main(string[] args) |
| 26 | + { |
| 27 | + if (args.Length == 0) |
| 28 | + { |
| 29 | + PrintUsage(); |
| 30 | + return 1; |
| 31 | + } |
| 32 | + |
| 33 | + var command = args[0]; |
| 34 | + var rest = args.Skip(1).ToArray(); |
| 35 | + |
| 36 | + try |
| 37 | + { |
| 38 | + return command switch |
| 39 | + { |
| 40 | + "generate-key" => GenerateKey(), |
| 41 | + "rotate-keys" => await RotateKeysAsync(rest), |
| 42 | + "--help" or "-h" or "help" => PrintUsage(), |
| 43 | + _ => UnknownCommand(command), |
| 44 | + }; |
| 45 | + } |
| 46 | + catch (Exception ex) |
| 47 | + { |
| 48 | + Console.Error.WriteLine($"Errore: {ex.Message}"); |
| 49 | + return 2; |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + private static int GenerateKey() |
| 54 | + { |
| 55 | + var key = new byte[32]; |
| 56 | + RandomNumberGenerator.Fill(key); |
| 57 | + Console.WriteLine(Convert.ToBase64String(key)); |
| 58 | + return 0; |
| 59 | + } |
| 60 | + |
| 61 | + private static async Task<int> RotateKeysAsync(string[] args) |
| 62 | + { |
| 63 | + var config = new ConfigurationBuilder() |
| 64 | + .SetBasePath(Directory.GetCurrentDirectory()) |
| 65 | + .AddJsonFile("appsettings.json", optional: true) |
| 66 | + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true) |
| 67 | + .AddEnvironmentVariables() |
| 68 | + .AddCommandLine(args) |
| 69 | + .Build(); |
| 70 | + |
| 71 | + var services = new ServiceCollection(); |
| 72 | + services.AddSingleton<IConfiguration>(config); |
| 73 | + services.AddLogging(); |
| 74 | + services.AddAccantoInfrastructure(config); |
| 75 | + services.AddScoped<KeyRotationService>(); |
| 76 | + |
| 77 | + await using var provider = services.BuildServiceProvider(); |
| 78 | + |
| 79 | + var protector = (AesGcmFieldProtector)provider.GetRequiredService<Accanto.Application.Common.Security.IFieldProtector>(); |
| 80 | + if (protector.ActiveKeyId is null) |
| 81 | + { |
| 82 | + Console.Error.WriteLine( |
| 83 | + "Nessuna ActiveKeyId configurata: imposta Encryption:ActiveKeyId e Encryption:Keys " |
| 84 | + + "(la chiave legacy va lasciata in Encryption:MasterKey finche' tutti i dati v1 non sono ruotati)."); |
| 85 | + return 3; |
| 86 | + } |
| 87 | + |
| 88 | + Console.WriteLine($"Avvio rotazione verso la chiave attiva '{protector.ActiveKeyId}'."); |
| 89 | + using var scope = provider.CreateScope(); |
| 90 | + var db = scope.ServiceProvider.GetRequiredService<AccantoDbContext>(); |
| 91 | + await db.Database.MigrateAsync(); |
| 92 | + |
| 93 | + var rotator = scope.ServiceProvider.GetRequiredService<KeyRotationService>(); |
| 94 | + var report = await rotator.RotateAsync(); |
| 95 | + |
| 96 | + Console.WriteLine("Rotazione completata:"); |
| 97 | + Console.WriteLine($" cerchi di cura : {report.CareCircles}"); |
| 98 | + Console.WriteLine($" voci di diario : {report.TimelineEntries}"); |
| 99 | + Console.WriteLine($" domande per medico : {report.DoctorQuestions}"); |
| 100 | + Console.WriteLine($" aggiornamenti : {report.SharedUpdates}"); |
| 101 | + Console.WriteLine($" documenti (DB) : {report.MedicalDocumentRows}"); |
| 102 | + Console.WriteLine($" documenti (file) : {report.MedicalDocumentFiles}"); |
| 103 | + if (report.MissingFiles.Count > 0) |
| 104 | + { |
| 105 | + Console.WriteLine($" file mancanti : {report.MissingFiles.Count}"); |
| 106 | + foreach (var f in report.MissingFiles) |
| 107 | + Console.WriteLine($" - {f}"); |
| 108 | + } |
| 109 | + return 0; |
| 110 | + } |
| 111 | + |
| 112 | + private static int UnknownCommand(string command) |
| 113 | + { |
| 114 | + Console.Error.WriteLine($"Comando sconosciuto: {command}"); |
| 115 | + PrintUsage(); |
| 116 | + return 1; |
| 117 | + } |
| 118 | + |
| 119 | + private static int PrintUsage() |
| 120 | + { |
| 121 | + Console.WriteLine("Uso: accanto <comando>"); |
| 122 | + Console.WriteLine(); |
| 123 | + Console.WriteLine("Comandi:"); |
| 124 | + Console.WriteLine(" generate-key Stampa una nuova chiave AES-256 in base64."); |
| 125 | + Console.WriteLine(" rotate-keys Riscrive i dati cifrati usando Encryption:ActiveKeyId."); |
| 126 | + Console.WriteLine(" help Mostra questo messaggio."); |
| 127 | + return 0; |
| 128 | + } |
| 129 | +} |
0 commit comments