惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI
罗磊的独立博客
腾讯CDC
云风的 BLOG
云风的 BLOG
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
宝玉的分享
宝玉的分享
U
Unit 42
I
InfoQ
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
V2EX
美团技术团队
IT之家
IT之家
Stack Overflow Blog
Stack Overflow Blog
F
Fortinet All Blogs
GbyAI
GbyAI
S
SegmentFault 最新的问题

博客园 - 无心々菜

InfluxDB2 在 docker 环境下部署,以及备份还原 高并发 - 配置2 高并发 - 配置 地图 - QGIS加载geojson,并导入Postgis数据库 VUE2 - Geoserver打印矢量PDF地图 - 在VUE2中的应用 地图 - Geoserver打印矢量PDF地图 Docker - 离线+无root环境安装 docker-rootless 运维 - 哪吒监控 Postgres 数据库在docker环境下分布式部署(arm64框架) Postgres 数据库在docker环境下分布式部署 Https - 配置方案 OpenObserver - 系统接口监控 VUE3 - Docker部署 + traefik反代 VUE2 - Docker部署 + traefik反代 高斯DB 数据库的安装 NETCORE - 使用 EFCORE 调用 高斯DB数据库 JMeter - 压力测试 NETCORE - IdentityServer4 多节点部署 NETCORE - IdentityServer4 相关文档 .NET框架 - NETCORE + API + EF(DBFirst) + PostgresSql Docker 部署 APISIX + etcd Docker部署martin 矢量切片服务器 Postgres 数据库的安装 NETCORE - 调用 RUSTFS Docker - 部署 RustFS 对像存储 Docker - 部署 Consul KV 统一管理所有项目的配置 Docker - 部署 HashiCorp Vault 开源密钥管理工具 kong 网关下集成 Consul服务注册与发现 Docker 部署 kong 网关 Docker - 部署Consul 新
Docker - 使用 Docker Secrets 敏感数据传输
无心々菜 · 2025-11-05 · via 博客园 - 无心々菜

Docker - 使用 Docker Secrets 敏感数据传输

Docker Secrets 是 Docker Swarm 提供的一种管理敏感数据的功能,用于安全地存储和传输密码、证书、私钥等不宜公开的数据,避免硬编码在镜像或配置文件中。 

核心功能

  • 数据加密‌:敏感数据在传输和存储时自动加密,仅限授权容器访问 ‌
  • 权限控制‌:可精细控制哪些服务或任务能访问特定秘密 ‌
  • 用途广泛‌:支持存储用户凭证、证书、API密钥等任何敏感信息 ‌

背景:

.net8 项目,需要连接 mysql 数据库,

开发环境:把连接字符串配置在 appsettings.Development.json 中

"ConnectionStrings": {
  "RailCDE": "Server=localhost;port=3306;Database=RailCDE;Uid=root;Pwd=123456;SslMode=None;",
  },

部署环境:

使用 consul kv 存储,参考:https://www.cnblogs.com/1285026182YUAN/p/19189386

连接字符串不配置密码,使用{password}占位符,在内存中替换,密码配置在 Docker Secrets 里面,用程序读取

"ConnectionStrings": {
  "RailCDE": "Server=localhost;port=3306;Database=RailCDE;Uid=root;Pwd={password};SslMode=None;",
  },

一. 生成密钥文件

