diff --git a/integration/service_account_test.go b/integration/service_account_test.go
new file mode 100644
index 000000000..cb0907b88
--- /dev/null
+++ b/integration/service_account_test.go
@@ -0,0 +1,168 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package integration
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "testing"
+ "time"
+
+ iampolicy "github.com/minio/pkg/iam/policy"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAddServiceAccount(t *testing.T) {
+ /*
+ This is an atomic API Test to add a user service account, the intention
+ is simple, add a user and make sure the response is 201 meaning that the
+ user got added successfully.
+ After test completion, it is expected that user is removed, so other
+ tests like users.ts can run over clean data and we don't collide against
+ it.
+ */
+
+ assert := assert.New(t)
+
+ client := &http.Client{
+ Timeout: 3 * time.Second,
+ }
+
+ // Add service account
+ fmt.Println(".......................TestServiceAccountPolicy(): Create Data to add user")
+ requestDataAddServiceAccount := map[string]interface{}{
+ "accessKey": "testuser1",
+ "secretKey": "password",
+ "policy": "{" +
+ "\n \"Version\": \"2012-10-17\"," +
+ "\n \"Statement\": [" +
+ "\n {" +
+ "\n \"Effect\": \"Allow\"," +
+ "\n \"Action\": [" +
+ "\n \"s3:GetBucketLocation\"," +
+ "\n \"s3:GetObject\"" +
+ "\n ]," +
+ "\n \"Resource\": [" +
+ "\n \"arn:aws:s3:::*\"" +
+ "\n ]" +
+ "\n }" +
+ "\n ]" +
+ "\n}",
+ }
+
+ fmt.Println("..............................TestServiceAccountPolicy(): Prepare the POST")
+ requestDataJSON, _ := json.Marshal(requestDataAddServiceAccount)
+ requestDataBody := bytes.NewReader(requestDataJSON)
+ request, err := http.NewRequest(
+ "POST", "http://localhost:9090/api/v1/service-account-credentials", requestDataBody)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
+ request.Header.Add("Content-Type", "application/json")
+
+ fmt.Println(".................................TestServiceAccountPolicy(): Make the POST")
+ response, err := client.Do(request)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ fmt.Println("..................................TestServiceAccountPolicy(): Verification")
+ fmt.Println(".................................TestServiceAccountPolicy(): POST response")
+ fmt.Println(response)
+ fmt.Println("....................................TestServiceAccountPolicy(): POST error")
+ fmt.Println(err)
+ if response != nil {
+ fmt.Println("POST StatusCode:", response.StatusCode)
+ assert.Equal(201, response.StatusCode, "Status Code is incorrect")
+ }
+
+ fmt.Println("...................................TestServiceAccountPolicy(): Remove user")
+
+ // Test policy
+ fmt.Println(".......................TestAddUserServiceAccount(): Create Data to add user")
+ request, err = http.NewRequest(
+ "GET", "http://localhost:9090/api/v1/service-accounts/testuser1/policy", nil)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
+ request.Header.Add("Content-Type", "application/json")
+
+ fmt.Println(".................................TestAddServiceAccount(): Make the POST")
+ response, err = client.Do(request)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ fmt.Println("..................................TestAddServiceAccount(): Verification")
+ fmt.Println(".................................TestAddServiceAccount(): POST response")
+ fmt.Println(response)
+ fmt.Println("....................................TestAddServiceAccount(): POST error")
+ fmt.Println(err)
+ if response != nil {
+ fmt.Println("POST StatusCode:", response.StatusCode)
+ assert.Equal(200, response.StatusCode, "Status Code is incorrect")
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(response.Body)
+ var actual *iampolicy.Policy
+ var expected *iampolicy.Policy
+ json.Unmarshal(buf.Bytes(), actual)
+ policy, err := json.Marshal(requestDataAddServiceAccount["policy"])
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ json.Unmarshal(policy, expected)
+ assert.Equal(expected, actual)
+ }
+
+ fmt.Println("...................................TestServiceAccountPolicy(): Remove service account")
+
+ // {{baseUrl}}/user?name=proident velit
+ // Investiga como se borra en el browser.
+ request, err = http.NewRequest(
+ "DELETE", "http://localhost:9090/api/v1/service-accounts/testuser1", nil)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
+ request.Header.Add("Content-Type", "application/json")
+
+ fmt.Println("...............................TestServiceAccountPolicy(): Make the DELETE")
+ response, err = client.Do(request)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ fmt.Println("..................................TestServiceAccountPolicy(): Verification")
+ fmt.Println("...............................TestServiceAccountPolicy(): DELETE response")
+ fmt.Println(response)
+ fmt.Println("..................................TestServiceAccountPolicy(): DELETE error")
+ fmt.Println(err)
+ if response != nil {
+ fmt.Println("DELETE StatusCode:", response.StatusCode)
+ assert.Equal(204, response.StatusCode, "has to be 204 when delete user")
+ }
+
+}
diff --git a/restapi/client-admin.go b/restapi/client-admin.go
index 4c1da2ed0..f28b38183 100644
--- a/restapi/client-admin.go
+++ b/restapi/client-admin.go
@@ -98,6 +98,7 @@ type MinioAdmin interface {
addServiceAccount(ctx context.Context, policy *iampolicy.Policy, user string, accessKey string, secretKey string) (madmin.Credentials, error)
listServiceAccounts(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error)
deleteServiceAccount(ctx context.Context, serviceAccount string) error
+ infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error)
// Remote Buckets
listRemoteBuckets(ctx context.Context, bucket, arnType string) (targets []madmin.BucketTarget, err error)
getRemoteBucket(ctx context.Context, bucket, arnType string) (targets *madmin.BucketTarget, err error)
@@ -306,6 +307,11 @@ func (ac AdminClient) deleteServiceAccount(ctx context.Context, serviceAccount s
return ac.Client.DeleteServiceAccount(ctx, serviceAccount)
}
+// implements madmin.InfoServiceAccount()
+func (ac AdminClient) infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error) {
+ return ac.Client.InfoServiceAccount(ctx, serviceAccount)
+}
+
// AccountInfo implements madmin.AccountInfo()
func (ac AdminClient) AccountInfo(ctx context.Context) (madmin.AccountInfo, error) {
return ac.Client.AccountInfo(ctx, madmin.AccountOpts{})
diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go
index a81819f20..4a13eace8 100644
--- a/restapi/embedded_spec.go
+++ b/restapi/embedded_spec.go
@@ -3090,6 +3090,37 @@ func init() {
}
}
},
+ "/service-accounts/{access_key}/policy": {
+ "get": {
+ "tags": [
+ "UserAPI"
+ ],
+ "summary": "Get Service Account Policy",
+ "operationId": "GetServiceAccountPolicy",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "access_key",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "default": {
+ "description": "Generic error response.",
+ "schema": {
+ "$ref": "#/definitions/error"
+ }
+ }
+ }
+ }
+ },
"/service/restart": {
"post": {
"tags": [
@@ -9144,6 +9175,37 @@ func init() {
}
}
},
+ "/service-accounts/{access_key}/policy": {
+ "get": {
+ "tags": [
+ "UserAPI"
+ ],
+ "summary": "Get Service Account Policy",
+ "operationId": "GetServiceAccountPolicy",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "access_key",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "default": {
+ "description": "Generic error response.",
+ "schema": {
+ "$ref": "#/definitions/error"
+ }
+ }
+ }
+ }
+ },
"/service/restart": {
"post": {
"tags": [
diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go
index 93053bef1..916a14743 100644
--- a/restapi/operations/console_api.go
+++ b/restapi/operations/console_api.go
@@ -197,6 +197,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
UserAPIGetObjectMetadataHandler: user_api.GetObjectMetadataHandlerFunc(func(params user_api.GetObjectMetadataParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.GetObjectMetadata has not yet been implemented")
}),
+ UserAPIGetServiceAccountPolicyHandler: user_api.GetServiceAccountPolicyHandlerFunc(func(params user_api.GetServiceAccountPolicyParams, principal *models.Principal) middleware.Responder {
+ return middleware.NotImplemented("operation user_api.GetServiceAccountPolicy has not yet been implemented")
+ }),
AdminAPIGetTierHandler: admin_api.GetTierHandlerFunc(func(params admin_api.GetTierParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation admin_api.GetTier has not yet been implemented")
}),
@@ -526,6 +529,8 @@ type ConsoleAPI struct {
UserAPIGetBucketVersioningHandler user_api.GetBucketVersioningHandler
// UserAPIGetObjectMetadataHandler sets the operation handler for the get object metadata operation
UserAPIGetObjectMetadataHandler user_api.GetObjectMetadataHandler
+ // UserAPIGetServiceAccountPolicyHandler sets the operation handler for the get service account policy operation
+ UserAPIGetServiceAccountPolicyHandler user_api.GetServiceAccountPolicyHandler
// AdminAPIGetTierHandler sets the operation handler for the get tier operation
AdminAPIGetTierHandler admin_api.GetTierHandler
// AdminAPIGetUserInfoHandler sets the operation handler for the get user info operation
@@ -869,6 +874,9 @@ func (o *ConsoleAPI) Validate() error {
if o.UserAPIGetObjectMetadataHandler == nil {
unregistered = append(unregistered, "user_api.GetObjectMetadataHandler")
}
+ if o.UserAPIGetServiceAccountPolicyHandler == nil {
+ unregistered = append(unregistered, "user_api.GetServiceAccountPolicyHandler")
+ }
if o.AdminAPIGetTierHandler == nil {
unregistered = append(unregistered, "admin_api.GetTierHandler")
}
@@ -1336,6 +1344,10 @@ func (o *ConsoleAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
+ o.handlers["GET"]["/service-accounts/{access_key}/policy"] = user_api.NewGetServiceAccountPolicy(o.context, o.UserAPIGetServiceAccountPolicyHandler)
+ if o.handlers["GET"] == nil {
+ o.handlers["GET"] = make(map[string]http.Handler)
+ }
o.handlers["GET"]["/admin/tiers/{type}/{name}"] = admin_api.NewGetTier(o.context, o.AdminAPIGetTierHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
diff --git a/restapi/operations/user_api/get_service_account_policy.go b/restapi/operations/user_api/get_service_account_policy.go
new file mode 100644
index 000000000..56888e22b
--- /dev/null
+++ b/restapi/operations/user_api/get_service_account_policy.go
@@ -0,0 +1,88 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// This file is part of MinIO Console Server
+// Copyright (c) 2021 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+package user_api
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime/middleware"
+
+ "github.com/minio/console/models"
+)
+
+// GetServiceAccountPolicyHandlerFunc turns a function with the right signature into a get service account policy handler
+type GetServiceAccountPolicyHandlerFunc func(GetServiceAccountPolicyParams, *models.Principal) middleware.Responder
+
+// Handle executing the request and returning a response
+func (fn GetServiceAccountPolicyHandlerFunc) Handle(params GetServiceAccountPolicyParams, principal *models.Principal) middleware.Responder {
+ return fn(params, principal)
+}
+
+// GetServiceAccountPolicyHandler interface for that can handle valid get service account policy params
+type GetServiceAccountPolicyHandler interface {
+ Handle(GetServiceAccountPolicyParams, *models.Principal) middleware.Responder
+}
+
+// NewGetServiceAccountPolicy creates a new http.Handler for the get service account policy operation
+func NewGetServiceAccountPolicy(ctx *middleware.Context, handler GetServiceAccountPolicyHandler) *GetServiceAccountPolicy {
+ return &GetServiceAccountPolicy{Context: ctx, Handler: handler}
+}
+
+/* GetServiceAccountPolicy swagger:route GET /service-accounts/{access_key}/policy UserAPI getServiceAccountPolicy
+
+Get Service Account Policy
+
+*/
+type GetServiceAccountPolicy struct {
+ Context *middleware.Context
+ Handler GetServiceAccountPolicyHandler
+}
+
+func (o *GetServiceAccountPolicy) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ route, rCtx, _ := o.Context.RouteInfo(r)
+ if rCtx != nil {
+ *r = *rCtx
+ }
+ var Params = NewGetServiceAccountPolicyParams()
+ uprinc, aCtx, err := o.Context.Authorize(r, route)
+ if err != nil {
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+ if aCtx != nil {
+ *r = *aCtx
+ }
+ var principal *models.Principal
+ if uprinc != nil {
+ principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
+ }
+
+ if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
+ o.Context.Respond(rw, r, route.Produces, route, err)
+ return
+ }
+
+ res := o.Handler.Handle(Params, principal) // actually handle the request
+ o.Context.Respond(rw, r, route.Produces, route, res)
+
+}
diff --git a/restapi/operations/user_api/get_service_account_policy_parameters.go b/restapi/operations/user_api/get_service_account_policy_parameters.go
new file mode 100644
index 000000000..eaee614c5
--- /dev/null
+++ b/restapi/operations/user_api/get_service_account_policy_parameters.go
@@ -0,0 +1,88 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// This file is part of MinIO Console Server
+// Copyright (c) 2021 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+package user_api
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime/middleware"
+ "github.com/go-openapi/strfmt"
+)
+
+// NewGetServiceAccountPolicyParams creates a new GetServiceAccountPolicyParams object
+//
+// There are no default values defined in the spec.
+func NewGetServiceAccountPolicyParams() GetServiceAccountPolicyParams {
+
+ return GetServiceAccountPolicyParams{}
+}
+
+// GetServiceAccountPolicyParams contains all the bound params for the get service account policy operation
+// typically these are obtained from a http.Request
+//
+// swagger:parameters GetServiceAccountPolicy
+type GetServiceAccountPolicyParams struct {
+
+ // HTTP Request Object
+ HTTPRequest *http.Request `json:"-"`
+
+ /*
+ Required: true
+ In: path
+ */
+ AccessKey string
+}
+
+// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
+// for simple values it will use straight method calls.
+//
+// To ensure default values, the struct must have been initialized with NewGetServiceAccountPolicyParams() beforehand.
+func (o *GetServiceAccountPolicyParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
+ var res []error
+
+ o.HTTPRequest = r
+
+ rAccessKey, rhkAccessKey, _ := route.Params.GetOK("access_key")
+ if err := o.bindAccessKey(rAccessKey, rhkAccessKey, route.Formats); err != nil {
+ res = append(res, err)
+ }
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+// bindAccessKey binds and validates parameter AccessKey from path.
+func (o *GetServiceAccountPolicyParams) bindAccessKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
+ var raw string
+ if len(rawData) > 0 {
+ raw = rawData[len(rawData)-1]
+ }
+
+ // Required: true
+ // Parameter is provided by construction from the route
+ o.AccessKey = raw
+
+ return nil
+}
diff --git a/restapi/operations/user_api/get_service_account_policy_responses.go b/restapi/operations/user_api/get_service_account_policy_responses.go
new file mode 100644
index 000000000..1a39408ae
--- /dev/null
+++ b/restapi/operations/user_api/get_service_account_policy_responses.go
@@ -0,0 +1,131 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// This file is part of MinIO Console Server
+// Copyright (c) 2021 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+package user_api
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "net/http"
+
+ "github.com/go-openapi/runtime"
+
+ "github.com/minio/console/models"
+)
+
+// GetServiceAccountPolicyOKCode is the HTTP code returned for type GetServiceAccountPolicyOK
+const GetServiceAccountPolicyOKCode int = 200
+
+/*GetServiceAccountPolicyOK A successful response.
+
+swagger:response getServiceAccountPolicyOK
+*/
+type GetServiceAccountPolicyOK struct {
+
+ /*
+ In: Body
+ */
+ Payload string `json:"body,omitempty"`
+}
+
+// NewGetServiceAccountPolicyOK creates GetServiceAccountPolicyOK with default headers values
+func NewGetServiceAccountPolicyOK() *GetServiceAccountPolicyOK {
+
+ return &GetServiceAccountPolicyOK{}
+}
+
+// WithPayload adds the payload to the get service account policy o k response
+func (o *GetServiceAccountPolicyOK) WithPayload(payload string) *GetServiceAccountPolicyOK {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the get service account policy o k response
+func (o *GetServiceAccountPolicyOK) SetPayload(payload string) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *GetServiceAccountPolicyOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(200)
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+}
+
+/*GetServiceAccountPolicyDefault Generic error response.
+
+swagger:response getServiceAccountPolicyDefault
+*/
+type GetServiceAccountPolicyDefault struct {
+ _statusCode int
+
+ /*
+ In: Body
+ */
+ Payload *models.Error `json:"body,omitempty"`
+}
+
+// NewGetServiceAccountPolicyDefault creates GetServiceAccountPolicyDefault with default headers values
+func NewGetServiceAccountPolicyDefault(code int) *GetServiceAccountPolicyDefault {
+ if code <= 0 {
+ code = 500
+ }
+
+ return &GetServiceAccountPolicyDefault{
+ _statusCode: code,
+ }
+}
+
+// WithStatusCode adds the status to the get service account policy default response
+func (o *GetServiceAccountPolicyDefault) WithStatusCode(code int) *GetServiceAccountPolicyDefault {
+ o._statusCode = code
+ return o
+}
+
+// SetStatusCode sets the status to the get service account policy default response
+func (o *GetServiceAccountPolicyDefault) SetStatusCode(code int) {
+ o._statusCode = code
+}
+
+// WithPayload adds the payload to the get service account policy default response
+func (o *GetServiceAccountPolicyDefault) WithPayload(payload *models.Error) *GetServiceAccountPolicyDefault {
+ o.Payload = payload
+ return o
+}
+
+// SetPayload sets the payload to the get service account policy default response
+func (o *GetServiceAccountPolicyDefault) SetPayload(payload *models.Error) {
+ o.Payload = payload
+}
+
+// WriteResponse to the client
+func (o *GetServiceAccountPolicyDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
+
+ rw.WriteHeader(o._statusCode)
+ if o.Payload != nil {
+ payload := o.Payload
+ if err := producer.Produce(rw, payload); err != nil {
+ panic(err) // let the recovery middleware deal with this
+ }
+ }
+}
diff --git a/restapi/operations/user_api/get_service_account_policy_urlbuilder.go b/restapi/operations/user_api/get_service_account_policy_urlbuilder.go
new file mode 100644
index 000000000..f89a1b0de
--- /dev/null
+++ b/restapi/operations/user_api/get_service_account_policy_urlbuilder.go
@@ -0,0 +1,116 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// This file is part of MinIO Console Server
+// Copyright (c) 2021 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+
+package user_api
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the generate command
+
+import (
+ "errors"
+ "net/url"
+ golangswaggerpaths "path"
+ "strings"
+)
+
+// GetServiceAccountPolicyURL generates an URL for the get service account policy operation
+type GetServiceAccountPolicyURL struct {
+ AccessKey string
+
+ _basePath string
+ // avoid unkeyed usage
+ _ struct{}
+}
+
+// WithBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *GetServiceAccountPolicyURL) WithBasePath(bp string) *GetServiceAccountPolicyURL {
+ o.SetBasePath(bp)
+ return o
+}
+
+// SetBasePath sets the base path for this url builder, only required when it's different from the
+// base path specified in the swagger spec.
+// When the value of the base path is an empty string
+func (o *GetServiceAccountPolicyURL) SetBasePath(bp string) {
+ o._basePath = bp
+}
+
+// Build a url path and query string
+func (o *GetServiceAccountPolicyURL) Build() (*url.URL, error) {
+ var _result url.URL
+
+ var _path = "/service-accounts/{access_key}/policy"
+
+ accessKey := o.AccessKey
+ if accessKey != "" {
+ _path = strings.Replace(_path, "{access_key}", accessKey, -1)
+ } else {
+ return nil, errors.New("accessKey is required on GetServiceAccountPolicyURL")
+ }
+
+ _basePath := o._basePath
+ if _basePath == "" {
+ _basePath = "/api/v1"
+ }
+ _result.Path = golangswaggerpaths.Join(_basePath, _path)
+
+ return &_result, nil
+}
+
+// Must is a helper function to panic when the url builder returns an error
+func (o *GetServiceAccountPolicyURL) Must(u *url.URL, err error) *url.URL {
+ if err != nil {
+ panic(err)
+ }
+ if u == nil {
+ panic("url can't be nil")
+ }
+ return u
+}
+
+// String returns the string representation of the path with query string
+func (o *GetServiceAccountPolicyURL) String() string {
+ return o.Must(o.Build()).String()
+}
+
+// BuildFull builds a full url with scheme, host, path and query string
+func (o *GetServiceAccountPolicyURL) BuildFull(scheme, host string) (*url.URL, error) {
+ if scheme == "" {
+ return nil, errors.New("scheme is required for a full url on GetServiceAccountPolicyURL")
+ }
+ if host == "" {
+ return nil, errors.New("host is required for a full url on GetServiceAccountPolicyURL")
+ }
+
+ base, err := o.Build()
+ if err != nil {
+ return nil, err
+ }
+
+ base.Scheme = scheme
+ base.Host = host
+ return base, nil
+}
+
+// StringFull returns the string representation of a complete url
+func (o *GetServiceAccountPolicyURL) StringFull(scheme, host string) string {
+ return o.Must(o.BuildFull(scheme, host)).String()
+}
diff --git a/restapi/user_service_accounts.go b/restapi/user_service_accounts.go
index 1faf666f5..65bc28bdb 100644
--- a/restapi/user_service_accounts.go
+++ b/restapi/user_service_accounts.go
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"errors"
+ "fmt"
"strings"
"time"
@@ -90,6 +91,14 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
return user_api.NewListUserServiceAccountsOK().WithPayload(serviceAccounts)
})
+ api.UserAPIGetServiceAccountPolicyHandler = user_api.GetServiceAccountPolicyHandlerFunc(func(params user_api.GetServiceAccountPolicyParams, session *models.Principal) middleware.Responder {
+ serviceAccounts, err := getServiceAccountPolicyResponse(session, params.AccessKey)
+ if err != nil {
+ return user_api.NewGetServiceAccountPolicyDefault(int(err.Code)).WithPayload(err)
+ }
+ return user_api.NewGetServiceAccountPolicyOK().WithPayload(serviceAccounts)
+ })
+
}
// createServiceAccount adds a service account to the userClient and assigns a policy to him if defined.
@@ -268,6 +277,7 @@ func getCreateServiceAccountCredsResponse(session *models.Principal, serviceAcco
}
accounts, err := userAdminClient.listServiceAccounts(ctx, "")
+ fmt.Println("dummy line")
if err != nil {
return nil, prepareError(err)
}
@@ -342,3 +352,33 @@ func getDeleteServiceAccountResponse(session *models.Principal, accessKey string
}
return nil
}
+
+// getServiceAccountPolicy gets policy for a service account
+func getServiceAccountPolicy(ctx context.Context, userClient MinioAdmin, accessKey string) (string, error) {
+ serviceAccountInfo, err := userClient.infoServiceAccount(ctx, accessKey)
+ if err != nil {
+ return "", err
+ }
+ return serviceAccountInfo.Policy, nil
+}
+
+// getServiceAccountPolicyResponse authenticates the user and calls
+// getServiceAccountPolicy to get the policy for a service account
+func getServiceAccountPolicyResponse(session *models.Principal, accessKey string) (string, *models.Error) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
+ defer cancel()
+
+ userAdmin, err := NewMinioAdminClient(session)
+ if err != nil {
+ return "", prepareError(err)
+ }
+ // create a MinIO user Admin Client interface implementation
+ // defining the client to be used
+ userAdminClient := AdminClient{Client: userAdmin}
+
+ serviceAccounts, err := getServiceAccountPolicy(ctx, userAdminClient, accessKey)
+ if err != nil {
+ return "", prepareError(err)
+ }
+ return serviceAccounts, nil
+}
diff --git a/restapi/user_service_accounts_test.go b/restapi/user_service_accounts_test.go
index b8cc00819..40848c3c5 100644
--- a/restapi/user_service_accounts_test.go
+++ b/restapi/user_service_accounts_test.go
@@ -32,6 +32,7 @@ import (
var minioAddServiceAccountMock func(ctx context.Context, policy *iampolicy.Policy, user string, accessKey string, secretKey string) (madmin.Credentials, error)
var minioListServiceAccountsMock func(ctx context.Context, user string) (madmin.ListServiceAccountsResp, error)
var minioDeleteServiceAccountMock func(ctx context.Context, serviceAccount string) error
+var minioInfoServiceAccountMock func(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error)
// mock function of AddServiceAccount()
func (ac adminClientMock) addServiceAccount(ctx context.Context, policy *iampolicy.Policy, user string, accessKey string, secretKey string) (madmin.Credentials, error) {
@@ -48,6 +49,11 @@ func (ac adminClientMock) deleteServiceAccount(ctx context.Context, serviceAccou
return minioDeleteServiceAccountMock(ctx, serviceAccount)
}
+// mock function of InfoServiceAccount()
+func (ac adminClientMock) infoServiceAccount(ctx context.Context, serviceAccount string) (madmin.InfoServiceAccountResp, error) {
+ return minioInfoServiceAccountMock(ctx, serviceAccount)
+}
+
func TestAddServiceAccount(t *testing.T) {
assert := assert.New(t)
// mock minIO client
@@ -154,3 +160,33 @@ func TestDeleteServiceAccount(t *testing.T) {
assert.Equal("error", err.Error())
}
}
+
+func TestGetServiceAccountPolicy(t *testing.T) {
+ assert := assert.New(t)
+ // mock minIO client
+ client := adminClientMock{}
+ function := "getServiceAccountPolicy()"
+
+ // Test-1: getServiceAccountPolicy list serviceaccounts for a user
+ ctx := context.Background()
+ mockResponse := madmin.InfoServiceAccountResp{
+ Policy: "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": [\n \"s3:PutObject\"\n ],\n \"Resource\": [\n \"arn:aws:s3:::*\"\n ]\n }\n ]\n}",
+ }
+ minioInfoServiceAccountMock = func(ctx context.Context, user string) (madmin.InfoServiceAccountResp, error) {
+ return mockResponse, nil
+ }
+ serviceAccount, err := getServiceAccountPolicy(ctx, client, "")
+ if err != nil {
+ t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
+ }
+ assert.Equal(mockResponse.Policy, serviceAccount)
+
+ // Test-2: getServiceAccountPolicy returns an error, handle it properly
+ minioInfoServiceAccountMock = func(ctx context.Context, user string) (madmin.InfoServiceAccountResp, error) {
+ return madmin.InfoServiceAccountResp{}, errors.New("error")
+ }
+ _, err = getServiceAccountPolicy(ctx, client, "")
+ if assert.Error(err) {
+ assert.Equal("error", err.Error())
+ }
+}
diff --git a/swagger-console.yml b/swagger-console.yml
index 8e3e2d974..a4dd947e6 100644
--- a/swagger-console.yml
+++ b/swagger-console.yml
@@ -1260,6 +1260,27 @@ paths:
tags:
- UserAPI
+ /service-accounts/{access_key}/policy:
+ get:
+ summary: Get Service Account Policy
+ operationId: GetServiceAccountPolicy
+ parameters:
+ - name: access_key
+ in: path
+ required: true
+ type: string
+ responses:
+ 200:
+ description: A successful response.
+ schema:
+ type: string
+ default:
+ description: Generic error response.
+ schema:
+ $ref: "#/definitions/error"
+ tags:
+ - UserAPI
+
/users:
get:
summary: List Users