]> source.dussan.org Git - gitea.git/commitdiff
Support optional/configurable IAMEndpoint for Minio Client (#32581) (#32581) main
authorMichael Owoc <130198442+mowoc-ocp@users.noreply.github.com>
Fri, 22 Nov 2024 20:12:06 +0000 (15:12 -0500)
committerGitHub <noreply@github.com>
Fri, 22 Nov 2024 20:12:06 +0000 (20:12 +0000)
Targeting issue #32271

This modification allows native Kubernetes + AWS (EKS) authentication
with the Minio client, to Amazon S3 using the IRSA role assigned to a
Service account by replacing the hard coded reference to the
`DefaultIAMRoleEndpoint` with an optional configurable endpoint.

Internally, Minio's `credentials.IAM` provider implements a discovery
flow for IAM Endpoints if it is not set.

For backwards compatibility:
- We have added a configuration mechanism for an `IamEndpoint` to retain
the unit test safety in `minio_test.go`.
- We believe existing clients will continue to function the same without
needing to provide a new config property since the internals of Minio
client also often resolve to the `http://169.254.169.254` default
endpoint that was being hard coded before

To test, we were able to build a docker image from source and, observe
it choosing the expected IAM endpoint, and see files uploaded via the
client.

custom/conf/app.example.ini
modules/setting/storage.go
modules/setting/storage_test.go
modules/storage/minio.go
modules/storage/minio_test.go

index ef5684237dc5ba29d2262df513f9c0b0168fca5a..c3b78e60bb47cf115e827c0a439028471edcc92d 100644 (file)
@@ -1944,6 +1944,13 @@ LEVEL = Info
 ;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
 ;MINIO_SECRET_ACCESS_KEY =
 ;;
+;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`. 
+;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables 
+;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, 
+;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION), 
+;; or the DefaultIAMRoleEndpoint if not provided otherwise.
+;MINIO_IAM_ENDPOINT =
+;; 
 ;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
 ;MINIO_BUCKET = gitea
 ;;
@@ -2688,6 +2695,13 @@ LEVEL = Info
 ;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
 ;MINIO_SECRET_ACCESS_KEY =
 ;;
+;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`. 
+;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables 
+;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, 
+;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION), 
+;; or the DefaultIAMRoleEndpoint if not provided otherwise.
+;MINIO_IAM_ENDPOINT =
+;;
 ;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
 ;MINIO_BUCKET = gitea
 ;;
