Skip to content

Commit c0880e7

Browse files
authored
feat: add support for a credentials chain for minio access (#31051)
We wanted to be able to use the IAM role provided by the EC2 instance metadata in order to access S3 via the Minio configuration. To do this, a new credentials chain is added that will check the following locations for credentials when an access key is not provided. In priority order, they are: 1. MINIO_ prefixed environment variables 2. AWS_ prefixed environment variables 3. a minio credentials file 4. an aws credentials file 5. EC2 instance metadata
1 parent 9875110 commit c0880e7

File tree

6 files changed

+169
-9
lines changed

6 files changed

+169
-9
lines changed

custom/conf/app.example.ini

+8-2
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,10 @@ LEVEL = Info
18721872
;; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
18731873
;MINIO_ENDPOINT = localhost:9000
18741874
;;
1875-
;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
1875+
;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`.
1876+
;; If not provided and STORAGE_TYPE is `minio`, will search for credentials in known
1877+
;; environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files
1878+
;; (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
18761879
;MINIO_ACCESS_KEY_ID =
18771880
;;
18781881
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
@@ -2573,7 +2576,10 @@ LEVEL = Info
25732576
;; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
25742577
;MINIO_ENDPOINT = localhost:9000
25752578
;;
2576-
;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
2579+
;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`.
2580+
;; If not provided and STORAGE_TYPE is `minio`, will search for credentials in known
2581+
;; environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files
2582+
;; (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
25772583
;MINIO_ACCESS_KEY_ID =
25782584
;;
25792585
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`

docs/content/administration/config-cheat-sheet.en-us.md

+12-6
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ Default templates for project board view:
843843
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
844844
- `PATH`: **attachments**: Path to store attachments only available when STORAGE_TYPE is `local`, relative paths will be resolved to `${AppDataPath}/${attachment.PATH}`.
845845
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when STORAGE_TYPE is `minio`
846-
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
846+
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`. If not provided and STORAGE_TYPE is `minio`, will search for credentials in known environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
847847
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
848848
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
849849
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
@@ -1274,7 +1274,7 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
12741274
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
12751275
- `PATH`: **./data/lfs**: Where to store LFS files, only available when `STORAGE_TYPE` is `local`. If not set it fall back to deprecated LFS_CONTENT_PATH value in [server] section.
12761276
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
1277-
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
1277+
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`. If not provided and STORAGE_TYPE is `minio`, will search for credentials in known environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
12781278
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
12791279
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
12801280
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
@@ -1290,7 +1290,7 @@ Default storage configuration for attachments, lfs, avatars, repo-avatars, repo-
12901290
- `STORAGE_TYPE`: **local**: Storage type, `local` for local disk or `minio` for s3 compatible object storage service.
12911291
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
12921292
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
1293-
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
1293+
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`. If not provided and STORAGE_TYPE is `minio`, will search for credentials in known environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
12941294
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
12951295
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
12961296
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
@@ -1305,7 +1305,10 @@ The recommended storage configuration for minio like below:
13051305
STORAGE_TYPE = minio
13061306
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
13071307
MINIO_ENDPOINT = localhost:9000
1308-
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
1308+
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`.
1309+
; If not provided and STORAGE_TYPE is `minio`, will search for credentials in known
1310+
; environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files
1311+
; (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
13091312
MINIO_ACCESS_KEY_ID =
13101313
; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
13111314
MINIO_SECRET_ACCESS_KEY =
@@ -1354,7 +1357,10 @@ STORAGE_TYPE = my_minio
13541357
STORAGE_TYPE = minio
13551358
; Minio endpoint to connect only available when STORAGE_TYPE is `minio`
13561359
MINIO_ENDPOINT = localhost:9000
1357-
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`
1360+
; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`.
1361+
; If not provided and STORAGE_TYPE is `minio`, will search for credentials in known
1362+
; environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files
1363+
; (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
13581364
MINIO_ACCESS_KEY_ID =
13591365
; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
13601366
MINIO_SECRET_ACCESS_KEY =
@@ -1380,7 +1386,7 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
13801386
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
13811387
- `PATH`: **./data/repo-archive**: Where to store archive files, only available when `STORAGE_TYPE` is `local`.
13821388
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
1383-
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
1389+
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when STORAGE_TYPE is `minio`. If not provided and STORAGE_TYPE is `minio`, will search for credentials in known environment variables (MINIO_ACCESS_KEY_ID, AWS_ACCESS_KEY_ID), credentials files (~/.mc/config.json, ~/.aws/credentials), and EC2 instance metadata.
13841390
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
13851391
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the lfs only available when `STORAGE_TYPE` is `minio`
13861392
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`

modules/storage/minio.go

+30-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
9797
}
9898

9999
minioClient, err := minio.New(config.Endpoint, &minio.Options{
100-
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
100+
Creds: buildMinioCredentials(config, credentials.DefaultIAMRoleEndpoint),
101101
Secure: config.UseSSL,
102102
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
103103
Region: config.Location,
@@ -164,6 +164,35 @@ func (m *MinioStorage) buildMinioDirPrefix(p string) string {
164164
return p
165165
}
166166

167+
func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string) *credentials.Credentials {
168+
// If static credentials are provided, use those
169+
if config.AccessKeyID != "" {
170+
return credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, "")
171+
}
172+
173+
// Otherwise, fallback to a credentials chain for S3 access
174+
chain := []credentials.Provider{
175+
// configure based upon MINIO_ prefixed environment variables
176+
&credentials.EnvMinio{},
177+
// configure based upon AWS_ prefixed environment variables
178+
&credentials.EnvAWS{},
179+
// read credentials from MINIO_SHARED_CREDENTIALS_FILE
180+
// environment variable, or default json config files
181+
&credentials.FileMinioClient{},
182+
// read credentials from AWS_SHARED_CREDENTIALS_FILE
183+
// environment variable, or default credentials file
184+
&credentials.FileAWSCredentials{},
185+
// read IAM role from EC2 metadata endpoint if available
186+
&credentials.IAM{
187+
Endpoint: iamEndpoint,
188+
Client: &http.Client{
189+
Transport: http.DefaultTransport,
190+
},
191+
},
192+
}
193+
return credentials.NewChainCredentials(chain)
194+
}
195+
167196
// Open opens a file
168197
func (m *MinioStorage) Open(path string) (Object, error) {
169198
opts := minio.GetObjectOptions{}

modules/storage/minio_test.go

+104
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package storage
66
import (
77
"context"
88
"net/http"
9+
"net/http/httptest"
910
"os"
1011
"testing"
1112

@@ -92,3 +93,106 @@ func TestS3StorageBadRequest(t *testing.T) {
9293
_, err := NewStorage(setting.MinioStorageType, cfg)
9394
assert.ErrorContains(t, err, message)
9495
}
96+
97+
func TestMinioCredentials(t *testing.T) {
98+
const (
99+
ExpectedAccessKey = "ExampleAccessKeyID"
100+
ExpectedSecretAccessKey = "ExampleSecretAccessKeyID"
101+
// Use a FakeEndpoint for IAM credentials to avoid logging any
102+
// potential real IAM credentials when running in EC2.
103+
FakeEndpoint = "http://localhost"
104+
)
105+
106+
t.Run("Static Credentials", func(t *testing.T) {
107+
cfg := setting.MinioStorageConfig{
108+
AccessKeyID: ExpectedAccessKey,
109+
SecretAccessKey: ExpectedSecretAccessKey,
110+
}
111+
creds := buildMinioCredentials(cfg, FakeEndpoint)
112+
v, err := creds.Get()
113+
114+
assert.NoError(t, err)
115+
assert.Equal(t, ExpectedAccessKey, v.AccessKeyID)
116+
assert.Equal(t, ExpectedSecretAccessKey, v.SecretAccessKey)
117+
})
118+
119+
t.Run("Chain", func(t *testing.T) {
120+
cfg := setting.MinioStorageConfig{}
121+
122+
t.Run("EnvMinio", func(t *testing.T) {
123+
t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio")
124+
t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio")
125+
126+
creds := buildMinioCredentials(cfg, FakeEndpoint)
127+
v, err := creds.Get()
128+
129+
assert.NoError(t, err)
130+
assert.Equal(t, ExpectedAccessKey+"Minio", v.AccessKeyID)
131+
assert.Equal(t, ExpectedSecretAccessKey+"Minio", v.SecretAccessKey)
132+
})
133+
134+
t.Run("EnvAWS", func(t *testing.T) {
135+
t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
136+
t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")
137+
138+
creds := buildMinioCredentials(cfg, FakeEndpoint)
139+
v, err := creds.Get()
140+
141+
assert.NoError(t, err)
142+
assert.Equal(t, ExpectedAccessKey+"AWS", v.AccessKeyID)
143+
assert.Equal(t, ExpectedSecretAccessKey+"AWS", v.SecretAccessKey)
144+
})
145+
146+
t.Run("FileMinio", func(t *testing.T) {
147+
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
148+
// prevent loading any actual credentials files from the user
149+
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
150+
151+
creds := buildMinioCredentials(cfg, FakeEndpoint)
152+
v, err := creds.Get()
153+
154+
assert.NoError(t, err)
155+
assert.Equal(t, ExpectedAccessKey+"MinioFile", v.AccessKeyID)
156+
assert.Equal(t, ExpectedSecretAccessKey+"MinioFile", v.SecretAccessKey)
157+
})
158+
159+
t.Run("FileAWS", func(t *testing.T) {
160+
// prevent loading any actual credentials files from the user
161+
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
162+
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")
163+
164+
creds := buildMinioCredentials(cfg, FakeEndpoint)
165+
v, err := creds.Get()
166+
167+
assert.NoError(t, err)
168+
assert.Equal(t, ExpectedAccessKey+"AWSFile", v.AccessKeyID)
169+
assert.Equal(t, ExpectedSecretAccessKey+"AWSFile", v.SecretAccessKey)
170+
})
171+
172+
t.Run("IAM", func(t *testing.T) {
173+
// prevent loading any actual credentials files from the user
174+
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
175+
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
176+
177+
// Spawn a server to emulate the EC2 Instance Metadata
178+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
179+
// The client will actually make 3 requests here,
180+
// first will be to get the IMDSv2 token, second to
181+
// get the role, and third for the actual
182+
// credentials. However, we can return credentials
183+
// every request since we're not emulating a full
184+
// IMDSv2 flow.
185+
w.Write([]byte(`{"Code":"Success","AccessKeyId":"ExampleAccessKeyIDIAM","SecretAccessKey":"ExampleSecretAccessKeyIDIAM"}`))
186+
}))
187+
defer server.Close()
188+
189+
// Use the provided EC2 Instance Metadata server
190+
creds := buildMinioCredentials(cfg, server.URL)
191+
v, err := creds.Get()
192+
193+
assert.NoError(t, err)
194+
assert.Equal(t, ExpectedAccessKey+"IAM", v.AccessKeyID)
195+
assert.Equal(t, ExpectedSecretAccessKey+"IAM", v.SecretAccessKey)
196+
})
197+
})
198+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[default]
2+
aws_access_key_id=ExampleAccessKeyIDAWSFile
3+
aws_secret_access_key=ExampleSecretAccessKeyIDAWSFile

modules/storage/testdata/minio.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "10",
3+
"aliases": {
4+
"s3": {
5+
"url": "https://s3.amazonaws.com",
6+
"accessKey": "ExampleAccessKeyIDMinioFile",
7+
"secretKey": "ExampleSecretAccessKeyIDMinioFile",
8+
"api": "S3v4",
9+
"path": "dns"
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)