Skip to content

Commit

Permalink
fix: add tests for Minio credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
bohde committed May 24, 2024
1 parent 197612e commit 1a4127f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 27 deletions.
57 changes: 30 additions & 27 deletions modules/storage/minio.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,34 +96,8 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
return nil, fmt.Errorf("invalid minio bucket lookup type: %s", config.BucketLookUpType)
}

// By default, use the static credentials
creds := credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, "")

// If the Access Key ID is empty, configure a credentials chain for S3 access
if config.AccessKeyID == "" {
chain := []credentials.Provider{
// configure based upon MINIO_ prefixed environment variables
&credentials.EnvMinio{},
// configure based upon AWS_ prefixed environment variables
&credentials.EnvAWS{},
// read credentials from MINIO_SHARED_CREDENTIALS_FILE
// environment variable, or default json config files
&credentials.FileMinioClient{},
// read credentials from AWS_SHARED_CREDENTIALS_FILE
// environment variable, or default credentials file
&credentials.FileAWSCredentials{},
// read IAM role from EC2 metadata endpoint if available
&credentials.IAM{
Client: &http.Client{
Transport: http.DefaultTransport,
},
},
}
creds = credentials.NewChainCredentials(chain)
}

minioClient, err := minio.New(config.Endpoint, &minio.Options{
Creds: creds,
Creds: buildMinioCredentials(config, credentials.DefaultIAMRoleEndpoint),
Secure: config.UseSSL,
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
Region: config.Location,
Expand Down Expand Up @@ -190,6 +164,35 @@ func (m *MinioStorage) buildMinioDirPrefix(p string) string {
return p
}

func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string) *credentials.Credentials {
// If static credentials are provided, use those
if config.AccessKeyID != "" {
return credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, "")
}

// Otherwise, fallback to a credentials chain for S3 access
chain := []credentials.Provider{
// configure based upon MINIO_ prefixed environment variables
&credentials.EnvMinio{},
// configure based upon AWS_ prefixed environment variables
&credentials.EnvAWS{},
// read credentials from MINIO_SHARED_CREDENTIALS_FILE
// environment variable, or default json config files
&credentials.FileMinioClient{},
// read credentials from AWS_SHARED_CREDENTIALS_FILE
// environment variable, or default credentials file
&credentials.FileAWSCredentials{},
// read IAM role from EC2 metadata endpoint if available
&credentials.IAM{
Endpoint: iamEndpoint,
Client: &http.Client{
Transport: http.DefaultTransport,
},
},
}
return credentials.NewChainCredentials(chain)
}

// Open opens a file
func (m *MinioStorage) Open(path string) (Object, error) {
opts := minio.GetObjectOptions{}
Expand Down
104 changes: 104 additions & 0 deletions modules/storage/minio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package storage
import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"

Expand Down Expand Up @@ -92,3 +93,106 @@ func TestS3StorageBadRequest(t *testing.T) {
_, err := NewStorage(setting.MinioStorageType, cfg)
assert.ErrorContains(t, err, message)
}

func TestMinioCredentials(t *testing.T) {
const (
ExpectedAccessKey = "ExampleAccessKeyID"
ExpectedSecretAccessKey = "ExampleSecretAccessKeyID"
// Use a FakeEndpoint for IAM credentials to avoid logging any
// potential real IAM credentials when running in EC2.
FakeEndpoint = "http://localhost"
)

t.Run("Static Credentials", func(t *testing.T) {
cfg := setting.MinioStorageConfig{
AccessKeyID: ExpectedAccessKey,
SecretAccessKey: ExpectedSecretAccessKey,
}
creds := buildMinioCredentials(cfg, FakeEndpoint)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey, v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey, v.SecretAccessKey)
})

t.Run("Chain", func(t *testing.T) {
cfg := setting.MinioStorageConfig{}

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)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey+"Minio", v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey+"Minio", v.SecretAccessKey)
})

t.Run("EnvAWS", func(t *testing.T) {
t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")

creds := buildMinioCredentials(cfg, FakeEndpoint)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey+"AWS", v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey+"AWS", v.SecretAccessKey)
})

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("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")

creds := buildMinioCredentials(cfg, FakeEndpoint)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey+"MinioFile", v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey+"MinioFile", v.SecretAccessKey)
})

t.Run("FileAWS", func(t *testing.T) {
// prevent loading any actual credentials files from the user
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")

creds := buildMinioCredentials(cfg, FakeEndpoint)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey+"AWSFile", v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey+"AWSFile", v.SecretAccessKey)
})

t.Run("IAM", func(t *testing.T) {
// prevent loading any actual credentials files from the user
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")

// Spawn a server to emulate the EC2 Instance Metadata
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// The client will actually make 3 requests here,
// first will be to get the IMDSv2 token, second to
// get the role, and third for the actual
// credentials. However, we can return credentials
// every request since we're not emulating a full
// IMDSv2 flow.
w.Write([]byte(`{"Code":"Success","AccessKeyId":"ExampleAccessKeyIDIAM","SecretAccessKey":"ExampleSecretAccessKeyIDIAM"}`))
}))
defer server.Close()

// Use the provided EC2 Instance Metadata server
creds := buildMinioCredentials(cfg, server.URL)
v, err := creds.Get()

assert.NoError(t, err)
assert.Equal(t, ExpectedAccessKey+"IAM", v.AccessKeyID)
assert.Equal(t, ExpectedSecretAccessKey+"IAM", v.SecretAccessKey)
})
})
}
3 changes: 3 additions & 0 deletions modules/storage/testdata/aws_credentials
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[default]
aws_access_key_id=ExampleAccessKeyIDAWSFile
aws_secret_access_key=ExampleSecretAccessKeyIDAWSFile
12 changes: 12 additions & 0 deletions modules/storage/testdata/minio.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "10",
"aliases": {
"s3": {
"url": "https://s3.amazonaws.com",
"accessKey": "ExampleAccessKeyIDMinioFile",
"secretKey": "ExampleSecretAccessKeyIDMinioFile",
"api": "S3v4",
"path": "dns"
}
}
}

0 comments on commit 1a4127f

Please sign in to comment.