Docker Minio 使用 SSEC 加密存储

date
Apr 19, 2024
slug
Minio-SSEC
status
Published
tags
Java
minio
Docker
summary
记录使用 Java 客户端通过 SSEC 加密 Minio 存储的文件
type
Post
Language
Java

目标

  • 使 Minio 存储的文件加密
  • 上传时加密,下载时解密

方案

  • SSE-S3 使用 S3 托管密钥的服务器端加密
  • SSE-C 使用客户托管密钥的服务器端加密(本文选择这种方式)

自签 SSL 证书

Java Minio SDK 使用 SSE-C 时会强制要求使用 https 连接 Minio
  • 没有根证书的情况
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=*.*.*.*" -days 365 -out ca.crt

openssl genrsa -out server.key 2048
openssl req -new -nodes -key server.key -subj "/CN=*.*.*.*" -out server.csr

# 服务端证书生成时,需要设置subjectAltName = IP:192.168.0.10,如果 Docker 部署一定要设置为宿主机 IP
echo subjectAltName = IP:192.168.0.10 > extfile.cnf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out server.crt -days 3650
  • 有根证书的情况
准备根证书 ca.crtca.key
创建 openssl.cnf 文件,修改 IP.1 为被签名的 IP,如果 Docker 部署一定要设置为宿主机 IP
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
countryName = CN
countryName_default = CN
stateOrProvinceName = ProvinceName
stateOrProvinceName_default = ProvinceName_default
localityName = localityName
localityName_default = localityName_default 
organizationalUnitName  = org
organizationalUnitName_default  = org
commonName = org
commonName_max  = 64

[ v3_req ]
basicConstraints = CA:TRUE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
IP.1 = 192.168.0.10
创建 v3.ext 文件,IP.1openssl.cnf 配置保持一致
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName=@alt_names
[alt_names]
IP.1 = 192.168.0.10
生成服务器私钥
sudo openssl genrsa -out server.key 2048
生成服务器公钥
sudo openssl req -new -days 3653 -key server.key -out server.csr -config openssl.cnf
使用 CA 根证书生成服务器签名
sudo openssl x509 -days 3653 -req -sha256 -extfile v3.ext -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
校验 IP 是否被签发到证书
sudo openssl x509 -in server.crt -noout -text

配置证书到 Minio

复制公钥和私钥到 minio 的 /root/.minio/certs 目录
注意:公钥重命名为 public.crt, 私钥重命名为 private.key

cp server.crt /root/.minio/certs/public.crt
cp server.key /root/.minio/certs/private.key

配置 Docker-Compose

设置变量 MINIO_SERVER_URL 解决使用控制台报 x509:cannot validate certificate
version: "3"
services:
  minio:
    image: minio/minio:RELEASE.2022-01-04T07-41-07Z
    container_name: minio
    environment:
      MINIO_ACCESS_KEY: ${ACCESS_KEY}
      MINIO_SECRET_KEY: ${SECRET_KEY}
      MINIO_SERVER_URL: "https://192.168.0.10:9000"
    volumes:
      - ./minio/data:/data
      - ./minio/config/:/root/.minio/
    command: server --console-address ':9001' /data
    privileged: true
    restart: always
    healthcheck:
      test: [ "CMD", "curl", "-f", "https://127.0.0.1:9000/minio/health/live" ]
      interval: 30s
      timeout: 20s
      retries: 3
    ports:
      - 9000:9000
      - 9001:9001

证书不受信任

不受信任的证书,在 Java Minio SDK 连接 Minio 时会报 KIX path building failed SSL
有两种解决办法:
  1. 创建跳过不受信任证书的 OkHttpClient 客户端,通过创建 MinioClient 的构造方法传递进去
  1. 导入证书到 JDK(本文选择这种方案)
  • name 为别名
  • path 为证书公钥路径
cd $JAVA_HOME/lib/security
keytool -keystore cacerts -storepass changeit -noprompt -trustcacerts -importcert -alias <name> -file <path>

客户端集成

本文使用 Java 集成 Minio,以下给出使用 SSE-C 示例
public class FileClient {

    private MinioClient client;

    private ClientProperty config;

    private ServerSideEncryptionCustomerKey ssec;

    public PanFileClient(ClientProperty config) {
        this.config = config;
    }

    protected void doInit() {
        // 初始化客户端
        client = MinioClient.builder()
                .endpoint(config.getEndpoint())
                .credentials(config.getAccessKey(), config.getAccessSecret())
                .build();
        // 生成客户端密钥
        try {
            ssec = new ServerSideEncryptionCustomerKey(
                    new SecretKeySpec(
                            config.getSsecKey().getBytes(StandardCharsets.UTF_8), "AES"));
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
    }

    public String upload(byte[] content, String path, String type) throws Exception {
        client.putObject(PutObjectArgs.builder()
                .bucket(config.getBucket())
                .sse(ssec)
                .contentType(type)
                .object(path)
                .stream(new ByteArrayInputStream(content), content.length, -1)
                .build());
        return path;
    }

    public void delete(String path) throws Exception {
        client.removeObject(RemoveObjectArgs.builder()
                .bucket(config.getBucket())
                .object(path)
                .build());
    }

    public byte[] getContent(String path) throws Exception {
        GetObjectResponse response = client.getObject(GetObjectArgs.builder()
                .bucket(config.getBucket())
                .ssec(ssec)
                .object(path)
                .build());
        return IoUtil.readBytes(response);
    }
}

Reference

 

© chobit blog 2025