index d6f7672b613d8b483931ee72aaca1238c16a2d23..d3d1fb9f30b209566ca124f3f451fee5696d58e6 100644 (file)
@@ -43,6 +43,7 @@ type MinioStorageConfig struct {
        Endpoint           string `ini:"MINIO_ENDPOINT" json:",omitempty"`
        AccessKeyID        string `ini:"MINIO_ACCESS_KEY_ID" json:",omitempty"`
        SecretAccessKey    string `ini:"MINIO_SECRET_ACCESS_KEY" json:",omitempty"`
+       IamEndpoint        string `ini:"MINIO_IAM_ENDPOINT" json:",omitempty"`
        Bucket             string `ini:"MINIO_BUCKET" json:",omitempty"`
        Location           string `ini:"MINIO_LOCATION" json:",omitempty"`
        BasePath           string `ini:"MINIO_BASE_PATH" json:",omitempty"`
index 44a5de68265ff77df504b38f6def0f0d4ebe5a24..8ee37fd2b6d9c7ad2fe12638b97d51071cd2357a 100644 (file)
@@ -470,6 +470,19 @@ MINIO_BASE_PATH = /prefix
        cfg, err = NewConfigProviderFromData(`
 [storage]
 STORAGE_TYPE = minio
+MINIO_IAM_ENDPOINT = 127.0.0.1
+MINIO_USE_SSL = true
+MINIO_BASE_PATH = /prefix
+`)
+       assert.NoError(t, err)
+       assert.NoError(t, loadRepoArchiveFrom(cfg))
+       assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint)
+       assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
+       assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
+
+       cfg, err = NewConfigProviderFromData(`
+[storage]
+STORAGE_TYPE = minio
 MINIO_ACCESS_KEY_ID = my_access_key
 MINIO_SECRET_ACCESS_KEY = my_secret_key
 MINIO_USE_SSL = true
index 8acb7b0354c2ebbdf8a7700e97b0df47ff324f73..6b92be61fb7d22090815eadbe9ddaf8e2ea52031 100644 (file)
@@ -97,7 +97,7 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
        }
 
        minioClient, err := minio.New(config.Endpoint, &minio.Options{
-               Creds:        buildMinioCredentials(config, credentials.DefaultIAMRoleEndpoint),
+               Creds:        buildMinioCredentials(config),
                Secure:       config.UseSSL,
                Transport:    &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
                Region:       config.Location,
@@ -164,7 +164,7 @@ func (m *MinioStorage) buildMinioDirPrefix(p string) string {
        return p
 }
 
-func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string) *credentials.Credentials {
+func buildMinioCredentials(config setting.MinioStorageConfig) *credentials.Credentials {
        // If static credentials are provided, use those
        if config.AccessKeyID != "" {
                return credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, "")
@@ -184,7 +184,9 @@ func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string
                &credentials.FileAWSCredentials{},
                // read IAM role from EC2 metadata endpoint if available
                &credentials.IAM{
-                       Endpoint: iamEndpoint,
+                       // passing in an empty Endpoint lets the IAM Provider
+                       // decide which endpoint to resolve internally
+                       Endpoint: config.IamEndpoint,
                        Client: &http.Client{
                                Transport: http.DefaultTransport,
                        },
index 6eb03c4a45fdd95abfce00a19a63b8a93a4b755a..395da051e869b6fb2ba14b8017d4823d3e95dd9f 100644 (file)
@@ -107,8 +107,9 @@ func TestMinioCredentials(t *testing.T) {
                cfg := setting.MinioStorageConfig{
                        AccessKeyID:     ExpectedAccessKey,
                        SecretAccessKey: ExpectedSecretAccessKey,
+                       IamEndpoint:     FakeEndpoint,
                }
-               creds := buildMinioCredentials(cfg, FakeEndpoint)
+               creds := buildMinioCredentials(cfg)
                v, err := creds.Get()
 
                assert.NoError(t, err)
@@ -117,13 +118,15 @@ func TestMinioCredentials(t *testing.T) {
        })
 
        t.Run("Chain", func(t *testing.T) {
-               cfg := setting.MinioStorageConfig{}
+               cfg := setting.MinioStorageConfig{
+                       IamEndpoint: FakeEndpoint,
+               }
 
                t.Run("EnvMinio", func(t *testing.T) {
                        t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio")
                        t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio")
 
-                       creds := buildMinioCredentials(cfg, FakeEndpoint)
+                       creds := buildMinioCredentials(cfg)
                        v, err := creds.Get()
 
                        assert.NoError(t, err)
@@ -135,7 +138,7 @@ func TestMinioCredentials(t *testing.T) {
                        t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
                        t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")
 
-                       creds := buildMinioCredentials(cfg, FakeEndpoint)
+                       creds := buildMinioCredentials(cfg)
                        v, err := creds.Get()
 
                        assert.NoError(t, err)
@@ -144,11 +147,11 @@ func TestMinioCredentials(t *testing.T) {
                })
 
                t.Run("FileMinio", func(t *testing.T) {
-                       t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
                        // prevent loading any actual credentials files from the user
+                       t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
                        t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
 
-                       creds := buildMinioCredentials(cfg, FakeEndpoint)
+                       creds := buildMinioCredentials(cfg)
                        v, err := creds.Get()
 
                        assert.NoError(t, err)
@@ -161,7 +164,7 @@ func TestMinioCredentials(t *testing.T) {
                        t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
                        t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")
 
-                       creds := buildMinioCredentials(cfg, FakeEndpoint)
+                       creds := buildMinioCredentials(cfg)
                        v, err := creds.Get()
 
                        assert.NoError(t, err)
@@ -187,7 +190,9 @@ func TestMinioCredentials(t *testing.T) {
                        defer server.Close()
 
                        // Use the provided EC2 Instance Metadata server
-                       creds := buildMinioCredentials(cfg, server.URL)
+                       creds := buildMinioCredentials(setting.MinioStorageConfig{
+                               IamEndpoint: server.URL,
+                       })
                        v, err := creds.Get()
 
                        assert.NoError(t, err)