1. 创建加密解密程序

    public static class AesEncryption
    {
        /// <summary>
        /// AES 加密(ECB 模式)
        /// </summary>
        /// <param name="plainText">明文</param>
        /// <param name="aesKey">加密密钥(16, 24, 32 字节)</param>
        /// <returns>Base64 编码的密文</returns>
        public static string AesEcbEncrypt(string plainText, string aesKey)
        {
            if (string.IsNullOrEmpty(plainText) || string.IsNullOrEmpty(aesKey))
                throw new ArgumentNullException("输入文本或密钥为空");

            // 确保密钥长度符合要求
            if (aesKey.Length != 16 && aesKey.Length != 24 && aesKey.Length != 32)
                throw new ArgumentException("密钥长度无效,必须是 16、24 或 32 字节");

            using (Aes aes = Aes.Create())
            {
                aes.Mode = CipherMode.ECB;
                aes.Padding = PaddingMode.PKCS7;
                aes.Key = Encoding.UTF8.GetBytes(aesKey);

                // 创建加密器
                ICryptoTransform encryptor = aes.CreateEncryptor();
                byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
                byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

                return Convert.ToBase64String(encryptedBytes);
            }
        }

        /// <summary>
        /// AES 解密(ECB 模式)
        /// </summary>
        /// <param name="cipherText">Base64 编码的密文</param>
        /// <param name="aesKey">解密密钥(16, 24, 32 字节)</param>
        /// <returns>解密后的明文</returns>
        public static string AesEcbDecrypt(string cipherText, string aesKey)
        {
            if (string.IsNullOrEmpty(cipherText) || string.IsNullOrEmpty(aesKey))
                throw new ArgumentNullException("输入密文或密钥为空");

            // 确保密钥长度符合要求
            if (aesKey.Length != 16 && aesKey.Length != 24 && aesKey.Length != 32)
                throw new ArgumentException("密钥长度无效,必须是 16、24 或 32 字节");

            using (Aes aes = Aes.Create())
            {
                aes.Mode = CipherMode.ECB;
                aes.Padding = PaddingMode.PKCS7;
                aes.Key = Encoding.UTF8.GetBytes(aesKey);

                // 创建解密器
                ICryptoTransform decryptor = aes.CreateDecryptor();
                byte[] cipherBytes = Convert.FromBase64String(cipherText);
                byte[] decryptedBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);

                return Encoding.UTF8.GetString(decryptedBytes);
            }
        }

        /// <summary>
        /// 生成指定长度的随机密钥
        /// </summary>
        /// <param name="lengthInBytes">密钥的字节长度</param>
        /// <returns>生成的密钥(Base64 编码)</returns>
        public static string GenerateRandomKey(int lengthInBytes = 16)
        {
            using (var rng = RandomNumberGenerator.Create())
            {
                byte[] key = new byte[lengthInBytes];
                rng.GetBytes(key);  // 填充密钥数组
                return Convert.ToBase64String(key); // 转换为 Base64 字符串
            }
        }

    }

使用程序对密码加密

var aeskey = AesEncryption.GenerateRandomKey();//记录下来,解密时用
var pwdEn = AesEncryption.AesEcbEncrypt("密码", aeskey);

//var aeskey = Const.DBAESKEY;
var pwdDe= AesEncryption.AesEcbDecrypt(pwdEn, aeskey);

 创建文件 D:\DockerConfig\configs\DbPwdMysql.txt

把生成的 pwdEn写入

二. 配置 docker-compose.yml 文件

version: '3.8'

services:
  your-service:
    image: your-image
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db-pwd-mysql
    secrets:
      - db-pwd-mysql

secrets:
  db-pwd-mysql:
    file: D:\DockerConfig\configs\DbPwdMysql.txt    #相对于 docker-compose.yml
    

三. 项目中解析

创建 UnifiedConfigurationBuilder.cs 文件

