[2025-05-04 追記]
minio-operator v7.1.0より、ServiceAccount Tokenのaudienceに sts.min.io
を指定しなければならなくなったので記載を修正しました。
github.com
はじめに
以下の記事を読んで、MinIOのSTS を使うとAccess Keyを毎回作って保存してとかしなくても済むことを知りました。
zenn.dev
ちょうど家にMinIO Operatorを使って構築したクラスタ が居たので、上記の記事では検証されていない MinIO Operator を使ったSTS をやってみました。
AWS に存在するSecurity Token Serviceの略です。
OpenID ConnectやSAML などの別のIdentity Providerへのフェデレーションを行い、 それらの認証トーク ンからAWS へアクセスするための短命なトーク ンを発行する方法が主流です。必要なときに都度リクエス トして短命なトーク ンを生成するので、Access Keyなどを作って保存しておく必要がなくなります。
docs.aws.amazon.com
MinIO Operatorでは、Kubernetes のService Account Tokenを使ってAssumeRoleWithWebIdentityを行います。Service Accountのトーク ンはKubernetes によって署名されており、operatorはその署名を検証してService Account Tokenの真正性を確認します。
事前にMinIO Operatorのカスタムリソースである PolicyBinding
を作っておくことで、MinIOクラスタ 内のアクセスポリシーとServiceAccountの紐付けを行います。operatorはService Account Tokenから得られたアカウント名に対応するPolicyBindingのアクセスポリシーを知り、MinioのAPI を使って一時的なトーク ンを発行します。
MinIO OperatorのSTS の流れ
そのため、Kubernetes クラスタ 内でAccess KeyやAccess SecretをSecretに保存する必要はなくなります。MinioのAPI へのアクセスをするアプリケーションをデプロイする前に、対応するPolicyとPolicyBinding・ServiceAccountを用意してアプリケーションの環境変数 として与えるだけでアクセスできるようになります。
正しいService Account以外からのリクエス トに対してはトーク ンが発行されず、MinIOへのアクセスはできません。
準備
今回はkindで作ったクラスタ で試します。STS のためにはTLS が必須となり、署名検証を無効にせずに使うためにはcert-manager およびtrust-manager を用いるのがよいです。いずれもhelmで簡単にインストールできます。
kindで建てたクラスタ にhelmを使ってcert-managerとtrust-managerをインストールします。trust-managerについて詳しくは過去記事をご覧下さい。
簡単に言うとクラスタ 内にCA証明書をばらまく機能を持ったカスタムコントローラです。
poyo.hatenablog.jp
kind create cluster --name minio-sts
helm repo add jetstack https://charts.jetstack.io --force-update
helm upgrade cert-manager jetstack/cert-manager \
--install \
--namespace cert-manager \
--create-namespace \
--set crds.enabled =true \
--wait
helm upgrade trust-manager jetstack/trust-manager \
--install \
--namespace cert-manager \
--wait
--set secretTargets.enabled =true \
--set secretTargets.authorizedSecretsAll =true
CAとClusterIssuerをセットアップ
このk8s クラスタ 内で使えるCAをセットアップし、各種コンポーネント の証明書はこのCAを使って発行します。tls/issuer.yaml
に記述します。
---
apiVersion : cert-manager.io/v1
kind : ClusterIssuer
metadata :
name : selfsign
spec :
selfSigned : {}
---
apiVersion : cert-manager.io/v1
kind : Certificate
metadata :
name : internal-ca-2025
spec :
isCA : true
commonName : internal-ca-2025
secretName : internal-ca-2025-secret
duration : 87600h
renewBefore : 336h
privateKey :
algorithm : ECDSA
size : 256
issuerRef :
name : selfsign
kind : ClusterIssuer
group : cert-manager.io
---
apiVersion : cert-manager.io/v1
kind : ClusterIssuer
metadata :
name : internal
spec :
ca :
secretName : internal-ca-2025-secret
kubectl apply -f tls/issuer.yaml -n cert-manager
Operator用の証明書とCA証明書をセットアップ
先にMinIO OperatorのSTS エンドポイント用証明書を発行しておきます。このとき、secretNameは sts-tls
とする必要があります*1 。minio-operator/cert.yaml
として記述します。
---
apiVersion : cert-manager.io/v1
kind : Certificate
metadata :
name : sts-certmanager-cert
namespace : minio-operator
spec :
dnsNames :
- sts
- sts.minio-operator.svc
- sts.minio-operator.svc.cluster.local
secretName : sts-tls
issuerRef :
kind : ClusterIssuer
name : internal
kubectl create ns minio-operator
kubectl apply -f minio-operator/cert.yaml
今回MinIO本体でもcert-managerが発行した証明書を使うのですが、operatorからMinIOへのリクエス トでも署名検証できるようにするためにCA証明書をマウントします。Operatorには特定の名前パターンでsecretを作ると自動で検知してくれる仕組み*2 があるのでこれを使います。
CA証明書はtrust-managerに配布させます。tls/bundle-for-minio.yaml
として記述します。
---
apiVersion : trust.cert-manager.io/v1alpha1
kind : Bundle
metadata :
name : operator-ca-tls-internal
namespace : cert-manager
spec :
sources :
- secret :
name : "internal-ca-2025-secret"
key : "ca.crt"
target :
secret :
key : "ca.crt"
namespaceSelector :
matchLabels :
kubernetes.io/metadata.name : minio-operator
kubectl apply -f tls/bundle-for-minio.yaml
MinIO Operatorをセットアップ
MinIO Operatorはアップストリームの手順に従ってインストールします。具体的にはリポジトリ のアーカイブ をダウンロードし、kustomizeでマニフェスト をビルド・一部パッチを当ててクラスタ にapplyします。
MINIO_OPERATOR_VERSION =" 7.1.1 "
mkdir -p minio-operator/upstream
wget -O minio-operator/upstream/v${MINIO_OPERATOR_VERSION} .tar.gz \
https://github.com/minio/operator/archive/refs/tags/v${MINIO_OPERATOR_VERSION} .tar.gz
cd " minio-operator/upstream "
tar xf " v ${MINIO_OPERATOR_VERSION} .tar.gz "
kustomize build operator-${MINIO_OPERATOR_VERSION} / > " operator.yaml "
rm -r operator-${MINIO_OPERATOR_VERSION} v${MINIO_OPERATOR_VERSION} .tar.gz
cd ../../
通常MinIO Operatorは自己署名証明書 を自動的に作成しますが、これはあらゆる署名検証を無効にする必要があり面倒です。そのため、自動作成する機能を切ってcert-managerで発行した証明書を使わせます。前のセクションで証明書は既に用意したので、ここでは自動作成する機能をオフにします。minio-operator/kustomization.yaml
でパッチを当てます。
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : minio-operator
resources :
- upstream/operator.yaml
- cert.yaml
patches :
- patch : |-
apiVersion : apps/v1
kind : Deployment
metadata :
name : minio-operator
namespace : minio-operator
spec :
template :
spec :
containers :
- name : minio-operator
env :
- name : OPERATOR_STS_AUTO_TLS_ENABLED
value : "off"
- name : OPERATOR_STS_ENABLED
value : "on"
kustomize build minio-operator/ | kubectl apply -f -
MinIO用の証明書をセットアップ
MinIOのWebコンソールおよびS3互換API のエンドポイントで利用される証明書を発行します。
---
apiVersion : cert-manager.io/v1
kind : Certificate
metadata :
name : minio-tenant-cert
namespace : objectstorage
spec :
dnsNames :
- "minio.objectstorage"
- "minio.objectstorage.svc"
- "minio.objectstorage.svc.cluster.local"
- "*.minio-hl.objectstorage.svc.cluster.local"
- "*.objectstorage.svc.cluster.local"
- "*.minio.objectstorage.svc.cluster.local"
secretName : minio-tenant-tls
issuerRef :
kind : ClusterIssuer
name : internal
kubectl create ns objectstorage
kubectl apply -f objectstorage/cert.yaml
MinIOをセットアップ
MinIO Operatorに対して、MinIO本体はTenantと呼ばれます。カスタムリソースであるTenantを作ることでOpertorによってMinIOがセットアップされます。
今回はテスト用にErasure Codingの設定を限界まで緩めているので冗長性がありません。本番環境でそのまま利用しないでください。
---
apiVersion : minio.min.io/v2
kind : Tenant
metadata :
name : minio
namespace : objectstorage
spec :
image : 'minio/minio:RELEASE.2025-03-12T18-04-18Z'
requestAutoCert : false
externalCertSecret :
- name : minio-tenant-tls
type : cert-manager.io/v1
configuration :
name : storage-configuration
pools :
- servers : 1
name : pool-0
volumesPerServer : 1
volumeClaimTemplate :
apiVersion : v1
kind : persistentvolumeclaims
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 1Gi
いくつかの追加設定をsecretを使って設定します。 MINIO_STORAGE_CLASS_STANDARD
でErasure Codingの設定をしています。本番環境では正しい設定をしてください*3 。パスワードも同様にランダムかつ十分長い文字列を設定するようにし、リポジトリ にはpushしないようにしてください。
---
apiVersion : v1
kind : Secret
metadata :
name : storage-configuration
namespace : objectstorage
stringData :
config.env : |
export MINIO_ROOT_USER="minio"
export MINIO_ROOT_PASSWORD="password"
export MINIO_STORAGE_CLASS_STANDARD="EC:0"
export MINIO_BROWSER="on"
これらを適用します。
cat << 'EOF' > objectstorage/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: objectstorage
resources:
- storage-configuration.yaml
- tenant.yaml
- tenant-cert.yaml
EOF
kustomize build objectstorage/ | kubectl apply -f -
最終的に以下の様にPodが立っていればOKです。
❯ kubectl get po -n objectstorage
NAME READY STATUS RESTARTS AGE
minio-pool-0-0 2/2 Running 0 6h24m
ポートフォワ ードしてコンソールにアクセスします。証明書のエラーが出ますが、これはクラスタ 内CAの証明書がブラウザ側にはインストールされていないためです。本番環境ではコンソールを公開するためのIngress リソースなどを用意し、クラスタ 外でもvalidな証明書を使いましょう。今回は単に警告を無視してアクセスします。
kubectl port-forward svc/minio-console -n objectstorage 9443:9443
https://localhost:9443
にアクセスすると設定したユーザ名・パスワードでログインできます。
MinIOのWebコンソール
Create Bucket
してテスト用のbucket を作ります。
testバケット を作った様子
最後にこの test
bucket のためのPolicyを作成します。
{
"Version ": "2012-10-17 ",
"Statement ": [
{
"Effect ": "Allow ",
"Action ": [
"s3:DeleteObject ",
"s3:GetObject ",
"s3:ListBucket ",
"s3:PutObject "
] ,
"Resource ": [
"arn:aws:s3:::test/* "
]
}
]
}
test-rw policyを追加する様子
MinIOのクライアント用CA証明書を配布する
MinIOの証明書の署名検証のため、CA証明書をクラスタ 内の全namespaceに配布します。各Podはこれをマウントすることで正しく署名検証できます。
---
apiVersion : trust.cert-manager.io/v1alpha1
kind : Bundle
metadata :
name : internal-ca-bundle
spec :
sources :
- useDefaultCAs : true
- secret :
name : "internal-ca-2025-secret"
key : "tls.crt"
target :
configMap :
key : "trust-bundle.pem"
kubectl apply -f tls/bundle.yaml
全namespaceで internal-ca-bundle
というConfigMapが作られているはずです。
❯ kubectl get cm internal-ca-bundle
NAME DATA AGE
internal-ca-bundle 1 6h4m
STS を使ってMinioにアクセスする
ようやく長いセットアップが終わったので実際にSTS を使ってMinIOのAPI を呼び出します。
PolicyBindingを作る
MinIO Operatorはこのカスタムリソースを元にService Accountとそれに許可するPolicyを識別します。このPolicy BindingはMinIO Tenantと同じnamespaceに配置する必要があります。今回はdefault namespaceの test
というService Accountに test-rw
Policyを許可します。
---
apiVersion : sts.min.io/v1alpha1
kind : PolicyBinding
metadata :
name : default-test
namespace : objectstorage
spec :
application :
namespace : default
serviceaccount : test
policies :
- test-rw
kubectl apply -f objectstorage/policybindings.yaml
awscliで操作する
基本的には環境変数 を使ってアクセス先や認証方法を設定します。awscliやAWS SDK はいずれも同じ環境変数 で設定を変更できるようになっています。
docs.aws.amazon.com
awscliの入ったPodを立ち上げます。
---
apiVersion : v1
kind : ServiceAccount
metadata :
name : test
---
apiVersion : v1
kind : Pod
metadata :
name : bastion
spec :
containers :
- name : bastion
image : amazon/aws-cli:latest
command :
- sleep
- infinity
volumeMounts :
- name : internal-ca
mountPath : /tls/internal
readOnly : true
- name : minio-sts-token
mountPath : /var/run/secrets/sts.min.io/serviceaccount
readOnly : true
volumes :
- name : internal-ca
configMap :
name : internal-ca-bundle
- name : minio-sts-token
projected :
sources :
- serviceAccountToken :
audience : "sts.min.io"
expirationSeconds : 86400
path : token
serviceAccountName : test
kubectl apply -f default/bastion.yaml
kubectl exec
を使って上記コンテナ内のbash を立ち上げます。
kubectl exec -it bastion -- bash
コンテナの中で以下の環境変数 を設定します。
export AWS_CA_BUNDLE =/tls/internal/trust-bundle.pem
export AWS_WEB_IDENTITY_TOKEN_FILE =/var/run/secrets/sts.min.io/serviceaccount/token
export AWS_ENDPOINT_URL_S3 =https://minio.objectstorage.svc:443
export AWS_ENDPOINT_URL_STS =https://sts.minio-operator.svc:4223/sts/objectstorage
export AWS_ROLE_ARN =arn:aws:iam::dummy:role/test
これだけでawscliを使ってMinIOのS3互換API を利用できます。
bash-4.2# aws s3 ls s3://test/
bash-4.2# echo hello > hello.txt
bash-4.2# aws s3 cp hello.txt s3://test/
upload: ./hello.txt to s3://test/hello.txt
bash-4.2# aws s3 ls s3://test/
2025-03-21 06:14:38 6 hello.txt
bash-4.2#
同じ環境変数 を用いると、以下の様なコードでMinIOへの操作を記述できます。今回はGoのAWS SDK v2を使っています。
package main
import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
var (
bucketName string
pathToList string
)
func listBucketItems() error {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return fmt.Errorf("failed to load configuration, %w" , err)
}
bucket := s3.NewFromConfig(cfg, func (o *s3.Options) {
o.UsePathStyle = true
})
outputs, err := bucket.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: &bucketName,
Prefix: &pathToList,
})
if err != nil {
return fmt.Errorf("failed to list bucket items, %w" , err)
}
for _, item := range outputs.Contents {
slog.Info("get" , "path" , item.Key, "size" , item.Size, "bucket" , bucketName)
}
return nil
}
func main() {
flag.StringVar(&bucketName, "bucket" , "" , "bucket name" )
flag.StringVar(&pathToList, "path" , "" , "path to list" )
flag.Parse()
if err := listBucketItems(); err != nil {
slog.Error("failed to list bucket items" , "error" , err)
os.Exit(1 )
}
}
同じようにAWS SDK を使っているアプリケーションでは同様の環境変数 の設定でアクセス出来るようになるため非常に便利です。
例えば筆者のクラスタ にはmoco というMySQL のオペレータがインストールされており、これはAWS SDK を使ってMySQL クラスタ のバックアップをS3にアップロードする仕組みを持っています。MySQLBackupというカスタムリソースでその設定を書くのですが、以下の様に書いて適切なBucket ・Policy・PolicyBindingを用意するだけでバックアップのデータをMinIOにアップロードできます。
apiVersion : moco.cybozu.com/v1beta2
kind : BackupPolicy
metadata :
name : daily
spec :
schedule : "5 0 * * *"
concurrencyPolicy : Forbid
jobConfig :
serviceAccountName : moco-mysql
env :
- name : AWS_CA_BUNDLE
value : /tls/internal/trust-bundle.pem
- name : AWS_WEB_IDENTITY_TOKEN_FILE
value : /var/run/secrets/sts.min.io/serviceaccount/token
- name : AWS_ENDPOINT_URL_STS
value : https://sts.minio-operator.svc:4223/sts/objectstorage
- name : AWS_ROLE_ARN
value : arn:aws:iam::dummy:role/test
bucketConfig :
bucketName : moco-mysql-backup
region : us-east-1
endpointURL : https://minio.objectstorage.svc:443
usePathStyle : true
workVolume :
ephemeral :
volumeClaimTemplate :
spec :
accessModes : [ "ReadWriteOnce" ]
resources :
requests :
storage : 10Gi
volumeMounts :
- name : internal-ca
mountPath : /tls/internal
readOnly : true
- name : minio-sts-token
mountPath : /var/run/secrets/sts.min.io/serviceaccount
readOnly : true
volumes :
- name : internal-ca
configMap :
name : internal-ca-bundle
- name : minio-sts-token
projected :
sources :
- serviceAccountToken :
audience : "sts.min.io"
expirationSeconds : 86400
path : token
気になったところ
STS のエンドポイントが /sts/{{Tenantのnamespace}}
となるのですが、 Tenant
リソース自体は一つのnamespaceに複数作成できそうな気がします。
すると、あるPolicyBindingがどのTenantのものなのか判別できない気がします。実用的にこれで困ることがあるかは不明です。
今回は自分しか管理する人が居ないクラスタ なのでよいのですが、複数の開発者が同じクラスタ を共有するマルチテナント構成の場合、PolicyBindingリソースの操作権限をどう与えるか悩ましいなと思いました。
いくらでも作成・更新できてしまうと他チームのbucket を勝手に操作する権限を付与できてしまいますし、逆に全く作れないようにしてMinIO Tenantの管理チームだけに絞ってしまうと都度依頼対応が必要になったりして手間が増えそうです。
また、Policyを宣言的に管理する方法がないのもつらいところです。どのように権限分担をするのかが悩ましい仕組みだなと思いました。
まとめ
Kubernetes のin clusterなMinIOへのアクセスでは、MinIO OperatorのSTS を利用することで面倒なAccess Keyの管理なしにMinIOへのアクセス権を付与出来ます。
MinIO OperatorのSTS はリクエス トに付与されたKubernetes のService Account Tokenを元に、PolicyBindingカスタムリソースを用いてMinIO上のPolicyを識別し、MinIOから短命なトーク ンを発行します。
awscliやAWS SDK は環境変数 経由で認証方法やアクセス先をカスタマイズでき、他者のアプリケーションであってもAWS SDK を使っていればSTS を使ってMinIOにアクセス出来るます(たぶん)。