From 7de29e6e6bf1194705001d4cd355840d0ba4eb4d Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 1 Apr 2020 13:34:20 -0700 Subject: [PATCH] Add rotating token support for admin API (#9244) Use the *credentials.Credentials implementation method *Get* ``` func (c *Credentials) Get() (Value, error) { ``` which also handles auto-refresh, this allows for chaining of various implementations together if necessary or simply initialize with credentials.NewStaticV4(access, secret, token) Co-authored-by: Klaus Post --- go.mod | 4 +- go.sum | 16 ++--- pkg/madmin/api.go | 100 +++++++++++++++++++------- pkg/madmin/config-commands.go | 4 +- pkg/madmin/config-history-commands.go | 2 +- pkg/madmin/config-kv-commands.go | 6 +- pkg/madmin/user-commands.go | 10 +-- 7 files changed, 93 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index ee01e8444..88064f8f7 100644 --- a/go.mod +++ b/go.mod @@ -114,9 +114,9 @@ require ( go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 // indirect golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f - golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect - golang.org/x/sys v0.0.0-20200320181252-af34d8274f85 + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 // indirect google.golang.org/api v0.5.0 google.golang.org/appengine v1.6.0 // indirect diff --git a/go.sum b/go.sum index 83729b6ba..760c813ca 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3 h1:1sl2HmNtqGnDuydLgCJwZIpDLGqZOdwOkcY8WtUl8Cw= -github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU= github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7 h1:smZXPopqRVVywwzou4WYWvUbJvSAzIDFizfWElpmAqY= github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= @@ -391,10 +389,6 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/secure-io/sio-go v0.3.0 h1:QKGb6rGJeiExac9wSWxnWPYo8O8OFN7lxXQvHshX6vo= github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU= -github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= -github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v2.20.2+incompatible h1:ucK79BhBpgqQxPASyS2cu9HX8cfDVljBN1WWFvbNvgY= -github.com/shirou/gopsutil v2.20.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible h1:YiKUe2ZOmfpDBH4OSyxwkx/mjNqHHnNhOtZ2mPyRme8= github.com/shirou/gopsutil v2.20.3-0.20200314133625-53cec6b37e6a+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -480,8 +474,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -508,10 +502,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200320181252-af34d8274f85 h1:fD99hd4ciR6T3oPhr2EkmuKe9oHixHx9Hj/hND89j3g= -golang.org/x/sys v0.0.0-20200320181252-af34d8274f85/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/pkg/madmin/api.go b/pkg/madmin/api.go index 2bc3df177..a0d902b47 100644 --- a/pkg/madmin/api.go +++ b/pkg/madmin/api.go @@ -26,6 +26,7 @@ import ( "io/ioutil" "math/rand" "net/http" + "net/http/cookiejar" "net/http/httputil" "net/url" "os" @@ -34,18 +35,21 @@ import ( "strings" "time" + "github.com/minio/minio-go/v6/pkg/credentials" "github.com/minio/minio-go/v6/pkg/s3signer" "github.com/minio/minio-go/v6/pkg/s3utils" + "golang.org/x/net/publicsuffix" ) // AdminClient implements Amazon S3 compatible methods. type AdminClient struct { /// Standard options. - // AccessKeyID required for authorized requests. - accessKeyID string - // SecretAccessKey required for authorized requests. - secretAccessKey string + // Parsed endpoint url provided by the user. + endpointURL *url.URL + + // Holds various credential providers. + credsProvider *credentials.Credentials // User supplied. appInfo struct { @@ -53,8 +57,6 @@ type AdminClient struct { appVersion string } - endpointURL url.URL - // Indicate whether we are using https or not secure bool @@ -85,38 +87,67 @@ const ( libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion ) -// New - instantiate minio client Client, adds automatic verification of signature. +// Options for New method +type Options struct { + Creds *credentials.Credentials + Secure bool + // Add future fields here +} + +// New - instantiate minio admin client func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*AdminClient, error) { - clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, secure) + creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "") + + clnt, err := privateNew(endpoint, creds, secure) if err != nil { return nil, err } return clnt, nil } -func privateNew(endpoint, accessKeyID, secretAccessKey string, secure bool) (*AdminClient, error) { +// NewWithOptions - instantiate minio admin client with options. +func NewWithOptions(endpoint string, opts *Options) (*AdminClient, error) { + clnt, err := privateNew(endpoint, opts.Creds, opts.Secure) + if err != nil { + return nil, err + } + return clnt, nil +} + +func privateNew(endpoint string, creds *credentials.Credentials, secure bool) (*AdminClient, error) { + // Initialize cookies to preserve server sent cookies if any and replay + // them upon each request. + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, err + } + // construct endpoint. endpointURL, err := getEndpointURL(endpoint, secure) if err != nil { return nil, err } - // instantiate new Client. - clnt := &AdminClient{ - accessKeyID: accessKeyID, - secretAccessKey: secretAccessKey, - // Remember whether we are using https or not - secure: secure, - // Save endpoint URL, user agent for future uses. - endpointURL: *endpointURL, - // Instantiate http client and bucket location cache. - httpClient: &http.Client{ - Transport: DefaultTransport(secure), - }, - // Introduce a new locked random seed. - random: rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())}), + clnt := new(AdminClient) + + // Save the credentials. + clnt.credsProvider = creds + + // Remember whether we are using https or not + clnt.secure = secure + + // Save endpoint URL, user agent for future uses. + clnt.endpointURL = endpointURL + + // Instantiate http client and bucket location cache. + clnt.httpClient = &http.Client{ + Jar: jar, + Transport: DefaultTransport(secure), } + // Add locked pseudo-random number generator. + clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())}) + // Return. return clnt, nil } @@ -393,6 +424,16 @@ func (adm AdminClient) setUserAgent(req *http.Request) { } } +func (adm AdminClient) getSecretKey() string { + value, err := adm.credsProvider.Get() + if err != nil { + // Return empty, call will fail. + return "" + } + + return value.SecretAccessKey +} + // newRequest - instantiate a new HTTP request for a given method. func (adm AdminClient) newRequest(method string, reqData requestData) (req *http.Request, err error) { // If no method is supplied default to 'POST'. @@ -415,6 +456,17 @@ func (adm AdminClient) newRequest(method string, reqData requestData) (req *http return nil, err } + value, err := adm.credsProvider.Get() + if err != nil { + return nil, err + } + + var ( + accessKeyID = value.AccessKeyID + secretAccessKey = value.SecretAccessKey + sessionToken = value.SessionToken + ) + adm.setUserAgent(req) for k, v := range reqData.customHeaders { req.Header.Set(k, v[0]) @@ -425,7 +477,7 @@ func (adm AdminClient) newRequest(method string, reqData requestData) (req *http req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(reqData.content))) req.Body = ioutil.NopCloser(bytes.NewReader(reqData.content)) - req = s3signer.SignV4(*req, adm.accessKeyID, adm.secretAccessKey, "", location) + req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location) return req, nil } diff --git a/pkg/madmin/config-commands.go b/pkg/madmin/config-commands.go index 9c43f1ab5..941cbf871 100644 --- a/pkg/madmin/config-commands.go +++ b/pkg/madmin/config-commands.go @@ -39,7 +39,7 @@ func (adm *AdminClient) GetConfig(ctx context.Context) ([]byte, error) { return nil, httpRespToErrorResponse(resp) } - return DecryptData(adm.secretAccessKey, resp.Body) + return DecryptData(adm.getSecretKey(), resp.Body) } // SetConfig - set config supplied as config.json for the setup. @@ -56,7 +56,7 @@ func (adm *AdminClient) SetConfig(ctx context.Context, config io.Reader) (err er return err } configBytes := configBuf[:n] - econfigBytes, err := EncryptData(adm.secretAccessKey, configBytes) + econfigBytes, err := EncryptData(adm.getSecretKey(), configBytes) if err != nil { return err } diff --git a/pkg/madmin/config-history-commands.go b/pkg/madmin/config-history-commands.go index 9e145219c..cd74bed42 100644 --- a/pkg/madmin/config-history-commands.go +++ b/pkg/madmin/config-history-commands.go @@ -113,7 +113,7 @@ func (adm *AdminClient) ListConfigHistoryKV(ctx context.Context, count int) ([]C return nil, httpRespToErrorResponse(resp) } - data, err := DecryptData(adm.secretAccessKey, resp.Body) + data, err := DecryptData(adm.getSecretKey(), resp.Body) if err != nil { return nil, err } diff --git a/pkg/madmin/config-kv-commands.go b/pkg/madmin/config-kv-commands.go index aa6b4cad2..314a30132 100644 --- a/pkg/madmin/config-kv-commands.go +++ b/pkg/madmin/config-kv-commands.go @@ -25,7 +25,7 @@ import ( // DelConfigKV - delete key from server config. func (adm *AdminClient) DelConfigKV(ctx context.Context, k string) (err error) { - econfigBytes, err := EncryptData(adm.secretAccessKey, []byte(k)) + econfigBytes, err := EncryptData(adm.getSecretKey(), []byte(k)) if err != nil { return err } @@ -52,7 +52,7 @@ func (adm *AdminClient) DelConfigKV(ctx context.Context, k string) (err error) { // SetConfigKV - set key value config to server. func (adm *AdminClient) SetConfigKV(ctx context.Context, kv string) (err error) { - econfigBytes, err := EncryptData(adm.secretAccessKey, []byte(kv)) + econfigBytes, err := EncryptData(adm.getSecretKey(), []byte(kv)) if err != nil { return err } @@ -100,7 +100,7 @@ func (adm *AdminClient) GetConfigKV(ctx context.Context, key string) (Targets, e return nil, httpRespToErrorResponse(resp) } - data, err := DecryptData(adm.secretAccessKey, resp.Body) + data, err := DecryptData(adm.getSecretKey(), resp.Body) if err != nil { return nil, err } diff --git a/pkg/madmin/user-commands.go b/pkg/madmin/user-commands.go index ce6eb37a5..e0ed1a01a 100644 --- a/pkg/madmin/user-commands.go +++ b/pkg/madmin/user-commands.go @@ -87,7 +87,7 @@ func (adm *AdminClient) ListUsers(ctx context.Context) (map[string]UserInfo, err return nil, httpRespToErrorResponse(resp) } - data, err := DecryptData(adm.secretAccessKey, resp.Body) + data, err := DecryptData(adm.getSecretKey(), resp.Body) if err != nil { return nil, err } @@ -152,7 +152,7 @@ func (adm *AdminClient) SetUser(ctx context.Context, accessKey, secretKey string if err != nil { return err } - econfigBytes, err := EncryptData(adm.secretAccessKey, data) + econfigBytes, err := EncryptData(adm.getSecretKey(), data) if err != nil { return err } @@ -239,7 +239,7 @@ func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string return auth.Credentials{}, err } - econfigBytes, err := EncryptData(adm.secretAccessKey, data) + econfigBytes, err := EncryptData(adm.getSecretKey(), data) if err != nil { return auth.Credentials{}, err } @@ -260,7 +260,7 @@ func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string return auth.Credentials{}, httpRespToErrorResponse(resp) } - data, err = DecryptData(adm.secretAccessKey, resp.Body) + data, err = DecryptData(adm.getSecretKey(), resp.Body) if err != nil { return auth.Credentials{}, err } @@ -298,7 +298,7 @@ func (adm *AdminClient) GetServiceAccount(ctx context.Context, serviceAccountAcc return auth.Credentials{}, httpRespToErrorResponse(resp) } - data, err := DecryptData(adm.secretAccessKey, resp.Body) + data, err := DecryptData(adm.getSecretKey(), resp.Body) if err != nil { return auth.Credentials{}, err }