在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
背景当我们把应用的配置都放到配置中心后,很多人会想到这样一个问题,配置里面有敏感的信息要怎么处理呢? 信息既然敏感的话,那么加个密就好了嘛,相信大部分人的第一感觉都是这个,确实这个是最简单也是最合适的方法。 其实很多人都在关注这个问题,好比说,数据库的连接字符串,调用第三方的密钥等等这些信息,都是不太想让很多人知道的。 那么如果我们把配置放在 Nacos 了,我们可以怎么操作呢? 想了想不外乎这么几种:
有一个老哥已经在 issue 里面提出了相关的落地方案,也包含了部分实现。 https://github.com/alibaba/nacos/issues/5367 简要概述的话就是,开个口子,用户可以在客户端拓展任意加解密方式,同时服务端可以辅助这一操作。 不过看了 2.0.2 的代码,服务端这一块的“辅助”还未完成,不过对客户端来说,这一块其实问题已经不大了。 6月14号发布的 下面就用 .NET 5 和 Nacos 2.0.2 为例,来简单说明一下。 简单原理说明sdk 里面在进行配置相关读写操作的时候,会有一个 既然要执行 Filter , 那么执行的 Filter 从那里来呢? 答案是 sdk 里面提供了 下面看看它的定义。 public interface IConfigFilter { void Init(NacosSdkOptions options); int GetOrder(); string GetFilterName(); void DoFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain); }
换言之,只要我们定义一个 ConfigFilter,实现了这个接口,那么配置想怎么操作都可以了,加解密就是小问题了。 其中 NacosSdkOptions 里面加了两个配置项,是专门给这个功能用的
下面来看个具体的例子吧。 自定义 ConfigFilter这个 Filter 实现的效果是把部分敏感配置项进行加密,敏感的配置项需要在配置文件中指定。 先是 public void Init(NacosSdkOptions options) { // 从 Options 里面的拓展信息获取需要加密的 json path // 这里只是示例,根据具体情况调整成自己合适的!!!! var extInfo = JObject.Parse(options.ConfigFilterExtInfo); if (extInfo.ContainsKey("JsonPaths")) { // JsonPaths 在这里的含义是,那个path下面的内容要加密 _jsonPaths = extInfo.GetValue("JsonPaths").ToObject<List<string>>(); } } 然后是 这个方法里面要注意几点:
public void DoFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain) { if (request != null) { var encryptedDataKey = DefaultKey; var raw_content = request.GetParameter(Nacos.V2.Config.ConfigConstants.CONTENT); // 部分配置加密后的 content var content = ReplaceJsonNode((string)raw_content, encryptedDataKey, true); // 加密配置后,不要忘记更新 request !!!! request.PutParameter(Nacos.V2.Config.ConfigConstants.ENCRYPTED_DATA_KEY, encryptedDataKey); request.PutParameter(Nacos.V2.Config.ConfigConstants.CONTENT, content); } if (response != null) { var resp_content = response.GetParameter(Nacos.V2.Config.ConfigConstants.CONTENT); var resp_encryptedDataKey = response.GetParameter(Nacos.V2.Config.ConfigConstants.ENCRYPTED_DATA_KEY); // nacos 2.0.2 服务端目前还没有把 encryptedDataKey 记录并返回,所以 resp_encryptedDataKey 目前只会是 null // 如果服务端有记录并且能返回,我们可以做到每一个配置都用不一样的 encryptedDataKey 来加解密。 // 目前的话,只能固定一个 encryptedDataKey var encryptedDataKey = (resp_encryptedDataKey == null || string.IsNullOrWhiteSpace((string)resp_encryptedDataKey)) ? DefaultKey : (string)resp_encryptedDataKey; var content = ReplaceJsonNode((string)resp_content, encryptedDataKey, false); response.PutParameter(Nacos.V2.Config.ConfigConstants.CONTENT, content); } } 这里涉及 还有一个 private string ReplaceJsonNode(string src, string encryptedDataKey, bool isEnc = true) { // 示例配置用的是JSON,如果用的是 yaml,这里换成用 yaml 解析即可。 var jObj = JObject.Parse(src); foreach (var item in _jsonPaths) { var t = jObj.SelectToken(item); if (t != null) { var r = t.ToString(); // 加解密 var newToken = isEnc ? AESEncrypt(r, encryptedDataKey) : AESDecrypt(r, encryptedDataKey); if (!string.IsNullOrWhiteSpace(newToken)) { // 替换旧值 t.Replace(newToken); } } } return jObj.ToString(); } 到这里,自定义的 ConfigFilter 已经完成了,下面就是真正的应用了。 简单应用老样子,建一个 WebApi 项目,添加自定义 ConfigFilter 所在的包/项目/程序集。 这里用的是集成 ASP.NET Core 的例子。 修改 { "NacosConfig": { "Listeners": [ { "Optional": true, "DataId": "demo", "Group": "DEFAULT_GROUP" } ], "Namespace": "cs", "ServerAddresses": [ "http://localhost:8848/" ], "ConfigFilterAssemblies": [ "XXXX.CusLib" ], "ConfigFilterExtInfo": "{\"JsonPaths\":[\"ConnectionStrings.Default\"],\"Other\":\"xxxxxx\"}" } } 注:老黄这里把 Optional 设置成 true,是为了第一次运行的时候,如果服务端没有进行配置而不至于退出程序。 修改 public class Program { public static void Main(string[] args) { var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}"; Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .MinimumLevel.Debug() .WriteTo.Console(outputTemplate: outputTemplate) .CreateLogger(); System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); try { Log.ForContext<Program>().Information("Application starting..."); CreateHostBuilder(args, Log.Logger).Build().Run(); } catch (System.Exception ex) { Log.ForContext<Program>().Fatal(ex, "Application start-up failed!!"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args, Serilog.ILogger logger) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, builder) => { var c = builder.Build(); builder.AddNacosV2Configuration(c.GetSection("NacosConfig"), logAction: x => x.AddSerilog(logger)); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>().UseUrls("http://*:8787"); }) .UseSerilog(); } 最后是 public class Startup { // 省略部分.... public void ConfigureServices(IServiceCollection services) { services.AddNacosV2Config(Configuration, null, "NacosConfig"); services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.AddControllers(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var configSvc = app.ApplicationServices.GetRequiredService<Nacos.V2.INacosConfigService>(); var db = $"demo-{DateTimeOffset.Now.ToString("yyyyMMdd_HHmmss")}"; var oldConfig = "{\"ConnectionStrings\":{\"Default\":\"Server=127.0.0.1;Port=3306;Database=" + db + ";User Id=app;Password=098765;\"},\"version\":\"测试version---\",\"AppSettings\":{\"Str\":\"val\",\"num\":100,\"arr\":[1,2,3,4,5],\"subobj\":{\"a\":\"" + db + "\"}}}"; configSvc.PublishConfig("demo", "DEFAULT_GROUP", oldConfig).ConfigureAwait(false).GetAwaiter().GetResult(); var options = app.ApplicationServices.GetRequiredService<IOptionsMonitor<AppSettings>>(); Console.WriteLine("===用 IOptionsMonitor 读取配置==="); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(options.CurrentValue)); Console.WriteLine(""); Console.WriteLine("===用 IConfiguration 读取配置==="); Console.WriteLine(Configuration["ConnectionStrings:Default"]); Console.WriteLine(""); var pwd = $"demo-{new Random().Next(100000, 999999)}"; var newConfig = "{\"ConnectionStrings\":{\"Default\":\"Server=127.0.0.1;Port=3306;Database="+ db + ";User Id=app;Password="+ pwd +";\"},\"version\":\"测试version---\",\"AppSettings\":{\"Str\":\"val\",\"num\":100,\"arr\":[1,2,3,4,5],\"subobj\":{\"a\":\""+ db +"\"}}}"; // 模拟 配置变更 configSvc.PublishConfig("demo", "DEFAULT_GROUP", newConfig).ConfigureAwait(false).GetAwaiter().GetResult(); System.Threading.Thread.Sleep(500); var options2 = app.ApplicationServices.GetRequiredService<IOptionsMonitor<AppSettings>>(); Console.WriteLine("===用 IOptionsMonitor 读取配置==="); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(options2.CurrentValue)); Console.WriteLine(""); Console.WriteLine("===用 IConfiguration 读取配置==="); Console.WriteLine(Configuration["ConnectionStrings:Default"]); Console.WriteLine(""); // 省略部分.... } } 最后来看看几张效果图: 首先是程序的运行日志。 其次是和 Nacos 控制台的对比。 到这里的话,基于 Nacos 的加解密就完成了。 写在最后敏感配置项的加解密还是很有必要的,配置中心负责存储,客户端负责加解密,这样的方式可以让用户更加灵活的选择自己想要的加解密方法。 本文的示例代码已经上传到 Github,仅供参考。 https://github.com/catcherwong-archive/2021/tree/main/NacosConfigWithEncryption 最后的最后,希望感兴趣的大佬可以一起参与到这个项目来。 nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp 到此这篇关于.NET Core结合Nacos实现配置加解密的方法的文章就介绍到这了,更多相关.NET Core Nacos配置加解密内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论