summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/credentials/file_minio_client.go18
-rw-r--r--pkg/credentials/iam_aws.go54
-rw-r--r--pkg/credentials/iam_aws_test.go46
-rw-r--r--pkg/policy/bucket-policy.go10
-rw-r--r--pkg/policy/bucket-policy_test.go8
-rw-r--r--pkg/s3utils/utils.go31
6 files changed, 148 insertions, 19 deletions
diff --git a/pkg/credentials/file_minio_client.go b/pkg/credentials/file_minio_client.go
index c282c2a..6a6827e 100644
--- a/pkg/credentials/file_minio_client.go
+++ b/pkg/credentials/file_minio_client.go
@@ -62,13 +62,17 @@ func NewFileMinioClient(filename string, alias string) *Credentials {
// users home directory.
func (p *FileMinioClient) Retrieve() (Value, error) {
if p.filename == "" {
- homeDir, err := homedir.Dir()
- if err != nil {
- return Value{}, err
- }
- p.filename = filepath.Join(homeDir, ".mc", "config.json")
- if runtime.GOOS == "windows" {
- p.filename = filepath.Join(homeDir, "mc", "config.json")
+ if value, ok := os.LookupEnv("MINIO_SHARED_CREDENTIALS_FILE"); ok {
+ p.filename = value
+ } else {
+ homeDir, err := homedir.Dir()
+ if err != nil {
+ return Value{}, err
+ }
+ p.filename = filepath.Join(homeDir, ".mc", "config.json")
+ if runtime.GOOS == "windows" {
+ p.filename = filepath.Join(homeDir, "mc", "config.json")
+ }
}
}
diff --git a/pkg/credentials/iam_aws.go b/pkg/credentials/iam_aws.go
index 637df74..6845c9a 100644
--- a/pkg/credentials/iam_aws.go
+++ b/pkg/credentials/iam_aws.go
@@ -21,8 +21,10 @@ import (
"bufio"
"encoding/json"
"errors"
+ "fmt"
"net/http"
"net/url"
+ "os"
"path"
"time"
)
@@ -50,16 +52,25 @@ type IAM struct {
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
const (
defaultIAMRoleEndpoint = "http://169.254.169.254"
+ defaultECSRoleEndpoint = "http://169.254.170.2"
defaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials"
)
+// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
+func getEndpoint(endpoint string) (string, bool) {
+ if endpoint != "" {
+ return endpoint, os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != ""
+ }
+ if ecsURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); ecsURI != "" {
+ return fmt.Sprintf("%s%s", defaultECSRoleEndpoint, ecsURI), true
+ }
+ return defaultIAMRoleEndpoint, false
+}
+
// NewIAM returns a pointer to a new Credentials object wrapping
// the IAM. Takes a ConfigProvider to create a EC2Metadata client.
// The ConfigProvider is satisfied by the session.Session type.
func NewIAM(endpoint string) *Credentials {
- if endpoint == "" {
- endpoint = defaultIAMRoleEndpoint
- }
p := &IAM{
Client: &http.Client{
Transport: http.DefaultTransport,
@@ -73,11 +84,17 @@ func NewIAM(endpoint string) *Credentials {
// Error will be returned if the request fails, or unable to extract
// the desired
func (m *IAM) Retrieve() (Value, error) {
- roleCreds, err := getCredentials(m.Client, m.endpoint)
+ endpoint, isEcsTask := getEndpoint(m.endpoint)
+ var roleCreds ec2RoleCredRespBody
+ var err error
+ if isEcsTask {
+ roleCreds, err = getEcsTaskCredentials(m.Client, endpoint)
+ } else {
+ roleCreds, err = getCredentials(m.Client, endpoint)
+ }
if err != nil {
return Value{}, err
}
-
// Expiry window is set to 10secs.
m.SetExpiration(roleCreds.Expiration, DefaultExpiryWindow)
@@ -111,9 +128,6 @@ type ec2RoleCredRespBody struct {
// be sent to fetch the rolling access credentials.
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
func getIAMRoleURL(endpoint string) (*url.URL, error) {
- if endpoint == "" {
- endpoint = defaultIAMRoleEndpoint
- }
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
@@ -153,12 +167,36 @@ func listRoleNames(client *http.Client, u *url.URL) ([]string, error) {
return credsList, nil
}
+func getEcsTaskCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody, error) {
+ req, err := http.NewRequest("GET", endpoint, nil)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return ec2RoleCredRespBody{}, errors.New(resp.Status)
+ }
+
+ respCreds := ec2RoleCredRespBody{}
+ if err := json.NewDecoder(resp.Body).Decode(&respCreds); err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ return respCreds, nil
+}
+
// getCredentials - obtains the credentials from the IAM role name associated with
// the current EC2 service.
//
// If the credentials cannot be found, or there is an error
// reading the response an error will be returned.
func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody, error) {
+
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
u, err := getIAMRoleURL(endpoint)
if err != nil {
diff --git a/pkg/credentials/iam_aws_test.go b/pkg/credentials/iam_aws_test.go
index 86ea66b..4dbbb0a 100644
--- a/pkg/credentials/iam_aws_test.go
+++ b/pkg/credentials/iam_aws_test.go
@@ -21,6 +21,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
+ "os"
"testing"
"time"
)
@@ -41,6 +42,13 @@ const credsFailRespTmpl = `{
"LastUpdated": "2009-11-23T0:00:00Z"
}`
+const credsRespEcsTaskTmpl = `{
+ "AccessKeyId" : "accessKey",
+ "SecretAccessKey" : "secret",
+ "Token" : "token",
+ "Expiration" : "%s"
+}`
+
func initTestFailServer() *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not allowed", http.StatusBadRequest)
@@ -73,6 +81,14 @@ func initTestServer(expireOn string, failAssume bool) *httptest.Server {
return server
}
+func initEcsTaskTestServer(expireOn string) *httptest.Server {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, credsRespEcsTaskTmpl, expireOn)
+ }))
+
+ return server
+}
+
func TestIAMMalformedEndpoint(t *testing.T) {
creds := NewIAM("%%%%")
_, err := creds.Get()
@@ -195,3 +211,33 @@ func TestIAMIsExpired(t *testing.T) {
t.Error("Expected creds to be expired when curren time has changed")
}
}
+
+func TestEcsTask(t *testing.T) {
+ server := initEcsTaskTestServer("2014-12-16T01:51:37Z")
+ defer server.Close()
+ p := &IAM{
+ Client: http.DefaultClient,
+ endpoint: server.URL,
+ }
+ os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials?id=task_credential_id")
+ creds, err := p.Retrieve()
+ os.Unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
+ if err != nil {
+ t.Errorf("Unexpected failure %s", err)
+ }
+ if "accessKey" != creds.AccessKeyID {
+ t.Errorf("Expected \"accessKey\", got %s", creds.AccessKeyID)
+ }
+
+ if "secret" != creds.SecretAccessKey {
+ t.Errorf("Expected \"secret\", got %s", creds.SecretAccessKey)
+ }
+
+ if "token" != creds.SessionToken {
+ t.Errorf("Expected \"token\", got %s", creds.SessionToken)
+ }
+
+ if !p.IsExpired() {
+ t.Error("Expected creds to be expired.")
+ }
+}
diff --git a/pkg/policy/bucket-policy.go b/pkg/policy/bucket-policy.go
index 9d5f5b3..79fd801 100644
--- a/pkg/policy/bucket-policy.go
+++ b/pkg/policy/bucket-policy.go
@@ -557,7 +557,6 @@ func GetPolicy(statements []Statement, bucketName string, prefix string) BucketP
} else {
matchedObjResources = s.Resources.FuncMatch(resourceMatch, objectResource)
}
-
if !matchedObjResources.IsEmpty() {
readOnly, writeOnly := getObjectPolicy(s)
for resource := range matchedObjResources {
@@ -571,7 +570,8 @@ func GetPolicy(statements []Statement, bucketName string, prefix string) BucketP
matchedResource = resource
}
}
- } else if s.Resources.Contains(bucketResource) {
+ }
+ if s.Resources.Contains(bucketResource) {
commonFound, readOnly, writeOnly := getBucketPolicy(s, prefix)
bucketCommonFound = bucketCommonFound || commonFound
bucketReadOnly = bucketReadOnly || readOnly
@@ -605,6 +605,7 @@ func GetPolicies(statements []Statement, bucketName, prefix string) map[string]B
}
}
}
+
// Pretend that policy resource as an actual object and fetch its policy
for r := range objResources {
// Put trailing * if exists in asterisk
@@ -613,7 +614,10 @@ func GetPolicies(statements []Statement, bucketName, prefix string) map[string]B
r = r[:len(r)-1]
asterisk = "*"
}
- objectPath := r[len(awsResourcePrefix+bucketName)+1:]
+ var objectPath string
+ if len(r) >= len(awsResourcePrefix+bucketName)+1 {
+ objectPath = r[len(awsResourcePrefix+bucketName)+1:]
+ }
p := GetPolicy(statements, bucketName, objectPath)
policyRules[bucketName+"/"+objectPath+asterisk] = p
}
diff --git a/pkg/policy/bucket-policy_test.go b/pkg/policy/bucket-policy_test.go
index b6b4551..1a71d87 100644
--- a/pkg/policy/bucket-policy_test.go
+++ b/pkg/policy/bucket-policy_test.go
@@ -1592,6 +1592,7 @@ func TestListBucketPolicies(t *testing.T) {
downloadUploadCondKeyMap.Add("s3:prefix", set.CreateStringSet("both"))
downloadUploadCondMap.Add("StringEquals", downloadUploadCondKeyMap)
+ commonSetActions := commonBucketActions.Union(readOnlyBucketActions)
testCases := []struct {
statements []Statement
bucketName string
@@ -1630,6 +1631,13 @@ func TestListBucketPolicies(t *testing.T) {
Principal: User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet("arn:aws:s3:::mybucket/download*"),
}}, "mybucket", "", map[string]BucketPolicy{"mybucket/download*": BucketPolicyReadOnly}},
+ {[]Statement{
+ {
+ Actions: commonSetActions.Union(readOnlyObjectActions),
+ Effect: "Allow",
+ Principal: User{AWS: set.CreateStringSet("*")},
+ Resources: set.CreateStringSet("arn:aws:s3:::mybucket", "arn:aws:s3:::mybucket/*"),
+ }}, "mybucket", "", map[string]BucketPolicy{"mybucket/*": BucketPolicyReadOnly}},
// Write Only
{[]Statement{
{
diff --git a/pkg/s3utils/utils.go b/pkg/s3utils/utils.go
index bfeb73e..adceb7f 100644
--- a/pkg/s3utils/utils.go
+++ b/pkg/s3utils/utils.go
@@ -143,11 +143,40 @@ func IsAmazonGovCloudEndpoint(endpointURL url.URL) bool {
}
// IsAmazonFIPSGovCloudEndpoint - Match if it is exactly Amazon S3 FIPS GovCloud endpoint.
+// See https://aws.amazon.com/compliance/fips.
func IsAmazonFIPSGovCloudEndpoint(endpointURL url.URL) bool {
if endpointURL == sentinelURL {
return false
}
- return endpointURL.Host == "s3-fips-us-gov-west-1.amazonaws.com"
+ return endpointURL.Host == "s3-fips-us-gov-west-1.amazonaws.com" ||
+ endpointURL.Host == "s3-fips.dualstack.us-gov-west-1.amazonaws.com"
+}
+
+// IsAmazonFIPSUSEastWestEndpoint - Match if it is exactly Amazon S3 FIPS US East/West endpoint.
+// See https://aws.amazon.com/compliance/fips.
+func IsAmazonFIPSUSEastWestEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ switch endpointURL.Host {
+ case "s3-fips.us-east-2.amazonaws.com":
+ case "s3-fips.dualstack.us-west-1.amazonaws.com":
+ case "s3-fips.dualstack.us-west-2.amazonaws.com":
+ case "s3-fips.dualstack.us-east-2.amazonaws.com":
+ case "s3-fips.dualstack.us-east-1.amazonaws.com":
+ case "s3-fips.us-west-1.amazonaws.com":
+ case "s3-fips.us-west-2.amazonaws.com":
+ case "s3-fips.us-east-1.amazonaws.com":
+ default:
+ return false
+ }
+ return true
+}
+
+// IsAmazonFIPSEndpoint - Match if it is exactly Amazon S3 FIPS endpoint.
+// See https://aws.amazon.com/compliance/fips.
+func IsAmazonFIPSEndpoint(endpointURL url.URL) bool {
+ return IsAmazonFIPSUSEastWestEndpoint(endpointURL) || IsAmazonFIPSGovCloudEndpoint(endpointURL)
}
// IsGoogleEndpoint - Match if it is exactly Google cloud storage endpoint.