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.crt 和 ca.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.1 与 openssl.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 certificateversion: "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有两种解决办法:
- 创建跳过不受信任证书的
OkHttpClient客户端,通过创建MinioClient的构造方法传递进去
- 导入证书到 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);
}
}