using Consul;
using Microsoft.Extensions.Configuration;
using Rail.Medium.Static;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Rail.Medium.Middleware
{
    public static class UnifiedConfigurationBuilder
    {
        public static IConfigurationBuilder AddUnifiedConfiguration(
            this IConfigurationBuilder builder,
            string consulAddress,
            string appName)
        {
            // 1. 加载 Docker Secrets
            builder.AddDockerSecrets();

            // 2. 构建临时 IConfiguration
            var tempConfig = builder.Build();

            // 3. 加载 Consul 配置,并传入临时 IConfiguration
            builder.Add(new ConsulConfigurationSource(consulAddress, appName, tempConfig));

            // 4. 加载环境变量
            builder.AddEnvironmentVariables();

            return builder;
        }

        private static IConfigurationBuilder AddDockerSecrets(this IConfigurationBuilder builder)
        {
            //var secretsPath = @"D:\DockerConfig\configs";
            var secretsPath = "/run/secrets";
            if (Directory.Exists(secretsPath))
            {
                foreach (var file in Directory.GetFiles(secretsPath))
                {
                    var key = Path.GetFileNameWithoutExtension(file);
                    var value_en = File.ReadAllText(file).Trim();
                    var value = AesEncryption.AesEcbDecrypt(value_en, Const.DBAESKEY);
                    builder.AddInMemoryCollection(new[]
                    {
                        new KeyValuePair<string, string>($"Secrets:{key}", value)
                    });
                }
            }
            return builder;
        }
    }

    public class ConsulConfigurationSource : IConfigurationSource
    {
        private readonly string _consulAddress;
        private readonly string _appName;
        private readonly IConfiguration _configuration; // 新增

        public ConsulConfigurationSource(string consulAddress, string appName, IConfiguration configuration)
        {
            _consulAddress = consulAddress;
            _appName = appName;
            _configuration = configuration;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new ConsulConfigurationProvider(_consulAddress, _appName, _configuration, "config");
        }
    }

    public class ConsulConfigurationProvider : ConfigurationProvider
    {
        private readonly string _consulAddress;
        private readonly string _appName;
        private readonly string _keyPrefix;
        private readonly IConfiguration _configuration; // 新增

        public ConsulConfigurationProvider(string consulAddress, string appName, IConfiguration configuration, string keyPrefix = "")
        {
            _consulAddress = consulAddress;
            _appName = appName;
            _keyPrefix = keyPrefix;
            _configuration = configuration;
        }

        public override void Load()
        {
            LoadConfigs().GetAwaiter().GetResult();
        }

        private async Task LoadConfigs()
        {
            try
            {
                using var client = new ConsulClient(config =>
                {
                    config.Address = new Uri(_consulAddress);
                });

                await LoadAllConfigKeys(client);

                Console.WriteLine($"配置加载完成,共 {Data.Count} 个配置项");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"加载配置失败: {ex.Message}");
            }
        }

        private async Task LoadAllConfigKeys(ConsulClient client)
        {
            // 获取根目录下的所有键(根据你的前缀过滤)
            var queryResult = await client.KV.List(_keyPrefix);
            if (queryResult?.Response != null)
            {
                var newData = new Dictionary<string, string?>();

                foreach (var kvPair in queryResult.Response)
                {
                    if (kvPair.Value != null)
                    {
                        var key = RemovePrefix(kvPair.Key);
                        var value = Encoding.UTF8.GetString(kvPair.Value);
                        value = GetConnectionString(key, value, _configuration);

                        newData[key] = value;
                    }
                }
                Data = newData;
            }
        }

        private string GetConnectionString(string key, string value, IConfiguration configuration)
        {
            // 判断是否为数据库连接,如果是,拼接密码
            if (key.StartsWith("ConnectionStrings", StringComparison.OrdinalIgnoreCase))
            {
                // 从已经加载的 Secrets 配置中获取密码
                var dbPwd = configuration["Secrets:DbPwdMysql"]; // 或根据 key 动态决定
                if (!string.IsNullOrEmpty(dbPwd))
                {
                    value = value.Replace("{password}", dbPwd); // 假设连接字符串里有 {password} 占位符
                }
            }

            return value;
        }

        private string RemovePrefix(string key)
        {
            key = key.StartsWith(_keyPrefix) ? key.Substring(_keyPrefix.Length + 1) : key;
            key = key.Replace("/", ":");
            return key;
        }


        #region 手动重新加载接口

        public async Task ReloadAsync()
        {
            await LoadConfigs();
            OnReload(); // 通知配置已更新
            Console.WriteLine("配置手动重新加载完成");
        }

        public Dictionary<string, string> GetCurrentConfigs()
        {
            return new Dictionary<string, string>(Data);
        }

        #endregion
    }


}

Program.cs 文件

#region == consul kv 加载 ==

var consulKVPath = configuration.GetSection("ConsulKVPath");

if (consulKVPath.Exists())
{
    builder.Configuration.AddUnifiedConfiguration(
        consulAddress: consulKVPath.Value,
        appName: builder.Environment.ApplicationName
    );
}

#endregion

end.